this
키워드의 경우 javascript
뿐만 아니라 다른 언어들에도 존재를 합니다만 특히, javascript
에서 이 this
키워드는 다른 언어와 조금 다르게 동작합니다.
또한 이 this
키워드는 javascript
내부적으로도 엄격모드와 비엄격모드에서 조금 다르게 동작을 하며 다른 언어들보다 조금 더 어려운 개념을 가지고 있습니다.
- this는 스코프와 연결이 있습니다.
- this는 객체에도 영향을 줍니다.
- 따라서 코드를 작성할 때의 시점과 코드를 실행했을 때의 this가 다를 수 있습니다.
- this는 실행컨택스트, 스코프를 모두 이해했을 때 내가 비로소 이해했다고 할 수 있습니다.
this
는 함수의 런타임에 따라, 함수가 호출되는 시점에 따라 그 값이 다르기 때문에 개발자의 의도와 다르게 코드를 작성하는 시점과 실제로 this
가 동작하는 시점에서 차이가 있을 수 있습니다.
이러한 불편 때문에 ES5에서 부터는 함수를 어떻게 호출했는지 상관하지 않고 this값을 설정할 수 있는 bind
메소드를 도입되었습니다.
이번 시간에는 이 this
가 상황에 따라 어떻게 동작하는지에 대해 자세히 알아보도록 하겠습니다.
scope, this, 실행 컨택스트의 동작 원리는 모두 이해해야 this
를 이해했다고 할 수 있기 때문에 집중해 공부를 해보면 좋겠습니다.
Node.js 환경에서의 this
는 global입니다. 이 global객체는 node.js답게 node.js를 실행하는데 필요한 실행환경들을 갖추고 있습니다.
브라우저에서의 this
는 window로 브라우저에서 실행하는데 필요한 객체들이 주로 담겨져 있습니다.
window.alert('Hello');
this.alert('Hello');
두 코드의 실행 결과는 undefined로 같습니다. 즉 전역공간에서의 this
는 window를 바라보는 것을 확인할 수 있습니다.
하지만 node.js환경에서는 alert이 존재하지 않습니다. 결국 같은 this
고 같은 전역공간에서도 제공되는 this
가 다르게 되는 것입니다.
함수에서의 this
는 window를 가르키게 됩니다. 즉, 함수에서의 this
는 전역공간을 가르키게 됩니다.
마찬가지로 브라우저에서 선언한 함수와 Node.js에서 선언한 함수가 바라보는 것은 this
가 바로 전역공간을 바라보기 때문에 전역공간에서 선언한 this
와 다르지 않다는 것입니다.
function func() {
console.log(this)
}
func() // Window {...}
아래 코드에서 확인을 할 수 있듯이 메서드에서의 this
는 함수에서의 this
와는 다릅니다. 메소드에서의this
는 호출되는 대상의 객체를 가르키게 됩니다.
const obj = {
name: 'obj',
method: function() {
return this.name
}
}
obj.method() // 'obj'
단, 함수를 어떠한 객체의 프로퍼티로 할당한다고 해서 무조건 메서드인 것은 아닙니다. 객체의 메서드로서 호출을 한 경우에만 메서드로 동작을하고, 그렇지 않은 경우에는 함수로 동작합니다.
그렇기 때문에 내부함수에서의 this를 정의하는 것에 어려움을 겪을 수 있습니다. 하지만 결국 메서드의 내부함수에서 함수로서 호출을 했는지 메서드로서 호출을 했는지를 파악한다면 this의 값을 맞추는 건 어렵지 않습니다.
내부 함수가 함수로서 호출을 했는지 메서드로서 호출을 했는지를 파악하기 위해서는 함수명 앞에
.
이 있는지{}
있는지를 살펴야합니다.let func = function (x) { console.log(this, x); }; func(1); // Window {...} 1 ←함수로 동작 let obj = { method: func } obj.method(2); // { method: f } 2 ←메서드로 동작
상위 스코프의 this를 저장함으로써 내부함수에서 활용할 수 있습니다. 단, 화살표함수는 실행컨텍스트를 생성할 때 this바인딩 과정이 빠지게 되어, 상위스코프의 this를 그대로 활용합니다.
let obj = {
fun1: function(){
console.log(this);
let self = this; //상위 스코프에 this저장
let innerFunc = function(){
console.log(self); // { outer: f }
};
innerFunc();
}
};
obj.fun1();
상황에 따라 this
가 달라지는 것을 암시적 this 바인딩이라고 합니다. 사용자가 생각하는 것과 다르게 호출될 수도 있고 굉장히 암시적이기 때문에 암시적 바인딩이라고 합니다.
이렇게 예측하기 어렵고 스코프와 실행되는 시점에 따라 바뀌는 암시적바인딩을 조금 더 안전하게 만들어주는 명시적 바인딩이 있습니다. 바로 함수의 내장메서드인call
bind
, apply
를 이용하는 방법입니다.
※var
전역 공간에서 var로 변수를 선언하는 경우 자바스크립트 엔진은 전역객체의 프로퍼티로 할당을 하게 됩니다. 즉, 변수이면서 객체의 프로퍼티가 되는 것이죠.
또한 var 변수로 선언을 한 경우, 전역객체의 프로퍼티로 할당은 가능하지만 삭제는 불가능합니다. (전역 객체의 프로퍼티로 할당하면 삭제도 가능합니다)
따라서 var보다는 const, let을 사용하는 것을 권장드립니다.
func.call(명시적으로 조작하고 싶은
this
의 대상, 원본의 함수가 받는 인자)
const person = {
name: '영서',
sayName: function () {
return this.name + '입니다'
},
};
const zero = {
name: '베이스',
sayName: function () {
return this.name + '입니다.'
},
}
function sayFullName(firstName) {
return firstName + this.sayName()
}
const result1 = sayFullName.call(person, '노')
const result2 = sayFullName.call(person, 'Roh')
const result3 = sayFullName.call(zero, '노')
console.log(result1);//노영서입니다
console.log(result1);//Roh영서입니다.
console.log(result1);//노베이스입니다.
sayFullName
을 호출하기 전에 call
메서드를 부르고 이 메서드의 첫번째 인자로 명시적으로 조작하고 싶은 this
의 대상을 넣으면 됩니다. 또한 두 번째 인자로는 원본의 함수(sayFullName
)가 받는 인자(firstName
)을 넣어주면 됩니다.
이렇게 call
에 첫번째로 넣는 인자로 this
가 명시적으로 바뀌게 되는 것입니다.
apply
메소드의 경우 call
이랑 다른 부분은 없지만 배열을 인자로 받을 수가 있습니다. 인자로 받는 원본 함수가 배열을 argument로 취해서 활용할 때 사용할 수 있습니다.
function sayFullName(firstName) {
return arguments[0] + this.sayName()
}
const result1 = sayFullName.apply(person, ['장', 'Jang']);
const result2 = sayFullName.apply(zero, ['장', 'Jang']);
console.log(result1) // 장영서입니다.
console.log(result2) //장베이스입니다.
function sayFullName(firstName) {
return arguments[1] + this.sayName()
}
const result3 = sayFullName.apply(person, ['장', 'Jang']);
const result4 = sayFullName.apply(zero, ['장', 'Jang']);
console.log(result3) //Jang영서입니다.
console.log(result4) //Jang베이스입니다.
bind
메소드를 이용하면 this를 고정시켜놓고 사용할 수 있습니다.
function sayFullName(firstName) {
return firstName + this.sayName()
}
const sayFullNamePerson = sayFullName.bind(person);
const sayFullNameZero = sayFullName.bind(zero)
console.log(sayFullNamePerson('노')) //노영서입니다
console.log(sayFullNameZero('제로')) //제로베이스입니다
이렇게 call
bind
apply
를 사용하면, this
를 고정시켜서 묶어 놓고 사용을 할 수 있습니다. 실제로 bind
의 경우 react컴포넌트에도 많이 사용되었습니다.
그만큼 this
에 대한 고민이 많았으며, this
를 명시적으로 지정을 해놓고 사용되기도 한다는 점을 잘 알아두시길 바랍니다.
참고자료