JavaScript에서 자주 사용되는 this
는 호출되는 위치에 따라 그 역할이 구별되는 것을 말합니다. 그렇다면 어디서 this
를 호출하고 어떻게 역할이 구별되는지 알아보겠습니다.
브라우저 콘솔창을 열고 this
를 입력해보면
this; // Window {}
window입니다.
이처럼 this
는 기본적으로 window
입니다.
window
가 아닐 때의 this
도 알아보겠습니다.
일반 함수 실행 방식으로 함수를 실행했을 때, this
의 값은 Global Object
를 가르킵니다. 브라우저 상에서는 window
입니다.
function foo(){
console.log(this);
}
foo();
위 코드에서 foo
라는 함수를 선언하고 foo();
로 실행했습니다. 이런식으로 함수를 호출하는 방식을 일반 함수 실행 방식이라고 합니다.
const name = 'Tiger';
function foo(){
console.log(this.name); // Tiger
}
foo();
위 코드에서는 전역변수로 name
이라는 변수를 만들고 Tiger라는 값을 할당하였습니다. 이 변수는 전역 변수이기 때문에 전역 객체인 window
에 추가됩니다.
즉, window
객체에 name
라는 key와 Tiger라는 value가 추가된 것 입니다.
그리고 함수를 일반 함수 실행 방식으로 실행했으므로 console.log(this.name);
은 console.log(window.name);
이라고 한 것과 동일합니다. 그렇기 때문에 위 코드를 실행시키면 console 창에 Tiger가 출력됩니다.
'use strict' // Strict mode
const name = 'Tiger';
function foo(){
console.log(this.name); // error!
}
foo();
하지만 위처럼 Strict mode에서 this
는 무조건 undefined
입니다. 그렇기 때문에 foo 함수가 아무리 일반 함수 실행 방식으로 실행되었다고 하더라도 this
는 undefined
가 됩니다.
생성자는 new 키워드를 사용해서 객체를 만들어 사용하는 방식입니다. 객체지향 언어에서 일반적으로 객체를 만들 때 쓰이는 문법과 동일하고 가리키는 대상 또한 객체지향 언어의 this
와 같습니다.
function exampleObject(name, color){
this.name = name;
this.color = color;
this.isWindow = function(){
return this === window;
}
}
const newObj = new exampleObject('cheese', 'yellow');
console.log(newObj.name); // cheese
console.log(newObj.color); // yellow
console.log(newObj.isWindow()); // false
const newObj2 = new exampleObject('coke', 'black');
console.log(newObj2.name); // coke
console.log(newObj2.color); // black
console.log(newObj2.isWindow()); // false
new 키워드로 새로운 객체를 생성했을 경우 생성자 함수 내의 this는 new를 통해 만들어진 새로운 변수가 됩니다.
newObj
, newObj2
는 같은 생성자 함수로 만들어진 객체이지만 완전히 별도의 객체이기 때문에 각 객체의 속성들은 서로 관련이 없습니다.
const withoutNew = exampleObject('cheese', 'yellow');
console.log(withoutNew.name); // error
console.log(withoutNew.color); // error
console.log(withoutNew.isWindow()); // error
하지만 만약 new 키워드를 빼먹으면 일반적인 함수 실행과 동일하게 동작하므로 exampleObject
함수 내의 this
는 window
객체가 됩니다. 하지만 withoutNew
가 함수 실행의 결과값이 할당되므로 각 프로퍼티를 가져올 수 없습니다.
const person = {
name: 'Tiger',
age: 3,
nickname: 'Tiger Finger',
getName: function(){
return this.name;
},
}
console.log(person.getName()) // Tiger
const otherPerson = person;
otherPerson.name = 'Cat'
console.log(person.getName()) // Cat
console.log(otherPerson.getName()) // Cat
일반 객체도 생성자 함수와 다르지 않지만 otherPerson.name
을 Cat으로 설정한 뒤 person.getName()
을 호출하면 결과는 Cat 입니다. 그 이유는 otherPerson
은 person
의 레퍼런스 변수이므로 하나를 변경하면 다른 하나도 변경됩니다. 이를 피하기 위해서는 Object.assign()
메서드를 이용하여 완전히 별도의 객체로 만들어야 합니다.
명백한 바인딩 메소드는 this
의 역할을 직접 명확하게 지정해준다는 뜻 입니다.
메소드 | 특징 |
---|---|
call | 제공된 컨텍스트와 매개변수로 함수를 실행 |
apply | 제공된 컨텍스트와 매개변수를 배열로 사용하여 함수를 실행 |
bind | 제공된 컨텍스트로 함수를 바인딩 하지만 실행하지 않음, 실행하려면 함수를 호출해야함 |
const person1 = {firstName: 'Jon', lastName: 'Kuperman'};
const person2 = {firstName: 'Kelly', lastName: 'King'};
function say(greeting) {
console.log(greeting + ' ' + this.firstName + ' ' + this.lastName);
}
// call 예제
say.call(person1, 'Hello'); // Hello Jon Kuperman
say.call(person2, 'Hello'); // Hello Kelly King
// apply 예제
say.apply(person1, ['Hello']); // Hello Jon Kuperman
say.apply(person2, ['Hello']); // Hello Kelly King
// bind 예제
const sayHelloJon = say.bind(person1);
const sayHelloKelly = say.bind(person2);
sayHelloJon(); // Hello Jon Kuperman
sayHelloKelly(); // Hello Kelly King
위와 같이 명백한 바인딩 메소드로 this
의 역할을 지정해주면 문제가 없어 보입니다.
const testObj = {
outerFunc: function() {
function innerFunc() {
console.log(this) // window
}
innerFunc()
},
}
testObj.outerFunc()
하지만 위와 같이 outerFunc
가 외부에서 실행되면 this
는 testObj
입니다. 그리고 outerFunc
내부에서 innerFunc
가 호출할때는 그 어떤 문맥도 바인드되지 않았습니다. 그 말인 즉, innerFunc
은 window
에서 실행되었다는 이야기가 됩니다. 이게 바로 비엄격모드에서 innerFunc
의 this
가 window
가 되는 이유입니다.
function Family(firstName) {
this.firstName = firstName
const names = ['bill', 'mark', 'steve']
const that = this
names.map(function(value, index) {
console.log(value + ' ' + that.firstName)
})
}
const kims = new Family('kim')
// bill kim
// mark kim
// steve kim
위와 같이 별도의 상수(const)를 지정해서 문제없이 이름들이 출력될 수 있게 만들 수 있지만 항상 that
이라는 상수를 만들어주어야 합니다. 매우 귀찮고 실수가 생기기 쉬운 작업이 될 수 있습니다. 이러한 문제점을 해결하기 위해서 ES6의 arrow function
을 사용한다면 해결할 수 있습니다.
function Family(firstName) {
this.firstName = firstName
const names = ['bill', 'mark', 'steve']
names.map((value, index) => {
console.log(value + ' ' + this.firstName)
})
}
const kims = new Family('kim')
arrow function
은 내부적으로 미리 내부에서만 사용할 변수를 만들어 두고 this를 할당합니다.
this
는 기본적으로 브라우저에서 Strict mode가 아닐 때는window
, Strict mode일 때는undefined
입니다. 하지만 일반 함수, 생성자 함수, 객체 메서드, bind, call, apply 일 때this
가 바뀝니다. 호출되는 위치에 따라 그 역할이 바뀌기 때문에 항상this
를 잘 확인해 봐야 합니다.
[javascript] this는 어렵지 않습니다.
[JS/this] 자바스크립트, this의 4가지 역할
자바스크립트의 this는 무엇인가?
Javascript call() & apply() vs bind()?