JavaScript의 this

조영도(Young-do Cho)·2020년 2월 17일
2

JavaScript에서 this는 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정됩니다. 그리고 함수가 호출되는 방식은 4가지로 나눌 수 있습니다.

설명하기 전에

바인딩(Binding)

각종 값들이 확정되어 더 이상 변경할 수 없는 구속(Bind) 상태가 되는 것.

전역 객체(Global Object)

전역 객체는 모든 객체의 유일한 최상위 객체를 의미하며, 브라우저 환경에서는 window, Node.js 환경에서는 global객체를 의미합니다. 이는 전역 객체 호출 시 환경에 맞춰 호출해야 한다는 것을 의미하기도 합니다. 더 자세한 내용은 이 링크에서 확인해주세요.

// 먼저, 모두 비엄격모드(=non-strict mode) 가정

// 브라우저 환경
this === window // true
this === global // ReferenceError: global is not defined

// Node.js 환경
node
this === global // true
this === window // ReferenceError: window is not defined

엄격모드(Strict Mode)에서 this

C The Strict Mode of ECMAScript

If this is evaluated within strict mode code , then the this value is not coerced to an object. A this value of undefined or null is not converted to the global object and primitive values are not converted to wrapper objects. The this value passed via a function call (including calls made using Function.prototype.apply and Function.prototype.call) do not coerce the passed this value to an object ( 9.2.1.2 , 19.2.3.1 , 19.2.3.3 ).

http://www.ecma-international.org/ecma-262/#sec-strict-mode-of-ecmascript

위는 ECMAScript에 명시된 엄격 모드에서의 this에 관한 설명입니다. 여기서 몇 가지를 알 수 있습니다.

  1. this값이 undefined 혹은 null이라면, 비엄격모드일 경우 this에 전역 객체를 바인딩합니다. 하지만, 엄격 모드라면 바인딩하지 않습니다.
  2. this값이 원시값(primitive value)이라면, 비엄격모드일 경우 autoboxing이 수행됩니다. 하지만, 엄격 모드라면 수행되지 않습니다(=원시 값 그대로를 가집니다). this값이 Function.prototype.call 혹은 Function.prototype.apply에 의해 전달된 경우 또한 동일합니다.

위에 대한 예시입니다. 즉, 본래 주어진 this의 값을 그대로 유지하기 위해 엄격모드를 사용합니다.

// 브라우저 환경, 비엄격모드(=non-strict mode) 가정
function nonStrictFunc() { 
    return this; 
}
function strictFunc() {
    'use strict'; // 함수 단위의 엄격모드 설정
    return this; 
}

// this 값이 undefined인 경우
console.log(nonStrictFunc() === window); // true
console.log(strictFunc() === window); // false
console.log(strictFunc()); // undefined

// this 값이 원시값인 경우
console.log(nonStrictFunc.bind('123')() === '123'); // false
console.log(strictFunc.bind('123')() === '123'); // true

// Function.prototype.call인 경우
console.log(nonStrictFunc.call('123') === '123'); // false
console.log(strictFunc.call('123') === '123'); // true

this를 결정하는 4가지 상황

다시 돌아왔습니다. 이제 this가 결정되는 4가지 상황에 대해 설명하겠습니다.

1. 함수 호출

기본적으로 일반적인 함수 호출 시, this는 전역 객체(Global Object)에 바인딩됩니다. 이는 나머지 함수 호출 방식들을 제외하면 this는 전역객체를 나타낸다는 것을 의미합니다. 예를 들어, (1) 전역 함수, (2) 내부 함수, (3) 객체 메서드의 내부 함수, (4) 콜백 함수 역시 전역 객체입니다.

// 브라우저 환경, 비엄격모드(=non-strict mode) 가정

function foo() { // 👈 (1) 전역함수
    console.log("foo’s this:", this); // window
    bar();
    function bar() { // 👈 (2) 내부함수
        console.log("bar’s this:", this); // window
    }
}
foo();

