this는 무엇인가?

this는 자바스크립트 언어에서 매우 추상적인 부분 중에 하나이다. 그래도 한마디로 단정짓는다면, this는 자기 자신을 나타내기 위한 키워드이다.

좀 더 자세히 말하면 자신이 속한 객체나 자신이 생성할 객체를 가리키는 자기 참조 변수이다.

  • this는 자바스크립트 엔진에 의해 암묵적으로 생성된다.

함수를 호출하면 arguments 객체와 this가 암묵적으로 함수 내부에 전달된다. arguments 객체를 함수 내부에서 지역변수처럼 사용할 수 있듯이 this도 마찬가지이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// this로 자신이 속한 객체 참조
const test = {
value: 10,
getValue() {
return this.value;
},
};

console.log(test.getValue()); // 10

// this로 자신이 생성할 객체 참조
function Circle(radius) {
this.radius = radius;
}

Circle.prototype.getDiameter = function () {
return 2 * this.radius;
};

const circle = new Circle(5);
console.log(circle.getDiameter()); // 10

this에 바인딩될 값은 함수 호출 방식에 의해 동적으로 결정되는 특징이 있다.

그러므로 this를 어떻게 호출하느냐에 따라 this 값이 달라지게 된다.

this의 작동원리 in JavaScript

1. 일반 함수로 호출

일반 함수로 호출할 경우 해당 함수 내부의 this는 전역 객체에 바인딩된다.

이 때, 중첩함수, 콜백함수가 일반함수로 호출되는 경우에도 마찬가지이다.

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
const obj = {
value: 100,
foo() {
console.log("foo's this: ", this); // {value: 100, foo: ƒ}
console.log("foo's this.value: ", this.value); // 100

// 메서드 내에서 정의한 중첩 함수
function bar() {
console.log("bar's this: ", this); // window
console.log("bar's this.value: ", this.value); // 1
}

// 메서드 내에서 정의한 중첩 함수도 일반 함수로 호출되면 중첩 함수 내부의 this에는 전역 객체가 바인딩된다.
bar();
},
// 콜백함수가 일반함수로 호출되는 경우
boo() {
console.log("foo's this: ", this); // {value: 100, foo: ƒ}
// 콜백 함수 내부의 this에는 전역 객체가 바인딩된다.
setTimeout(function () {
console.log("callback's this: ", this); // window
console.log("callback's this.value: ", this.value); // 1
}, 100);
},
};

2. new 키워드 사용하여 생성자 함수로 호출

new 키워드로 함수를 호출할 경우 자바스크립트는 해당 함수를 생성자 함수로 판단하고 암시적으로 다음 행동을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Circle(radius) {
// 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩된다.

// 2. this에 바인딩되어 있는 인스턴스를 초기화
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};

// 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
}

// 인스턴스 생성. Circle 생성자 함수는 암묵적으로 this를 반환한다.
const circle = new Circle(1);
  1. 새로운 객체를 생성하고 this를 바인딩한다.
  2. this에 바인딩 되어 있는 객체를 초기화한다.
  3. 해당 함수는 return 하는 값이 없다면 this가 암묵적으로 반환된다.

생성자 함수는 this가 바인딩된 객체를 암묵적으로 반환하기 때문에, 생성자 함수로 호출 시 생성자 함수 내부의 this는 생성자 함수가 미래에 생성할 객체에 바인딩된다.

new 키워드를 사용한 함수 내부에서 return을 명시적으로 해준다는 것은 this를 암묵적으로 반환하는 생성자 함수 행동을 훼손하므로 이는 안티 패턴이다.

3. 메서드로 호출

메서드란, 객체의 프로퍼티 값이 함수인 경우를 해당 함수를 메서드라고 부른다.

