전역 공간에서 this는 전역 객체를 가리킴 → 전역 컨텍스트를 생성하는 주체가 바로 전역객체이기 때문
전역 객체는 자바스크립트 런타임 환경에 따라 다른 이름과 정보를 가지고 있음 (브라우저 환경에서 window, Node.js 환경에서 global)
예제 3-1. 전역 공간에서의 this(브라우저 환경)
console.log(this); // { alert: f(), atob: f(), blur: f(), ... }
console.log(window); // { alert: f(), atob: f(), blur: f(), ... }
console.log(this === window);// true
예제 3-2. 전역 공간에서의 this(Node.js 환경)
console.log(this); // { process: {title: 'node', version: ... } }
console.log(global); // { process: {title: 'node', version: ... } }
console.log(this === global); // true
전역 공간에서만 발생하는 특이한 성질
전역변수를 선언하면 JS엔진은 이를 전역객체의 프로퍼티로 할당
예제 3-3. 전역변수와 전역객체(1)
var a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a); // 1
위 이유는 JS의 모든 변수는 특정 객체의 프로퍼티로서 동작하기 때문
여기서 특정 객체란 실행 컨텍스트의 LexicalEnvironment(L.E)
변수 선언 → L.E의 프로퍼티로 저장 → 호출 시 L.E를 조회해 그 값 반환
그럼 window.a와 this.a로 호출하는 것은 이해되는데, a로 직접 호출할 때는 왜 작동할까? → 스코프 체인에서 a 검색하다가 전역 스코프의 L.E에서 해당 프로퍼티 a를 발견해 그 값을 반환
그럼 전역 공간에서 var 변수 선언 대신 window의 프로퍼티 할당도 똑같은 것 아닌가? → 대부분 맞음(완전 똑같은건 아님)
예제 3-4. 전역변수와 전역객체(2)
var a = 1;
window.b = 2;
console.log(a, window.a, this.a); // 1 1 1
console.log(b, window.b, this.b); // 2 2 2
window.a = 3;
b = 4;
console.log(a, window.a, this.a); // 3 3 3
console.log(b, window.b, this.b); // 4 4 4
하지만 위 방법은 ‘삭제’ 명령에서 전혀 다름
예제 3-5. 전역변수와 전역객체(3)
var a = 1;
delete window.a; // false
console.log(a, window.a, this.a); // 1 1 1
var b = 2;
delete b; // false
console.log(b, window.b, this.b); // 2 2 2
window.c = 3;
delete window.c; // true
console.log(c, window.c, this.c); // Uncaught ReferenceError: c is not defined
window.d = 4;
delete d; // true
console.log(d, window.d, this.d); // Uncaught ReferenceError: d is not defined
위 예제를 통해 전역객체의 프로퍼티로 할당한 경우에는 삭제가 되는 반면, 전역변수로 선언한 경우에는 삭제가 되지 않음 → 사용자가 의도치 않게 삭제하는 것을 방지하는 차원에서 마련한 방어 전략
전역변수를 선언 → JS 엔진이 이를 자동으로 전역객체의 프로퍼티로 할당 → 해당 프로퍼티의 configurable 속성(변경 및 삭제 가능성)을 false로 정의
함수를 실행하는 두 가지 방법
위 둘을 구분하는 유일한 차이는 독립성
JS는 상황별로 this 키워드에 다른 값을 부여하게 함으로써 이를 구현
흔히 메서드를 ‘객체의 프로퍼티에 할당된 함수’로 이해 → 반은 맞고 반은 틀린 얘기
어떤 함수를 객체의 프로퍼티에 할당한다고 해서 그 자체로서 무조건 메서드가 되는 것이 아니라, 객체의 메서드로서 호출할 경우에만 메서드로 동작하고, 그렇지 않으면 함수로 동작
예제 3-6. 함수로서 호출, 메서드로서 호출
var func = function (x) {
console.log(this, x);
};
func(1); // Window { ... } 1
var obj = {
method: func
}
obj.method(2) // { method: f } 2
위 예제를 통해, 원래의 익명함수는 그대로인데 이를 변수에 담아 호출한 경우와 obj 객체의 프로퍼티에 할당해서 호출한 경우에 this가 달라짐
함수로서 호출, 메서드로서 호출을 구분하는 방법 → 함수 앞에 점(.)의 여부 → func(1): 함수로서 호출, obj.method(2): 메서드로서 호출
대괄호 표기법도 해당됨
예제 3-7. 메서드로서 호출 - 점 표기법, 대괄호 표기법
var obj = {
method: function (x) { console.log(this, x); }
};
obj.method(1); // { method: f } 1
obj['method'](2); // { method: f } 2
위 예제들을 통틀어서, 어떤 함수를 호출할 때 그 함수 이름(프로퍼티명) 앞에 객체가 명시돼 있는 경우에는 메서드로 호출한 것, 그렇지 않은 모든 경우에는 함수로 호출한 것
this에는 호출한 주체에 대한 정보가 담김
어떤 함수를 메서드로서 호출한 경우 호출 주체는 함수명 앞의 객체
점 표기법의 경우 마지막 점 앞에 명시된 객체가 곧 this
예제 3-8. 메서드 내부에서의 this
var obj = {
methodA: function () { console.log(this); },
inner: {
methodB: function () { console.log(this); }
}
};
obj.methodA(); // { methodA: f, inner: {...} } ( === obj)
obj['methodA'](); // { methodA: f, inner: {...} } ( === obj)
obj.inner.methodB(); // { methodB: f } ( === obj.inner)
obj.inner['methodB'](); // { methodB: f } ( === obj.inner)
obj['inner'].methodB(); // { methodB: f } ( === obj.inner)
obj['inner']['methodB'](); // { methodB: f } ( === obj.inner)
메서드의 내부 함수에서도 이를 함수로서 호출했는지, 메서드로서 호출했는지만 파악하면 this의 값을 정확히 파악할 수 있음
예제 3-9. 내부함수에서의 this
var obj1 = {
outer: function () {
console.log(this); // obj1
var innerFunc = function () {
console.log(this); // 전역객체, obj2
}
innerFunc();
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod();
}
};
obj1.outer();
위 예제를 보면, this 바인딩은 해당 함수를 호출하는 구문 앞에 점 또는 대괄효 표기가 있는지가 관건
위 내용을 보면 this가 주는 이미지와 좀 다름
호출 당시 주변 환경의 this를 그대로 상속 받아 사용할 수 있으면 좋을 듯
this 역시 현재 컨텍스트에 바인딩된 대상이 없으면 직전 컨텍스트의 this를 바라보도록 하는 것
ES5에서는 상속할 방법이 없어 변수를 활용하여 우회
예제 3-10. 내부함수에서의 this를 우회하는 방법
var obj1 = {
outer: function () {
console.log(this); // { outer: f }
var innerFunc = function () {
console.log(this); // Window { ... }
}
innerFunc();
var self = this;
var innerFunc2 = function () {
console.log(self); // { outer: f }
};
innerFunc2();
}
};
obj1.outer();
ES6에서는 this를 바인딩하지 않는 화살표 함수 도입 → 상위 스코프의 this를 그대로 활용
위 우회법이 불필요
예제 3-11. this를 바인딩하지 않는 함수(화살표 함수)
var obj = {
outer: function () {
console.log(this); // { outer: f }
var innerFunc = () => {
console.log(this); // { outer: f }
};
innerFunc();
}
};
obj.outer();
그 밖에도 call, apply 등의 메서드를 활용해 함수를 호출할 때 명시적으로 this를 지정하는 방법도 있음