JavaScript this binding 정리

Jeongkuk Seo
sjk5766
Published in
9 min readDec 26, 2019

최근에 자바스크립트 객체의 메소드를 화살표 함수(arrow function)로 사용했다가 this가 제대로 바인딩 되지 않은 현상이 있었습니다. 이 포스팅으로 this를 제대로 이해해 보려 합니다.

목차

  • this 설명
  • this 바인딩 설명
  • arrow function과 this 바인딩 설명

자바스크립트에서 this란

자바스크립트는 스크립트 언어로, interpreter가 코드를 라인 단위로 읽고, 해석 후 실행시킵니다.

interpreter에 의해 현재 실행되는 자바스크립트 코드의 (환경 or scope)를 실행 컨텍스트(execution context) 라고 부릅니다. 자바스크립트 내부에서 이런 실행 컨텍스트를 stack으로 관리하고 있으며 실행되는 시점에 자주 변경되는 실행 컨텍스트를 this가 가리키고 있습니다.

요약하자면 this는 현재 실행되는 코드의 실행 컨텍스트를 가리킵니다.

this Binding 정리

1. default binding

기본적으로 this는 전역 객체를 가리키게 되는데, Node환경에서는 global객체를, Browser에서는 Window객체를 가리킵니다.

  • Browser 에서
브라우저 개발자 도구에서 — console 탭에서 확인
  • Node 에서

2. 함수 내부의 this binding

일반적인 함수 내부에서 this를 호출하면 전역 객체를 가리킵니다. 아래 코드는 browser 에서 실행시켰기 때문에 window 객체와 비교 했습니다.

function checkThisInNormalFunc() {
console.log(this === window) // true
}
checkThisInNormalFunc()
console.log(this === window) // true

만약 함수 내부 or 외부에서 strict 모드를 사용한다면 함수 내부에서 this는 전역객체를 binding 하지 않습니다.

function checkThisInNormalFunc() {
'use strict';
console.log(this === window) // false
}
checkThisInNormalFunc()
console.log(this === window) // true

함수 외부에서 strict 모드를 사용한다면 모든 함수 내부의 this는 전역 객체를 binding 하지 않습니다.

'use strict';
function checkThisInNormalFunc() {
console.log(this === window) // false
}
function checkThisInNormalFunc2() {
console.log(this === window) // false
}
checkThisInNormalFunc()
checkThisInNormalFunc2()
console.log(this === window) // true

3. 즉시 실행함수 내부의 this binding

(function(){
console.log(this === window); // true
})();
---------------------------------------'use strict';
(function(){
console.log(this === window); // false
})();

4. 객체의 메소드 this binding

객체 내부의 메소드에서 this를 binding 할 경우, 그 객체 자신을 가리키게 됩니다.

var obj = {
name: "seo",
print: function() {
console.log(this.name) // seo
console.log(this === obj) // true
}
}
obj.print()

아래 코드를 읽고 결과를 본다면 this가 참조되는 위치가 중요한게 아니라
어디서, 어떻게 this를 포함하는 코드를 호출하느냐가 중요합니다.

var obj1 = {
name: "seo",
print: function() {
console.log(this.name); // this가 참조되는 위치
}
}
var obj2 = { name: "jeong", print: obj1.print };
var name = "kuk";
var print = obj1.print;
print(); // kuk
obj1.print(); // jeong
obj2.print(); // seo

어떻게 호출하느냐가 왜 중요하냐? 코드를 보기 쉽게 아래와 같이 변경하면..

var obj = {
print: function() {
console.log(this == obj);
}
}
var print = obj.print

obj.print(); // true
print(); // false

첫 번째 obj.print() 의 경우 객체의 메소드 방식으로 호출하고 있습니다. 이 경우 this는 객체 자신이 됩니다.

두 번째 print()의 경우 일반적인 함수 호출 형태로 호출하고 있습니다. 이 경우 this는 전역객체를 가리키게 됩니다.

5. new 키워드와 this binding

아래 printName함수 안에 var로 선언한 lastName과 this로 접근한 firstName이 있습니다. 함수 내부에서 this는 전역 객체를 가리키므로 firstName 은 전역 객체에 들어가게 됩니다. 이를 생각하면 printName() 의 결과는 이해가 갈 겁니다.

대신 new로 선언할 경우 this는 전역 객체가 아닌 생성된 객체 자체를 가리키게 됩니다. 따라서 this로 접근한 firstName은 정상적인 값을 가져오지만 더 이상 전역 객체가 아니기에 lastNameundefined로 나옵니다.

function printName() {
var lastName = "seo";
this.firstName = "jeong kuk"
console.log(this.lastName + " " + this.firstName)
}
var lastName = "kim";
printName(); // kim jeong kuk
var o = new printName(); // undefined jeong kuk

6. Call & Apply 메소드와 this binding

callapply 메소드는 첫 번째 인자로 실행 컨테스트를 인자로 전달합니다.

printName.call(obj) 에서 obj를 전달할 때, this가 obj의 실행 컨텍스트를 가리키게 되므로 결과는 아래와 같습니다.

function printName() {    
console.log(this.name)
}
var name = "seo"
printName(); // seo
var obj = {name: "jeong kuk"}
printName.call(obj); // jeong kuk

3. arrow function과 this binding

ES 6에서 나온 arrow function의 가장 큰 특징은 this가 binding 하는 대상이 달라지는 것입니다.

아래 코드는 thisprint 메소드 내부에서, print 메소드 내부의 forEach메소드 내부에서 접근하고 있습니다. print 메소드 내부에서는 위에서 설명한 대로 정상적으로 this를 바인딩 하고 있지만 메소드 내부의 forEach 메소드에서는 다시 this가 전역객체를 binding 하고 있기 때문에 결과가 seoundefined가 나옵니다.

var obj = {
names: ["seo"],
text: "님 안녕하세요",
print: function() {
console.log(this.text) //님 안녕하세요
this.names.forEach(function(name) {
console.log(name + this.text) // seoundefined
})
}
}
obj.print()

아래 코드가 위와 다른점은 forEach 메소드를 일반 함수가 아닌 arrorw 함수로 변경한 것입니다.

var obj = {
names: ["seo"],
text: "님 안녕하세요",
print: function() {
console.log(this.text)
this.names.forEach(name => {
console.log(name + this.text) // seo님 안녕하세요
})
}
}
obj.print()

결과가 다른 이유는 arrow function 내부에서 this는 전역 객체가 아닌 부모 객체인 obj를 가리키기 때문입니다. 이를 lexical this 라고도 표현하는데 이에 대한 설명은 주제와 맞지 않기 때문에 생략합니다.

화살표 함수는 짧은 타이핑lexical this를 제공하기 때문에 많이 사용되지만 사용해서 안되는 경우도 존재합니다. 제가 이걸 몰라서 그 계기로 이 포스팅을 작성하게 되었습니다.

아래 코드에서 obj 객체의 print 메소드를 화살표 함수로 선언했습니다. 이 처럼 객체의 메소드를 화살표 함수로 선언할 경우 this는 전역 객체를 가리키므로 주의해야 합니다.

var obj = {
names: ["seo"],
text: "님 안녕하세요",
print: () => { console.log(this.text) } // undefined
}
obj.print()

이 외에도 화살표 함수는 prototype 속성이 없으며 new 연산자로 생성 될 수 없다는 특징이 있습니다.

var Name = () => {}
console.log(Name.prototype)// undefined
var name = new Name() // TypeError: Name is not a constructor

--

--