자바스크립트의 THIS
봐도 봐도 이해가 되지 않고 계속해서 헷깔리는 this
에 대하여 정리하고자 한다.
이번 글에서는 기본바인딩
암시적 바인딩
명시적 바인딩
new 바인딩
에 대해 정리할 것이다.
🙄 This 란
This
는 작성 시점이 아닌 런타임 시점에 바인딩 되며 함수 호출 당시 상황에 따라 콘텍스트가 결정된다. 함수 선언 위치와 상관 없이 this 바인딩은 오로지 어떻게 함수를 호출했느냐에 따라 정해진다.
🔨 This
의 기본 규칙
1. 기본바인딩
-
단독 함수 실행 Standalone Function Invocation
에 관한 규칙이다. -
this
의 기본 규칙이다. -
일반적으로 호출 했을 때 (전역 객체가 바인딩 된다.)
function foo() { console.log(this.a); } var a = 2; foo(); //2
-
엄격모드 (전역 객체가 기본 바인딩 대상에서 제외된다. 이 때
this
는undefined
이다.)function foo() { "use strict"; console.log(this.a); } var a = 2; foo(); // 타입 에러 'this'는 'undefined'입니다
2. 암시적 바인딩
-
호출부에 콘텍스트 객체가 있는지, 객체의 소유/포함 여부를 확인하는 것이다.
-
object에서 호출 (object가 바인딩 된다.)
function foo() { console.log(this.a); } var obj = { a: 2, foo: foo, }; obj.foo(); //2
- 호출부는
obj
콘텍스트로foo()
를 참조하므로obj
객체는 함수 호출 시점에 함수의 레퍼런스를 '소유'하거나 '포함' 한다고 볼 수 있다. foo()
호출 시obj
는this
이니this.a
는obj.a
가 된다.
- 호출부는
-
객체 프로퍼티 참조 체이닝 (최상위object가 바인딩)
function foo() { console.log(this.a); } var obj2 = { a: 42, foo: foo, }; var obj1 = { a: 2, obj2: obj2, }; obj1.obj2.foo(); //42
- 최상위 수준의 정보만 호출부와 연관된다.
obj1.obj2.foo()
처럼 체이닝 호출을 할 경우 중간 단계인obj1.a
값은 무시된다.
- 최상위 수준의 정보만 호출부와 연관된다.
-
암시적 소실 - 암시적으로 바인딩 된 함수에서 바인딩이 소실되는 경우
function foo() { console.log(this.a); } var obj = { a: 2, foo: foo, }; var bar = obj.foo; // 함수 레퍼런스 var a = "엥, 전역이네!"; //a는 전역객체의 프로퍼티 bar(); //엥, 전역이네!
bar
는foo
를 가리키는 또 다른 레퍼런스다.- 그냥 일반적으로
bar()
를 호출하므로 기본 바인딩이 적용된다.
-
암시적 소실2 - 콜백함수
function foo() { console.log(this.a); } function doFoo(fn) { fn(); } var obj = { a: 2, foo: foo, }; var a = "엥, 전역이네"; doFoo(obj.foo); //엥, 전역이네
- 인자로 전달하는 건 일종의 암시적 할당이다. 함수를 인자로 넘기면 암시적으로 레퍼런스가 할당되어 이전 예제와 결과가 같다.
3. 명시적 바인딩
-
call
apply
:this
에 바인딩 할 객체를 첫째 인자로 받아 함수 호출 시 이 객체를this
로 세팅한다. -
명시적 바인딩 -
this
는 반드시 호출 시 넘긴 인자가 된다.function foo() { console.log(this.a); } var obj = { a: 2, }; foo.call(obj); //2
-
하드 바인딩 -
this
바인딩이 중간에 소실되거나 프레임워크가 임의로 덮어쓰는 문제 해결function foo() { console.log(this.a); } var obj = { a: 2, }; var bar = function () { foo.call(obj); }; bar(); setTimeout(bar, 100); //하드 바인딩된 'bar'에서 재정의된 'this'는 의미없다. bar.call(window);
- 함수
bar()
내부에서foo.call(obj)
로foo
를 호출하면서obj
를this
에 강제로 바인딩 하도록 하드 코딩한다.
- 함수
-
하드 바인딩
bind
- 하드 바인딩은 매우 자주 쓰는 패턴이라 ES5 내장 유틸리티에 구현되어 있다.
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
연산자가 있을 때 호출되는 일반함수다. - 클래스에 붙은 것도 아니고 클래스 인스턴스화 기능도 없다.
생성자 함수(Constructor Function)
가 아니라함수를 생성하는 호출(Construction Calls Of Functions)
이라고 옳다.
- 자바스크립트의
-
new
를 붙여 생성자 호출을 했을 때 벌어지는 일- 새 객체가 만들어진다.
- 새로 생성된 객체의
[[Prototype]]
이 연결된다. - 새로 생성된 객체는 해당 함수 호출 시
this
로 바인딩 된다. - 이 함수가 자신의 또 다른 객체를 반환하지 않는 한
new
와 함께 호출된 함수는 자동으로 새로 생성된 객체를 반환한다.
function foo(a) { this.a = a; } var bar = new foo(2); console.log(bar.a); //2
new
는 함수 호출 시this
를 새 객체와 바인딩 하는 방법이고, 이것이new
바인딩이다.
This에 대해 기본적으로 많이 알고 있는 내용들에 대하여 정리해봤다.
예시를 보면서 몇 번이고 되돌아가서 많은 양이 아닌데도 읽는데 꽤 오랜 시간이 걸리는 것 같다.
순수 js를 쓰면서 This 때문에 당황스러웠던 적이 몇 번 있는데, 그 때의 경험이 있어서 조금 더 와닿았다. 다음 번에 읽을 때는 지금보다는 쉽게 읽을 수 있을거라는 기대를.............해본다....
(🍕TIP) new 에 대한 체크는 이런 식으로 할 수 있다..
function Foo() {
if (!new.target) {
throw "Foo() 함수는 new 연산자로 호출되어야 합니다!";
}
}
Foo(); //Foo() 함수는 new 연산자로 호출되어야 합니다!
이 다음에는 this가 바인딩 되는 순서(우선순위)
, 바인딩 예외
, 어휘적(lexical) this
에 대해 정리할 것이다. 😊
출처
[You don't know JS - this와 객체 프로토 타입, 비동기와 성능] - 카일심슨