인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



This 바인딩


자바스크립트에서 함수를 호출할 때 기존 매개변수로 전달되는 인자값에 더해, arguments 객체 및 this 인자가 함수 내부로 암묵적으로 전달됩니다. 여기서 this는 함수가 호출되는 방식(호출 패턴)에 따라 다른 객체를 참조하게(this 바인딩) 됩니다.

지금부터 여러가지 호출 패턴에 따른 this 바인딩에 대하여 나열해 보겠습니다.



1.객체의 메서드 호출할 때 This 바인딩


객체의 메서드를 호출할 때에는 해당 메서드를 호출한 객체로 바인딩 됩니다. 


var person ={
    name : 'song',
    sayName : function(){
        console.log(this.name);
    }
}


위와 같은예제에서 person.sayName();을 호출하면 결과는 'song'이 로그에 찍히게 됩니다. 즉, 자신을 호출한 객체가 this에 바인딩 되었습니다.




2.함수를 호출할 때 this 바인딩


자바스크립트에서 함수를 호출하면, 해당 함수 내부 코드에서 사용된 this는 전역 객체에 바인딩됩니다.(window)

*브라우저에서 전역객체란 window 객체, node.js에서는 global 객체.


var name = 'sooooong';

var sayName =function(){
  console.log(this.name);
}


위와 같은예제에서 sayName();을 호출하면 결과는 'sooooong'이 로그에 찍히게 됩니다. 즉 window.name이 this.name이 되는겁니다.


이러한 함수 호출에서의 this 바인딩 특성은 내부 함수를 호출했을 때에도 동일합니다. 때문에 내부 함수에서 this를 사용할 때 주의해야합니다.

var age = 40;
var person ={
    name : 'song',
    age : 29,
    sayName : function(){
        console.log(this.name);
    },
    sayAge : function(){
     console.log(this.age);

     sayAge2:fucntion(){
      console.log(this.age + 1);
     }
      sayAge2();
    }
}

위와 같은예제에서 person.sayAge();을 호출하면 결과는

29

30

이 아닌

29

41

이 나오게 됩니다. 이유는 sayAge의 내부함수인 sayAge2에서의 this는 window객체이기 때문입니다. 때문에 window.age인 40이 참조되게 되는 것입니다.


이렇게 되는 이유는 자바스크립트에서는 내부 함수 호출 패턴을 정의해 놓지 않기 때문입니다. 내부 함수도 결국 함수이므로 함수 호출 패턴 규칙에 따라서 window에 this가 binding됩니다.


때문에 이러한 자바스크립트의 한계를 극복하려면 부모함수의 this를 내부 함수가 접근 가능한 다른 변수에 저장하는 방법을 사용해야합니다.



var age = 40;
var person ={
    var that = this;
    name : 'song',
    age : 29,
    sayName : function(){
        console.log(this.name);
    },
    sayAge : function(){
     console.log(that.age);

     sayAge2:fucntion(){
      console.log(this.age + 1);
     }
      sayAge2();
    }
}

위와 같이 this를 that에 저장하면 원래 의도했던 29, 30이라는 결과를 얻을수있습니다.

자바스크립트에서는 이러한 this 바인딩의 한계를 극복하기위해 this 바인딩을 명시적으로 할 수 있는 call과 apply라는 메서드를 제공합니다.



3. 생성자 함수를 호출할 때 this 바인딩


자바스크립트의 생성자 함수는 말 그대로 자바스크립트의 객체를 생성합니다. 하지만 c++과 같이 그 형식이 정해지지는 않았고, 기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작합니다. 다만 특정 함수가 생성자 함수로 정의되어 있음을 알리려고 첫 글자를 대문자로 하는 것을 권하고 있습니다.


new 연산자로 자바스크립트 함수를 생성자로 호출하면, 다음과 같은 순서로 동작합니다. 


1) 빈 객체 생성 및 this 바인딩

먼저 빈 객체가 생성되고, 이 객체가 생성자 함수가 새로 생성하는 객체입니다. 이 객체는 this로 바인딩됩니다. 따라서 이후 생성자 함수의 코드 내부에서 사용된 this는 이 빈 객체를 가리킵니다.


엄밀히 말해서 빈 객체는 아니고 자신을 생성한 생성자 함수의 prototoype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정합니다.


2) this를 통한 프로퍼티 설정

이후에는 함수 코드 내부에서 this를 사용해서, 앞에서 생성된 빈 객체에 동적으로 프로퍼티나 메서드를 생성할 수 있습니다.


3) 생성된 객체 리턴

특별하게 정의된 리턴문이 없는 경우 this로 바인딩된 새로 생성한 객체가 리턴됩니다. 만약 명시적으로 다른 객체를 리턴하도록 했다면 다른 객체가 리턴됩니다.


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


이렇게 생성자를 이용해 객체를 생성하는 방식과 리터럴을 이용해 생성하는 방식의 차이는 프로토타입 객체(_proto_)에 있습니다.

위에 예제에서의 song객체의 프로토타입 객체는 Person이고 리터럴로 동일한 song 객체를 만들면 그 song객체의 프로토타입 객체는 Object입니다.


이는 자바스크립트 객체 생성 규칙 때문입니다. 자바스크립트 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정합니다.


객체 리터럴 방식에서는 객체 생성자 함수가 Object이고, 생성자 함수 방식의 경우는 생성자 함수 자체가 프로토타입 객체입니다.



3. call과 apply 메서드를 이용한 명시적인 this 바인딩


자바스크립트에서는 this를 특정 객체에 명시적으로 바인딩시키는 방법도 제공합니다. 이를 가능하게 하는 것이 함수 객체의 기본 프로퍼티인 apply와 call메서드입니다. 


이 메서드들은 모든 함수의 부모 객체인 Function.prototype 객체의 메서드이므로, 모든 함수는 다음과 같이 apply를 호출할 수 있습니다.


function.apply(thisArg, argArray);


call과 apply는 기능은 같고 단지 넘겨받는 인자의 형식만 다릅니다.


apply() 메서드를 호출하는 주체는 함수고, 특정 this를 바인딩할 뿐 본질적인 기능은 함수 호출입니다. sayName(); 은 

sayName.apply(); 와 같습니다.


apply의 첫 번째 인자인 thisArg는 apply() 메서드를 호출한 함수 내부에서 사용한 this에 바인딩할 객체입니다.

즉, 첫 번째 인자로 넘긴 객체가 this에 명시적으로 바인딩됩니다.


두 번째 인자인 argArray는 함수를 호출할 때 넘길 인자들의 배열입니다.


call()은 apply()와 기능은 같지만, apply에서 두 번째 인자로 배열 형태로 넘기는 것을 각각 하나의 인자로 넘기는 차이가 있습니다. 


Person.apply(song, ['song', 29]);

Person.call(song, 'song', 29);


call()과 apply() 함수의 대표적인 용도는 유사 배열 객체에서 배열 표준 메서드를 사용할 때 입니다.