자바스크립트 this 바인딩
✏️

자바스크립트 this 바인딩

Description
자바스크립트에서 this가 어떻게 바인딩 되는지 알아보자
Tags
Published
Published April 20, 2020

this

많은 언어에서 this는 인스턴스 자신(self)을 가리키는 참조변수로 사용된다. 하지만 자바스크립트에서는 대부분의경우 this의 값은 함수를 호출하는 방법에 의해 결정된다.

함수 호출 방식

자바스크립트에서는 함수가 어떻게 호출(실행) 되었는가에 따라서 this에 바인딩될 객체가 동적으로 결정된다.
함수의 호출방식은 아래와 같다.
 
  1. 일반 함수 호출
  1. 메소드 호출
  1. 생성자 함수 호출
  1. apply / call / bind 호출
 

1. 일반 함수 호출

function func1() { console.log(`func1's this: ${this}`); const func2 = function() { console.log(`func2's this: ${this}`); } func2(); } func1(); // func1's this: [object Window] // func2's this: [object Window]
기본적으로 this는 전역객체(Window, Global)에 바인딩 된다.
 

2. 메소드 호출

const personA = { name: 'juicy', sayName: function() { console.log(`sayname: ${this} / ${this.name}`); const sayName2 = function() { console.log(`sayname2: ${this} / ${this.name}`); } sayName2(); } } personA.sayName(); // sayname: [object Object] / juicy // sayname2: [object Window] / undefined
function이 프로퍼티로서 호출되면 메소드로서 호출된다고 한다. 이때 this는 해당 메소드를 호출한 객체에 바인딩 된다.
 
코드상 선언되어있는 this는 그 자체로는 this가 무엇이 될지 알수가없다. 함수가 호출될때(실행될때) (정확히는 함수가 실행되기전 실행 컨텍스트 생성에서) this가 바인딩 되기 때문이다.
 

3. 생성자 함수 호출

function Person(name) { this.name = name; console.log('this: ', this); } const me = new Person('juicy'); // this: Person {name: "juicy"} console.log(me); // Person {name: "juicy"} console.log(this.name); // undefined /* this: Person {name: "juicy"} Person {name: "juicy"} undefined */ const me2 = Person('juicy'); console.log(me2); //undefined console.log(this.name); // juicy /* this: Window {...} undefined juicy */ undefined
 
자바스크립트의 함수는 호출(실행)시 new 연산자를 붙이면 생성자 함수로서 동작한다. (특정 함수만 되거나 특별한 형식이 정해져있는게 아니라 모든 함수가) 이말은 생성자 함수라는게 따로 있다기보다 new 키워드와 함께 호출하면 일반 함수도 생성자 처럼 동작하고 생성자 함수로서 사용되는 함수도 new 키워드가 없다면 일반 함수처럼 동작한다. 이러한 혼란을 방지하기 위해 보통 생성자 함수로서 사용되는 함수는 첫문자를 대문자로 사용한다.
 

4. apply / call / bind

자바스크립트는 엔진의 this 바인딩 동작 이외에 특정 객체에 명시적으로 this를 바인딩하는 메소드를 제공한다. (Function.prototype.apply, Function.prototype.call)
 
Function.prototype.apply / Function.prototype.call
 
const Person = function(name){ this.name = name; }; const personA = new Person('juicy'); const personB = {}; Person.apply(personB, ['jusung']); console.log(personA) // Person {name: "juicy"} console.log(personB) // {name: "jusung"}
Person.apply(personB, ['jusung']); 를 보면 Person 함수를 호출하되 this를 빈객체인 personB로 바인딩하고 두번째 파라미터로 arguments 값을 전달한다.
 
function func1(){ console.log(arguments); // 1,2,3 const double = arguments.map(v => v * 2); // Uncaught TypeError: arguments.map is not a function // Array를 프로토타이핑 하지않아 map method 미존재 console.log(double); } func1(1,2,3) /* Uncaught TypeError: arguments.map is not a function at func1 (<anonymous>:3:27) at <anonymous>:6:1 */
arguments는 유사배열로 Array를 프로토타이핑 하지 않는다. 따라서 Array의 메소드들을 사용할 수 없다.
 
function func2() { console.log('arguments: ', arguments); //const sorted = [...arguments].sort(); const sorted = Array.prototype.sort.apply(arguments) console.log('sorted: ', sorted); } func2(1,4,3,5)
하지만 위와같이 apply 메소드를 사용하면 가능하다.
Array.prototype.sort.apply(arguments) 는 Array.prototype.sort() 를 호출하되 this를 arguments로 바인딩 하라는 의미이다. 즉, Array 오브젝트에 프로토 타이핑 되어있는 sort 메소드를 arguments의 메소드인것 처럼 (arguments.sort()) 호출하는것이다.
 
const foo = { first: 'first', } class Person { first; printFullName(second, third) { console.log(`${this.first} ${second} ${third}`); } } const A = new Person(); A.printFullName.apply(foo, ['second', 'third']); //first second third A.printFullName.call(foo, 'second', 'third'); //first second third
call() 메소드도 apply() 메소드와 기능은 동일하지만 두번째 파라미터가 배열 형태이냐 spread된 형태이냐의 차이만 다르다.
 
class Person { name; constructor(name) { this.name = name; } doSomething(cb) { cb(); // undefined cb.call(this); } } const A = new Person('juicy') function sayName() { console.log(this.name); } A.doSomething(sayName)
 
 
Function.prototype.bind
bind는 명시적으로 this가 바인딩된 새로운 함수를 리턴한다. 단 call과 apply처럼 함수를 실행하지는 않기때문에 아래와 같이 명시적으로 직접 호출 해주어야 한다.
function func3() { console.log(argumnets); const double = Array.prototype.map.bind(arguments)(v => v * 2); //const double = [...arguments].map(v => v * 2); // 이런식으로도 유사배열을 Array로 바꿔서도 가능 console.log(double); } func3(1,2,3); // or class Person { name; constructor(name) { this.name = name; } doSomething(cb) { cb(); // undefined cb.call(this); // call() 메소드 사용 cb.bind(this)(); // bind() 메소드 사용 } } const A = new Person('juicy') function sayName() { console.log(this.name); } A.doSomething(sayName)