메서드로 호출될 경우 메서드 내부의 this는 해당 메서드가 속한 객체가 아닌 메서드를 호출한 객체에 바인딩된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const person = {
name: "Lee",
getName() {
// 메서드 내부의 this는 메서드를 호출한 객체에 바인딩된다.
return this.name;
},
};
const people = {
name: "Kim",
getName() {
return this.name;
},
};

console.log(person.getName()); // Lee
console.log(people.getName()); // Kim

getName()이라는 메서드가 person 객체 내부에 있으니 메서드로 호출 시 this 바인딩이 메서드를 포함하는 객체인 person에 될 것이라 생각하는 것은 잘못된 생각이다.

1
2
3
4
5
6
7
8
const anotherPerson = {
name: "Kim",
};
// getName 메서드를 anotherPerson 객체의 메서드로 할당
anotherPerson.getName = person.getName;

// getName 메서드를 호출한 객체는 anotherPerson이다.
console.log(anotherPerson.getName()); // Kim
  • 위와 같이 getName 메서드를 anotherPerson 객체의 메서드로 할당한 경우, getName 메서드 내부의 this는 자신을 호출한 객체에 바인딩 되기 때문에 anotherPerson.name을 참조하게 된다.

4. apply,call,bind 함수에 의한 간접 호출

이 경우 함수 내부의 this는 인수로 전달된 객체에 바인딩된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getThisBinding() {
return this;
}

// this로 사용할 객체
const thisArg = { a: 1 };

console.log(getThisBinding()); // window

// getThisBinding 함수를 호출하면서 인수로 전달한 객체를 getThisBinding 함수의 this에 바인딩한다.
console.log(getThisBinding.apply(thisArg)); // {a: 1}
console.log(getThisBinding.call(thisArg)); // {a: 1}

// bind 메서드는 첫 번째 인수로 전달한 thisArg로 this 바인딩이 교체된
// getThisBinding 함수를 새롭게 생성해 반환한다.
console.log(getThisBinding.bind(thisArg)); // getThisBinding
// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 한다.
console.log(getThisBinding.bind(thisArg)()); // {a: 1}
  • call, apply의 반환값은 호출한 함수의 반환값이다.
  • bind는 함수를 호출하지 않고 인수로 전달받은 객체로 this 바인딩이 교체된 함수를 새롭게 생성하여 반환한다.

과거에는 this를 불일치 문제를 bind 함수를 사용하여 해결하였다.

1
2
3
4
5
6
7
8
9
10
const person = {
name: "Lee",
foo(callback) {
setTimeout(callback, 100);
},
};

person.foo(function () {
console.log(`Hi! my name is ${this.name}.`); // ② Hi! my name is .
});
  • 일반 함수로 호출된 콜백 함수 내부의 this.name은 브라우저 환경에서 window.name과 같다.
  • 브라우저 환경에서 window.name은 브라우저 창의 이름을 나타내는 빌트인 프로퍼티이며 기본값은 ‘’이다.
1
2
3
4
5
6
7
8
9
10
const person = {
name: "Lee",
foo(callback) {
setTimeout(callback.bind(this), 100);
},
};

person.foo(function () {
console.log(`Hi! my name is ${this.name}.`); // ② Hi! my name is Lee.
});

5. 화살표 함수로 호출

화살표 함수로 호출할 경우 위의 규칙을 모두 무시하고 화살표 함수가 생성된 시점에서 상위 스코프의 this에 바인딩된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const arrow = () => {
return this; // Window
};

const obj = {
far() {
return arrow();
},
};

const anotherObj = {
baz() {
return (() => this)();
},
};

obj.far(); // Window
anotherObj.baz(); // anotherObj

this 화살표함수 예시

  • arrow 함수는 생성된 시점에서 상위 스코프의 this는 전역객체(Window)이다.
  • anotherObj 객체의 baz 메서드 내부의 this는 자신을 호출한 객체에 바인딩된다. 그러므로 화살표 함수 내부의 this는 자신이 생성된 시점에서 상위 스코프인 baz의 this와 일치한다.