2015년 4월 2일 목요일

[JavaScript] 자바스크립트 this 키워드의 모든것

this 키워드를 이해하기 위해서는 Execution Context를 이해해야하니, 혹시 Execution Context를 모른다면 이 글(클릭하기)을 읽어 봐 주길 바란다.

자바스크립트에서는 매번 함수의 호출마다 새로운 Execution Context가 생성된다. 이 Execution Context는 Lexical Environment, Variable Environment, 그리고 ThisBinding에 대한 정보를 가지고 있는데, 바로 this 키워드가 이 이 thisbinding이라고 할 수 있겠다. this값은 해당 함수가 현재 어느 Execution Context에서 구동하고 있는지를 알려준다.

Context는 함수 호출에 의해 새로이 생성되며, thisBinding 값은 함수 호출시점에 정해지게 된다. 정확히 말하자면 ECMAScript 내부에서 Function call 발생할 때, 함수의 Arguments List와 thisArg라 불리는 두 객체를 전달하게 되는데, ECMAScript® Language Specification 10.4.3에 따르면 여기서 아래의 과정을 거친후 thisBinding의 값이 결정 된다.
  1. 만약 함수가 strict mode에서 정의된 함수인지를 확인한 후, thisBinding 값은 전달된 thisArg값이 된다.
  2. (strict mode에서 정의된 함수가 아니고) thisArg 값이 null 이나 undefined라면면,  thisBinding 값은 global object가 된다.
  3. 전달된 Type(thisArg) 연산으로 얻어진 thisArg 값의 Typeobject(객체)가 아니라면, thisBinding 값은 toObject(thisArg) 오퍼레이션을 통해 객체화된 객체가 된다.
    • Type() 함수는 ECMA스크립트 내부 함수로, 주어진 변수의 Type을 연산합니다.
    • toObjecT() 함수는 주어진 변수를 Boolean, Number, String, Object 형으로 변환 합니다
  4. 위 모든 과정에서 thisBinding 값이 정해지지 않았다면, 전달된 thisArgthisBinding이 된다.

참고로 funciton.prototype.call, function.prototype.apply 와 같은 this 값을 수정 가능케 하는 기능들은 모두 thisArg 값을 사용자가 전달할 수 있게 함으로써 가능한 기능들이다.

그렇다면, 일반적으로는 이 thisArg 값이 어떻게 결정 되는 것 일까? 이는 함수 호출(Function Call) 과정을 설명한 ECMAScript® Language Specification 11.2.3 에 잘 정의 되어 있다 - 6번과 7번에 주목하자.

*우선 이전에 몇가지 알아야 될것들이 있다

  • 함수가 저장된 주소인 reference 값은 base value, referenced name, strict reference flag, 이 3가지 요소로 이루어져 있다
    • referenced namestring 값으로 저장된 function identifier이다
    • strict reference flagboolean 값으로, strict mode 에서 실행되는 코드인지를 표시한다
    • base valuereference가 속한 context object를 가리킨다

thisArg값은 함수 호출(Function Call)의 6번째 과정에서에서 결정되며, 함수 호출의 전체 과정은 아래와 같다.

  1. 함수 객체의 이름(MemberExpression)을 해석해서 함수가 저장된 레퍼런스 값인 ref를 얻는다.
  2. GetValue(ref) 오퍼레이션을 이용, 함수 func을 불러온다.
  3. 주어진 arguments 들로 argList를 생성한다.
  4. Type(func) 오퍼레이션을 이용, 함수 func의 타입을 확인한후, 객체타입이 아니라면 TypeError Exception 을 발생시킨다.
  5. IsCallable(func) 오퍼레이션을이용, 함수 func의 호출이 가능한지를 확인한 후, 불가능하다면 TypeError Exception을 발생시킨다.
  6. Type(ref)를 이용, ref의 타입을 확인한 후, reference 타입이라면
    1. IsPropertyReference(ref) 오퍼레이션을 이용, ref가 object 또는 primitive wrapper object(String, Boolean, Number 등의 객체)에 속한 property 라면 thisArg값은 getBase(ref) 오퍼레이션에서 얻어진 해당 객체 인스턴스가 된다.
    2. 그 이외의 경우에는 ref가 속한 Environment Record(Variable Environment Object)라면 getBase(ref)에서 얻어진 값의 ImplicitThisValue()가 바로 thisArg가 된다.
  7. 만약 Type(ref)를 이용해 확인한 ref의 타입이 reference 가 아니라면, thisArg값은 undefined가 된다.
  8. Call 오퍼레이션을 이용, thisArg 값과 argList를 전달해 함수를 호출한 후 그 결과값을 리턴한다.

결국 각각의 경우에 따라 thisBinding의 값이 달라지니 좀 헷깔린다. 그래서 자주 보게되는 상황들을 Case By Case 로 설명을 해보려 한다.

* 참고로 this 키워드는 객체이기 대문에 '.'(Dot Notation) 또는 '[', ']'(Bracket Notation) 를 이용해서도 this 객체의 변수나 함수를 호출 할 수 있다.
* 위 과정을 읽었으면 알겠지만 strict 모드에서는 함수 호출시 thisArg값은 non-strict mode 일때와는 다르다.


1. Declaration environment record에 존재하는 함수 호출(Function Call)


  • 위의 6-2번 과정이다. ImplicitThisValue() 는 모든 declaration에 대해 undefined를 리턴한다
  • 이 때문에 this 키워드는 strict mode에서는 undefined 값을, non-strict mode에서는 global object(window object)값을 참조하게 된다.


function invokeFunction() {
    console.log("invokeFunction");
    console.log(this);//Window
    demo1();
    function demo1() {
 console.log("demo1");
 console.log(this);//Window

 var demo2 = function() {
     console.log("demo2");
     console.log(this);//Window

     var demo3 = function() {
  console.log("demo3");
  console.log(this);//Window
     };
     demo3();
 };
 demo2();
    }
}
console.log(this);//Window
invokeFunction();


2. new 키워드를 이용해 객체(object)로 인스턴스화 될 때, this 값은 언제나 인스턴스화된 객체를 참조한다.

  • 위 과정의 6-1번에 속하는 경우이다. 호출된 함수가 객체의 property인 경우이다


function thisObject(name, property) {
    this.name = name;
    this.property = property

    this.thisValue = this;

    this.f = function() {
 console.log("this in function f \n" + this); //object
    }
}

var demo = new thisObject("testObject", 0);
console.log("demo.thisvalue is " + demo.thisValue); //object demo


3. 객체의 메소드로써 호출될 때, this 값은 언제나 인스턴스화된 객체를 참조한다.


  • 메소드란 객체의 프로퍼티(property)로 존재하는 함수를 한다.
  • 이 경우 또한 위 과정의 6-1번의 경우가 되겠다.


var a = {
    test : function() {
 return this;
    }
};

a.test();// object 'a'

var b = {};
b.test = a.test;

b.test()// object 'b'


4. function.prototype.call 또는 function.prototype.apply 메소드를 이용해 호출될 때


  • thisArg의 값은 사용자에 의해 주어진다.


function test() {
    return this;
}
test();// windows

var a = {};
test.apply(a);// object 'a'

test.call(a);// object 'a'

댓글 1개:

  1. 몇 번이나 돌려보고 있습니다.
    정말 유익한 게시글을 올려주셔서 너무나 감사드립니다.

    답글삭제