Ch1. this라나 뭐라나
- this는 모든 함수 스코프 내에 자동으로 설정되는 특수한 식별자이다.
this를 왜 사용할까
function identify() {
return this.name.toUpperCase()
}
function speak() {
var greeting = "Hello, I`m " + identify.call(this)
console.log(greeting)
}
var me = {
name: "Kyle",
}
var you = {
name: "Reader",
}
identify.call(me) // KYLE
identify.call(you) // READER
speak.call(me) // "Hello, I`m KYLE"
speak.call(you) // "Hello, I`m READER"
- 사용 패턴이 복잡해질수록 명시적인 인자로 콘텍스트를 넘기는 방법이 this 콘텍스트를 사용하는 것보다 코드가 더 지저분해진다.
헷갈리는 것들
- this는 자기 자신도 아니며, 자신의 스코프를 가리키는 것도 아니다.
- this로 함수 자기 자신을 참조할 수 없다.
this는 자기 자신을 가리키는가?
- 함수가 내부에서 자기 자신을 가리킬 일은 재귀 로직이나, 최초 호출 시 이벤트에 바인딩 된 함수 자신을 언바인딩 할때가 있다.
- 자바스크립트에 생소한 개발자는 함수를 객체로 참조하여 상태를 저장할 수 있을거라고 생각한다. 이렇게 할 수 있지만, 더 좋ㅇ느 장소에 상태를 저장하는 패턴에 대해서 이 책의 나머지 부분에서 설명한다.
this는 자신의 스코프를 가리키는가?
- this는 어떤 식으로도 함수의 렉시컬 스코프를 참조하지 않는다.
- 렉시컬 스코프 안에 있는 뭔가를 this 레퍼런스로 참조하기란 애당초 가능하지 않다.
결국 this는 무엇인가
- this는 작성 시점이 아닌 런타임 시점에 바인딩 되며 함수 호출 당시 상황에 따라 콘텍스트가 결정된다. 오로지 어떻게 함수를 호출했느냐에 따라 정해진다.
- 어떤 함수를 호출하면 실행 콘텍스트가 만들어진다. 여기에 함수가 호출된 근원(콜스택)과 호출 방법, 전달된 인자 등의 정보가 담겨있다. this 레퍼런스는 그중 하나로, 함수가 실행되는 동안 이용할 수 있다.
- this는 함수 자신이나 함수의 렉시컬 스코프를 가리키는 레퍼런스가 아니다.
- this는 실제로 함수 호출 시점에 바인딩 되며 무엇을 가리킬지는 전적으로 함수를 호출한 코드에 달렸다.
Ch2. this가 이런 거로군!
2.1 호출부
- this 바인딩의 개념을 이해하려면 함수 호출 코드부터 확인해야 한다.
- 호출부는 현재 실행 중인 함수 '직전'의 호출 코드 '내부'에 있다
function a() {
// 호출 스택: ['a']
// 해당 함수의 호출부는 전역 스코프 내부이다.
console.log("a")
b() // b의 호출부
}
function b() {
// 호출 스택: ['a', 'b']
// 호출부는 'a'의 내부다.
console.log("b")
c() // c의 호출부
}
function c() {
// 호출 스택: ['a', 'b','c'];
// 호출부는 b의 내부이다.
console.log("c")
}
a() // a의 호출부
- this 바인딩은 오직 호출부와 연관되기 때문에 코드를 주의 깊게 봐야한다.
2.2 단지 규칙일 뿐
- 함수가 실행되는 동안 this가 무엇을 참조할지를 호출부는 어떻게 결정하는 것일까.
- 호출부를 살펴보고 다음 4가지 규칙 중 어느 것이 해당하는지 확인한다.
(1) 기본 바인딩
- 가장 평범한 함수 호출인 '단독 함수 실행'에 관한 규칙으로 나머지 규칙에 해당하지 않을 경우 적용되는 this의 기본 규칙이다.
function foo() {
console.log(this.a)
}
var a = 2
foo()
- 위 코드는 기본 바인딩이 적용되어 this는 전역 객체를 참조한다. foo()의 호출부는 전역변수이다.
- 엄격 모드에서는 전역 객체가 기본 바인딩 대상에서 제외된다. 이 때는 this가 undefined가 된다.
(2) 암시적 바인딩
- 암시적 바인딩은 호출부에 콘텍스트 객체가 있는지 확인하여 판단한다.
function foo() {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo,
}
obj.foo() // 2
- 위 코드에서 호출부는 obj 콘텍스트로 foo()를 참조하므로 obj 객체는 함수 호출 시점에 함수의 레퍼런스를 소유 / 포함한다고 볼 수 있다.
- 함수 레퍼런스에 대한 콘텍스트 객체가 존재할 때 암시적 바인딩 규칙에 따르면 바로 이 콘텍스트 객체가 함수 호출 시 this에 바인딩 된다.
암시적 소실
function foo() {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo,
}
var bar = obj.foo
var a = "엥 전역이네"
bar()
- bar는 obj의 foo를 참조하는 변수처럼 보이지만 실은 foo를 직접 가리키는 또 다른 레퍼런스다.
- 콜백 함수 같은 경우도 함수의 인자에 함수를 넣으면 call by reference로 넘기는 것이기 때문에 위의 결과와 같은 결과가 나온다.
(3) 명시적 바인딩
- call과 apply 메서드를 사용하여 this를 명시적으로 함수에 바인딩해줄 수 있다.
- this에 바인딩 할 객체를 첫째 인자로 받아 함수 호출 시 이 객체를 this로 세팅한다. 직접 바인딩하기 때문에 명시적 바인딩이라고 한다.
function foo() {
console.log(this.a)
}
var obj = {
a: 2,
}
foo.call(obj)
- 인자에 객체 대신 단순 원시 값을 인자로 전달하면 원시 값에 대응되는 객체로 래핑된다.
- 명시적으로 해당 함수를 어떻게 호출하던 객체를 this에 강제로 바인딩하는 함수가 있다. 이것은 ES5에서 추가되어, bind함수로 사용할 수 있다.
function foo(something) {
console.log(this.a, something)
return this.a + something
}
var obj = {
a: 2,
}
var bar = foo.bind(obj)
var b = bar(3) // 2 3
console.log(b) // 5
- bind 함수는 주어진 this 콘텍스트로 원본 함수를 호출하도록 하드 코딩된 새 함수를 반환한다.
(4) new 바인딩
- 자바스크립트에서 new는 의미상 클래스 지향적인 기능과 아무 상관이 없다.
- 자바스크립트 생성자는 앞에 new 연산자가 있을 때 호출되는 일반 함수에 불과하다. 생성자 함수가 아니라 함수를 생성하는 호출이라고 해야 옳다.
- 함수 앞에 new를 붙여 생성자 호출을 하면 다음과 같은 일이 일어난다.
- 새 객체가 툭 만들어진다.
- 새로 생성된 객체의 [[Prototype]]이 연결된다.
- 새로 생성된 객체는 해당 함수 호출 시 this로 바인딩 된다.
- 이 함수가 자신의 또 다른 객체를 반환하지 않는 한 new와 함께 호출된 함수는 자동으로 새로 생성된 객체를 반환한다.
- new는 함수 호출 시 this를 새 객체와 바인딩 하는 방법이다.
2.3 적용 순서
- new로 함수를 호출했는가? (맞으면 새로 생성된 객체가 this);
- call 또는 apply로 함수를 호출 했는가? (맞으면 명시적으로 지정된 객체가 this)
- 함수를 객체를 소유 또는 포함하는 형태로 호출했는가? (맞으면 이 콘텍스트 객체가 this)
- 그 외의 경우에 this는 기본값(엄격 모드 시 undefined, 아닐 시 전역 객체)으로 세팅된다.
- 위 네 가지가 대부분 함수 호출 시 this가 바인딩 되는 규칙의 전부다.
2.4 바인딩 예외
- call,apply,bind 함수에 null 또는 undefined를 넘기면 this 바인딩이 무시되고 기본 바인딩이 적용된다.
- 하지만 null을 사용할 때 리스크가 있는데, 해당 함수가 내부적으로 this 레퍼런스를 참조하면 기본 바인딩이 적용되어 전역 변수를 참조하거나 변경하는 일이 벌어질 수도 있다. 그렇기 때문에 부작용과 무관한 객체를 this로 바인딩하는 것이 좋다. 책에서는 Object.create(null)을 이용해서 완전히 빈 객체를 넘기는 것을 권장하고 있다.
2.5 어휘적 this
- 일반적인 함수들은 위와 같은 규칙을 따르지만, 화살표 함수는 예외이다. 화살표 함수는 Enclosing Scope를 보고 this를 알아서 바인딩 한다.
- 화살표 함수의 어휘적 바인딩은 절대로 오버라이드 할 수 없다.
- 다음 두 가지 사항을 명심하자
- 오직 렉시컬 스코프만 사용하고 this 스타일의 코드는 쓰지 않는다.
- 필요하면 완전한 this 스타일의 코드를 구현하되 self = this나 화살표 함수같은 것은 삼간다.
- 두 스타일 모두 적절히 혼용하여 효율적인 프로그래밍을 할 수도 있지만 저자는 골칫덩어리 코드가 될 수 있다고 우려한다.