January 12, 2020
대표적으로 JAVA 같은 객체지향 언어에서의 this는 클래스 인스턴스의 레퍼런스 변수입니다. 하지만 javacript에서 this는 전혀 다른 의미를 가집니다. this가 어려운 이유는 자바스크립트에서 this는 전혀 다른 의미를 가지기 때문입니다. 이 글은 공부를 목적으로 정리한 글입니다.
“실행 문맥”이란 말은 호출자가 누구냐는 것과 같습니다.
alert(this === window) //true, 호출자는 window
const caller = {
f: function() {
alert(this === window)
},
}
caller.f() // false, 호출자는 caller 객체
평상시의 this는 window를 가리킵니다. 첫 번째 alert는 window.alert() 즉 window 객체의 메소드 호출이기 때문에 호출자는 window이고 두 번째는 caller 객체의 호출입니다. 다만 strict-mode에서는 전역 객체냐 일반 객체냐에 따라 함수 내부의 this의 결과가 차이가 있습니다. 이 문제는 window를 함수 호출 앞에 붙여주면 해결됩니다.
function nonStrictMode() {
return this
}
function strictMode() {
'use strict'
return this
}
console.log(nonStrictMode()) // window
console.log(strictMode()) // undefined
console.log(window.stricMode()) //window
생성자에는 우리가 제일 싫어하는 this가 많이 나옵니다. 여기서 생성자 함수도 함수입니다. 생성자는 new로 객체를 만들어 사용합니다. 만약에 new로 호출하지 않고 그냥 호출을 하게 되면 어떻게 될까요?
function NewObject(name, color) {
this.name = name
this.color = color
this.isWindow = function() {
return this === window
}
}
const newObj = NewObject('nana', 'yellow')
console.log(newObj.name) // error
console.log(newObj.color) // error
console.log(newObj.isWindow()) // error
const newObj2 = new NewObject('didi', 'red')
console.log(newObj2.name) // didi
console.log(newObj2.color) // red
console.log(newObj2.isWindow()) // false
new를 붙이지 않고 그냥 호출하면 어떻게 될까요? 위에서 그냥 함수에서 this가 window를 가리킨다고 말했습니다. 이렇게 되면 각 property를 가져올 수 없게 됩니다. 생성자 함수가 아닌 일반 객체에서는 어떨까요?
const person = {
name: 'john',
age: 15000,
nickname: 'man from earth',
getName: function() {
return this.name
},
}
console.log(person.getName()) //john
const otherPerson = person
ortherPerson.name = 'chris'
console.log(person.getName()) // chris
console.log(otherPerson.getName()) //chris
생성자 함수와 크게 다르지 않습니다. 한 가지 볼 점은 otherPerson.name을 chris로 설정한 뒤 person.getName()을 호출하면 출력 결과는 chris로 변경됩니다. 이유는 otherPerson은 person의 레퍼런스 변수이므로 하나를 변경하면 다른 하나도 변경됩니다. 이를 피하기 위해 Object.assign() 메서드를 이용하여 완전히 별도의 객체로 만들어야 합니다.
const person = {
name: 'john',
age: 15000,
nickname: 'man from earth',
getName: function() {
return this.name
},
}
const newPerson = Object.assign({}, person)
newPerson.name = 'chris'
console.log(person.getName()) // john
console.log(newPerson.getName()) // chris
이번에는 생성자 함수 안에서 또 다른 함수가 있는 경우를 살펴보겠습니다.
function Family(firstName) {
this.firstName = firstName
const names = ['bill', 'mark', 'steve']
names.forEach(function(lastName, index) {
console.log(lastName + ' ' + this.firstName)
console.log(this)
})
}
const kims = new Family('kim')
// bill undefined
// window
// mark undefined
// window
// steve undefined
// window
Family 생성자 함수 안에서 forEach 메서드를 호출합니다. undefined가 출력되는 이유는 forEach 메서드의 서브루틴은 호출될 때 forEach의 context(this)로 바인드 되지 않습니다. 바인드가 안되었다는 것은 전역이라는 것이고 실행 문맥이 전역이라는 말은 this가 window라는 것입니다.
이 문제를 해결하기 위해서는 this를 that이라는 변수에 저장하면 됩니다.
function Family(firstName) {
this.firstName = firstName
const names = ['bill', 'mark', 'steve']
const that = this
names.forEach(function(value, index) {
console.log(value + ' ' + that.firstName)
})
}
const kims = new Family('kim')
// bill kim
// mark kim
// steve kim
또 다른 방법은 bind라는 메서드를 사용하면 됩니다.
function Family(firstName) {
this.firstName = firstName
const names = ['bill', 'mark', 'steve']
names.forEach(
function(value, index) {
console.log(value + ' ' + this.firstName)
}.bind(this)
)
}
const kims = new Family('kim')
또는 ES6 화살표 함수(arrow function)를 사용할 수 있습니다.
function Family(firstName) {
this.firstName = firstName
const names = ['bill', 'mark', 'steve']
name.forEach((value, index) => {
console.log(value + ' ' + this.firstName)
})
}
const kims = new Family('kim')
정리
참고