var obj = {
    foo: function() { 
        console.log("foo’s this:", this); // obj
        bar();
        function bar() { // 👈 (3) 객체 메소드의 내부함수
            console.log("bar’s this:", this); // window
        }
    }
};
obj.foo();

setTimeout(function() { // 👈 (4) 콜백함수
    console.log("callback’s this:", this); // window
}, 1000);

2. 메서드 호출

JavaScript methods are actions that can be performed on objects.
A JavaScript method is a property containing a function definition.

JavaScript Methods

객체에서 메서드는 함수 정의를 포함한 속성을 뜻합니다. 즉, 객체의 속성 중 함수인 것이 메서드입니다. this는 메서드에서 호출될 때, 해당 메서드를 호출한 객체에 바인딩됩니다. 이 규칙은 프로토타입 객체(Prototype Object)에도 동일하게 적용됩니다.

// 브라우저 환경, 비엄격모드(=non-strict mode) 가정
var obj1 = {
  name: 'Lee',
  sayName: function() {
    console.log(this.name);
  }
};

var obj2 = {
  name: 'Kim'
};

obj2.sayName = obj1.sayName; // 객체 obj2에 새로운 속성 추가

obj1.sayName(); // Lee
obj2.sayName(); // Kim

var sayName = obj1.sayName;
sayName(); // undefined (=window.name)

3. 생성자 함수 호출

생성자(constructor) 함수는 객체 생성하는 역할을 합니다. 하지만, 자바스크립트에서 생성자 함수는 별도로 형식이 정해져 있지 않습니다. 왜냐하면 모든 함수는 선언 시 생성자 함수 자격을 가지며, new 연산자를 붙여서 호출하면 해당 함수가 생성자 함수로 동작하기 때문입니다. 그러나 구분의 편의성을 위해 생성자 함수는 관습적으로 첫 문자를 대문자로 명시합니다.

function Person(name) {
    this.name = name;
}
var someone = new Person('Lee');
console.log(someone.name) // Lee

이때 생성자 함수에서 thisnew 연산자에 의해 새롭게 만들어진 객체에 바인딩됩니다. 즉, this는 새로 생성된 객체를 나타냅니다.

4. apply | call | bind 호출

this에 바인딩될 객체는 함수 호출 방식에 따라 결정되지만, 사용자가 명시적으로 this에 바인딩될 객체를 지정할 수 있는 방법이 있습니다. 바로 Function.prototype.apply, Function.prototype.call, Function.prototype.bind를 사용하는 것입니다. 각 메서드의 활용 예제는 아래와 같습니다.

// Function.apply();
var numbers = [5, 6, 2, 3, 7];
var max = Math.max.apply(null, numbers);
console.log(max); // 7


// Function.call()
function Product(name, price) {
  this.name = name;
  this.price = price;
}
function Food(name, price) {
  Product.call(this, name, price);
  this.category = 'food';
}
console.log(new Food('cheese', 5).name); // "cheese"


// Function.bind()
var module = {
  x: 42,
  getX: function() {
    return this.x;
  }
}
var unboundGetX = module.getX;
var boundGetX = unboundGetX.bind(module);
console.log(unboundGetX()); // undefined
console.log(boundGetX()); // 42

결론

  1. 엄격 모드에서 this는 본래 값을 유지하나, 비 엄격 모드에선 값에 따라 전환됩니다.
  2. 객체 메서드에서 this는 해당 메서드를 소유하고 있는 객체를 나타냅니다.
  3. 생성자 함수에서 thisnew 연산자를 통해 새로 생성될 객체를 나타냅니다.
  4. this에 바인딩할 값을 변경할 수도 있습니다.
  5. 엄격 모드가 아니며 2~4를 제외한 경우의 this는 전역 객체를 나타냅니다.

하지만 아직 스스로 생각하기에도 글이 정리가 되지 않은 느낌이 듭니다. 문제가 되는 부분은 언제든 지적 부탁드립니다.

참고

profile
개념 정리 + 호기심 해결용 블로그

0개의 댓글