Java에서의 this는 인스턴스 자신(self)을 가리키는 참조변수이다.
함수 호출 방식에 따라 this에 바인딩될 객체가 동적으로 결정된다.
// 전역 공간(브라우저)
console.log(this) // window {aler: f(), atob: f(), ...}
// 전역 공간(Node.js)
console.log(this) // global {aler: f(), atob: f(), ...}
// 전역 공간(브라우저)
console.log(a); // 출력: 1
console.log(window.a); // 출력: 1
console.log(this.a); // 출력: 1
전역공간에서 선언한 변수 a에 1을 할당했을 뿐인데 window.a와 this.a 모두 1이 출력된다. 자바스크립트의 모든 변수는 특정 객체의 프로퍼티로서 작동한다는 것을 볼 수 있다.
대부분의 경우 해당 변수 a 는 window 의 프로퍼티로써의 역할과 거의 동일하게 움직이지만, delete 키워드를 이용한 '삭제' 명령에서는 다른 모습을 보여준다.
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
delete a; // false
// (=== delete window.a;)
console.log(a, window.a, this.a) // 출력: 1 1 1
delete b; // true
// (=== delete window.b;)
console.log(b, window.b, this.b) // Uncaught ReferenceError : b is not defined
위처럼, 전역변수로 선언을 하든, 전역객체의 프로퍼티로 선언을 하든 동작은 같지만 delete 명령시 전역객체의 프로퍼티로 선언을 한 경우만 해당 변수를 삭제할 수 있는 것을 알 수 있다.
이는 JS 엔진이 변수의 의도치 않은 삭제에 대해 마련해둔 방어 전략이라 할 수 있는데, 전역객체의 프로퍼티로 선언하는 경우만 삭제 여부의 configurable 속성을 true로 부여하게 해놓은 것이다.
메서드로서 호출할 때 그 메서드 내부에서의 this
함수를 실행하는 방법은 여러 가지가 있는데, 가장 일반적인 방법 두 가지는 함수로서 호출하는 경우와 메서드로서 호출하는 경우이다.
함수는 그 자체로 독립적인 기능을 수행하는 반면, 메서드는 자신을 호출한 대상 객체에 관한 동작을 수행한다. 따라서 이 둘을 독립성을 기준으로 구분한다.
어떤 함수를 객체의 프로퍼티에 할당한다고 해서 그 자체로서 무조건 메서드가 되는 것이 아니라 객체의 메서드로서 호출할 경우에만 메서드로 동작하고, 그렇지 않으면 함수로 동작한다.
function a() {console.log(this)}
var obj = {
method: a
}
a(); // 함수로서 호출
obj.method(); // 메서드로서 호출
'함수로서 호출'과 메서드로서 호출'은 함수 앞에 점(.)이 있는지 여부만으로 간단하게 구분할 수 있다. (대괄호도 포함)
메서드 내부에서의 this는 호출한 주체에 대한 정보가 담긴다. 어떤 함수를 메서드로서 호출하는 경우 호출 주체는 바로 함수명(프로퍼티명) 앞의 객체이다.
함수로서 호출할 때 그 함수 내부에서의 this
다음은 메서드의 내부 함서에서의 this를 우회하는 방법이다.
var obj = {
outer: function() {
console.log(this); // 출력: obj1
var self = this; // 상위스코프를 가리키는 임의의 self 생성
var innerFunction = function() {
console.log(self); // 출력: obj1 (this 대신 사용)
};
innerFunction();
}
};
obj.outer();
self 라는 변수를 만들어서 사용하는 것인데, 상위 스코프를 가리키는 this를 self 에 담아두고 이를 this 대신 이용하는 것이다.
변수에 상위스코프를 담아서 this 대신에 사용한다. 문제없이 잘 작동하고 ES6가 나오기 이전까지는 많이 쓰이던 방식이다. 직관적으로 변수에 담아서 this 대신에 사용한다는 것이 썩 내키지는 않는 사람들도 있을 것이다. 그래서 ES6는 우리에게 화살표 함수를 제공해주었다.
var obj = {
outer: function() {
console.log(this); // 출력: obj1
var innerFunction = () => {
console.log(this); // 출력: obj1
}
innerFunction(); // 실행컨텍스트 생성 시 화살표 함수를 만나면 this 바인딩 제외
}
};
obj.outer();
ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표 함수를 새로 도입했다. 화살표 함수는 실행 컨텍스트 생성 시, this 바인딩 과정 자체를 제외하여 상위 스코프의 this를 그대로 활용할 수 있도록 하였다.
1) call 메서드
call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령이다. (호출 주체는 해당 메서드를 부른, . 앞에 있는 대상이다)
첫 번째 인자를 this 로 바인딩하고, 이후의 인자들을 호출할 함수의 매개변수로 이용한다.
var func = function (a, b, c) {
console.log(this, a, b, c);
}
func(1, 2, 3); // 출력: Window {...} 1 2 3
func.call({ x: 1 }, 4, 5, 6); // 출력: { x: 1 } 4 5 6
call 메서드의 호출 주체인 func 를 즉시 호출하고, 첫번째 인자인 { x: 1 } 로 해당 함수의 this 에 바인딩하였으며, 이후 인자들을 매개변수로 이용하였다.
예제는 함수에서만 살펴보았지만 객체의 메서드에서도 동일하게 작동한다.
2) apply 메서드
apply 메서드는 call 메서드의 기능적 동작방식과 완전히 동일하다.
다만 다른 점은 첫번째 인자 이후 두번째 인자를 배열로 받아 배열의 요소들을 매개변수로 지정한다는 것이다.
call 메서드의 예시에서 함수로 사용했으니 이번엔 메서드로 호출해보겠다.
var obj = {
x: 1,
method: function(a, b, c) {
console.log(this, a, b, c)
}
}
obj.method(1, 2, 3) // 출력: obj { x: 1, method: f } 1 2 3
obj.method.apply({ y: 2 }, [4, 5, 6]); // 출력: { y: 2 } 4 5 6
3) bind 메서드
bind 메서드는 ES5에서 추가된 기능으로, call 메서드와 비슷하지만 즉시 호출하지는 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환해주는 메서드이다.
첫번째 인자로 this 바인딩을 하고 두번째부터 인자로 넘겨받은 값들은 순서대로 매개변수에 등록하게 된다.
bind 를 통해 생성된 함수에 인자를 넘기면 bind 메서드에서 넘겨받은 값들에 뒤이어 등록된다.
함수에는 name 이라는 식별자를 담는 프로퍼티를 가지고 있는데, bind 메서드를 통해 새로 만들어진 함수엔 이 name 프로퍼티의 식별자 앞에 bound 라는 접두어가 붙는다.
즉, xxx 라는 식별자를 가진 어떤 함수의 name 프로퍼티가 bound xxx 라면 이 함수는 bind 메서드를 통해 새로 생성된 함수라는 것을 알 수 있다.
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // 출력: Window {...} 1 2 3 4
console.log(func.name); // 출력: func
var bindFunc = func.bind({ x: 1 }, 4, 5);
bindFunc(6, 7); // 출력: { x: 1} 4 5 6 7
console.log(bindFunc.name); // 출력:bound func
생성자 함수 내부에서의 this
생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수이다.
생성자를 class, 클래스를 통해 만든 객체를 인스턴스라고 한다.
var Cat = function (name, age) {
this.bark = '야옹';
this.name = name;
this.age = age;
console.log(this);
};
var choco = new Cat('초코', 7); // 출력: Cat {bark: '야옹', name: '초코', age: 7}
var nabi = new Cat('나비', 5); // 출력: Cat {bark: '야옹', name: '나비', age: 5}
new명령어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작하게 된다. 그리고 어떤 함수가 생성자 함수로서 호출된 경우 내부에서의 this는 곧 새로 만들 구체적인 인스턴스 자신이 된다.