자바스크립트에서 객체는 상태를 나타내는 프로퍼티와 동작을 나타내는 메서드를 하나의 논리적인 단위로 묶은 복합적인 자료구조이다.
동작을 나타내는 메서드는 자신이 속한 객체의 상태를 참조하여 동적으로 변경할 수 있어야 한다. 즉, 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 한다.
이런 문제를 해결하기 위해 this
키워드가 사용된다.
this
는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수 ( self-referencing variable )다.
this
는 함수 호출 방식에 의해 바인딩이 동적으로 결정된다.
함수를 호출하는 방식은 다양하다.
Function.prototype.apply/call/bind
메서드에 의한 간접 호출기본적으로 일반 함수 호출 시 this
는 전역 객체 ( global variable) 이 바인딩된다.
function foo() {
console.log(`foo's this => ${this}`)
//foo's this => [object Window]
function bar() {
console.log(`bar's this => ${this}`)
//bar's this => [object Window]
}
bar()
}
foo()
일반 함수 내부의 중첩 함수 또한 일반 함수로 호출하여도 this
는 전역 객체와 바인딩된다.
메서드 내부의 this
에는 메서드를 호출한 객체에 바인딩된다.
=> this
를 포함한 객체가 아닌 메서드를 호출한 객체이다.
const person = {
name: 'Kang',
getName() {
return this.name
},
}
console.log(person.getName()) //Kang
this
를 호출한 person 객체와 바인딩된다.
//다른 객체로 호출
const me = {
name: 'Lee',
}
me.getName = person.getName
console.log(me.getName()) //Lee
//일반 함수로 호출
var name = 'window name'
const callName = person.getName
console.log(callName()) // 'window name'
하지만 중요한 점은 this
는 메서드를 호출한 객체와 바인딩되기 때문에 항상 person 객체만 바인딩되는 것이 아니라는 것이다.
me.getName()
으로 호출하게 되면 this
는 me 객체와 바인딩되고, callName
일반 함수로 호출하게 되면 전역 객체와 바인딩되어 window.name
으로 참조하게 된다.
var
로 선언한 전역 변수는 전역 객체의 프로퍼티가 된다. 하지만 만약 메서드에 중첩 함수를 통해 호출하게 되면 어떻게 작동할까?
var name = 'window name'
const person = {
name: 'Kang',
getName() {
console.log(`person name => ${this.name}`) // person name => Kang
//중첩함수 => 일반함수
function foo() {
console.log(`foo name => ${this.name}`)// foo name => window name
}
//콜백함수 => 일반함수
setTimeout(function () {
console.log(`callback => ${this.name}`)// callback => window name
}, 100)
foo()
},
}
메서드 내부에서 호출하더라도 일반 함수로 호출 시 this
는 전역객체와 바인딩된다. 이렇게 메서드와 일반 함수의 this
바인딩 불일치는 로직 상 혼란을 일으킬 수 있으며 이를 해결하기 위해 Function.prototype.apply/call/bind
메서드가 사용된다.
생성자 함수로 this
호출 시 미래에 생성될 인스턴스와 바인딩된다.
function Person(name) {
this.name = name
this.hello = function () {
return `hello ${this.name}`
}
}
const Kang = new Person('Kang')
console.log(Kang.hello())// hello Kang
const Lee = new Person('Lee')
console.log(Lee.hello())// hello Lee
new
연산자를 사용하지 않으면 생성자 함수는 일반 함수로 동작한다. Function.prototype.apply/call/bind
메서드는 Function.prototype
의 메서드로 모든 함수가 상속받아 사용할 수 있다.
Function.prototype.apply,Function.prototype.call
메서드는 this
와 바인딩할 객체와 함수에 전달할 인수를 전달받아 함수를 호출한다.
function returnThis() {
return this
}
const arg = { x: 1 }
console.log(returnThis()) // window
console.log(returnThis.apply(arg), [1, 2, 3]) // {x: 1} (3) [1, 2, 3]
console.log(returnThis.call(arg), 1, 2, 3) // {x: 1} 1 2 3
Function.prototype.apply
와 Function.prototype.call
의 차이는 인수를 받는 방식에서 차이를 보인다.
Function.prototype.bind
는 함수를 호출하지 않고 인수로 this
와 바인딩할 객체만 전달한다.
var name = 'window name'
const person = {
name: 'Kang',
getName() {
console.log(`person name => ${this.name}`) //person name => Kang
//중첩함수 => 일반함수
function foo() {
console.log(`foo name => ${this.name}`)
}
//콜백함수 => 일반함수
setTimeout(
function () {
console.log(`callback => ${this.name}`)
}.bind(this), //callback => Kang
100
)
foo.bind(this)() // foo name => Kang
},
}
console.log(person.getName()) //Kang