🍑 자바스크립트의 this
자바스크립트의 this
는 다른 언어와는 달라 헷갈리는 개념입니다.
java에서의 this는 인스턴스가 자기 자신을 가리키는 참조변수입니다.
하지만 자바스크립트의 경우 this
에 바인딩 되는 객체는 함수의 호출 방식에 따라 달라지게 됩니다.
🍑 함수호출 방식에 따른 this 바인딩
자바스크립트의 this는 함수가 어떻게 호출되는지에 따라 바인딩 할 객체가 동적으로 결정됩니다. 아래는 자바스크립트의 함수 호출방식입니다.
- 함수 호출
1 2 3 4
function foo(){ console.log(this); } foo(); // window
- 메소드 호출
1 2
const obj = {foo:foo}; obj.foo(); // obj
- 생성자 함수 호출
1
const instance = new foo(); // instance;
- apply,call,bind 호출
1 2 3 4
const bar = {hi:'hi'}; foo.call(bar); // bar foo.apply(bar); // bar foo.bind(bar)(); // bar
이제 이 4가지 함수 호출 방식에 따라 this
가 어떻게 바인딩 되는지 자세히 살펴보겠습니다.
🍒 1. 함수 호출
전역 객체는 모든 객체의 최상위 계층을 의미합니다. 브라우저에서는 window
, node에서는 global
이 전역 객체입니다.
만일 함수나 변수를 전역에 선언하게 되면 그것들은 전역 객체의 프로퍼티가 됩니다.
1
2
3
4
5
6
7
8
const hello = 'hello';
function foo(){
console.log('hi');
}
console.log(window.hello) // hello
window.foo() // hi
✅ 기본적으로 전역 함수의 this
는 이 전역객체에 바인딩이 됩니다. 그리고 내부함수의 this
도 마찬가지로 전역객체에 바인딩 됩니다.
1
2
3
4
5
6
7
8
function foo(){
console.log(this); // window
function bar(){
console.log(this); // window
}
bar();
}
foo();
✅ 내부함수는 일반 함수, 메소드, 콜백함수 어디에서 선언됐는지에 상관없이 항상 전역객체에 this
가 바인딩 되게 됩니다.
🍒 2. 메소드 호출
함수가 객체의 프로퍼티가 되면 이것을 메소드라고 부릅니다. 이때 메소드 내부의 this
는 해당 메소드를 소유한 객체, 즉 메소드를 호출한 객체와 바인딩 됩니다.
1
2
3
4
5
6
7
8
const obj = {
name: 'dongjune',
hi:function(){
console.log('hi' + this.name);
}
}
obj.hi(); // hi dongjune
프로토타입 객체도 메소드를 가질 수 있습니다. 프로토타입에서도 마찬가지로 this
는 그 메소드를 호출한 객체에 바인딩 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
const me = new Person('Lee');
console.log(me.getName()); // Lee
Person.prototype.name = 'Kim';
console.log(Person.prototype.getName()); // Kim
🍒 3. 생성자 함수 호출
생성자 함수를 통해 객체를 생성할 수 있습니다. 다른 언어와는 다르게 기존 함수에 new
키워드를 붙이면 해당 함수가 생성자 함수로 동작하게 됩니다.
1
2
3
4
5
6
7
8
9
10
11
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
console.log(me); // {name: "Lee"}
// new 연산자와 함께 생성자 함수를 호출하지 않으면 생성자 함수로 동작하지 않는다.
const you = Person('Kim');
console.log(you); // undefined
new
연산자로 생성자 함수를 호출하게 되면 앞서 살펴본 메소드나 함수 호출때와는 this
바인딩이 다르게 동작합니다.
🌱 3.1. 생성자 함수 동작 방식
new 연산자로 생성자 함수를 호출하면 다음의 과정을 거치게 됩니다.
- 빈 객체 생성 및
this
바인딩- 생성자 함수의 코드 실행 전에 빈 객체가 생성된다.
- 생성자 함수 내에서 사용하는
this
는 이 빈 객체를 가리킴 - 그렇게 생성 된 빈 객체는 생성자 함수의
prototype
프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정한다.
this
를 통한 프로퍼티 생성- 생성된 빈 객체에
this
를 통해 동적으로 프로퍼티나 메소드를 할당한다.
- 생성된 빈 객체에
- 생성된 객체 반환
- 반환문이 없는 경우
this
에 바인딩 된 새로 생성한 객체를 반환한다. 명시적으로this
를 반환하여도 같은 결과이다.
- 반환문이 없는 경우
1
2
3
4
5
6
7
8
function Person(name) {
// 생성자 함수 코드 실행 전 -------- 1
this.name = name; // --------- 2
// 생성된 함수 반환 -------------- 3
}
const me = new Person('Lee');
console.log(me.name);
🌱 3.2. 생성자 함수에 new 연산자를 붙이지 않는 경우
생성자 함수를 new
없이 호출한 경우 함수 내부의 this
는 전역 객체에 바인딩 됩니다. 또한 암묵적으로 생성한 빅 객체를 반환해주지 않기 때문에 아래의 예제에서 me
는 undefined
가 됩니다.
1
2
3
4
5
6
7
8
9
10
11
function Person(name) {
// new없이 호출하는 경우, 전역객체에 name 프로퍼티를 추가
this.name = name;
};
// 일반 함수로서 호출되었기 때문에 객체를 암묵적으로 생성하여 반환하지 않는다.
// 일반 함수의 this는 전역객체를 가리킨다.
var me = Person('Kim');
console.log(me); // undefined
console.log(window.name); // Kim
위와 같이 생성자 함수에 new
를 붙이지 않아 발생하는 오류들이 있습니다. 보통 생성자 함수는 첫문자를 대문자로 하여 일반 함수와 구분하고 있지만 그럼에도 실수를 방지하기는 어렵습니다.
그래서 이러한 위험성을 방지하기 위한 Scope-Safe Constructor
패턴이 있습니다. 이 패턴은 대부분의 라이브러리에서 광범위하게 사용된다고 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Scope-Safe Constructor Pattern
function A(arg) {
// 생성자 함수가 new 연산자와 함께 호출되면 함수의 선두에서 빈객체를 생성하고 this에 바인딩한다.
/*
this가 호출된 함수(arguments.callee, 본 예제의 경우 A)의 인스턴스가 아니면
new 연산자를 사용하지 않은 것이므로 이 경우 new와 함께 생성자 함수를 호출하여
인스턴스를 반환한다.
arguments.callee는 호출된 함수의 이름을 나타낸다. 이 예제의 경우 A로 표기하여도
문제없이 동작하지만 특정함수의 이름과 의존성을 없애기 위해서 arguments.callee를
사용하는 것이 좋다.
*/
if (!(this instanceof arguments.callee)) {
return new arguments.callee(arg);
}
// 프로퍼티 생성과 값의 할당
this.value = arg ? arg : 0;
}
var a = new A(100);
var b = A(10);
console.log(a.value); // 100
console.log(b.value); // 10
🍒 4. apply,call,bind 호출
this에 바인딩 될 객체는 자바스크립트 엔진이 암묵적으로 수행해줍니다.
하지만 apply, call, bind
메소드를 사용하면 명시적으로 this를 바인딩 할 수 있는습니다.
apply, call, bind는 Function.prototype의 메소드이다.
1
2
3
4
5
Person.apply(foo, [1, 2, 3]);
Person.call(foo, 1, 2, 3);
Person.bind(foo)(1,2,3);
이 메소드들은 콜백 함수의 this
를 위해서도 사용됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(name) {
this.name = name;
}
Person.prototype.doSomething = function(callback) {
if(typeof callback == 'function') {
// --------- 1
callback();
}
};
function foo() {
console.log(this.name); // --------- 2
}
var p = new Person('Lee');
p.doSomething(foo); // undefined
1 시점에서 this
는 Person
객체입니다. 그러나 2 시점에서 this
는 전역 객체입니다. 다음과 같이 call, apply, bind
메소드를 사용하여 this를 일치시킬 수 있습니다.
call
1
2
3
4
5
6
Person.prototype.doSomething = function(callback) {
if(typeof callback == 'function') {
// --------- 1
callback.call(this);
}
};
apply
1
2
3
4
5
6
Person.prototype.doSomething = function(callback) {
if(typeof callback == 'function') {
// --------- 1
callback.apply(this);
}
};
bind
bind
는 this
가 바인딩 된 새로운 함수를 리턴합니다.
1
2
3
4
5
6
Person.prototype.doSomething = function(callback) {
if(typeof callback == 'function') {
// --------- 1
callback.bind(this)();
}
};