다시 한번 언급하지만 우리는 실행 컨텍스트에 대해 배우고 있었다. 실행 컨텍스트는 실행할 코드에 제공할 환경정보들을 모아놓은 객체이다.
그 객체 안에는 세가지가 존재한다.
1. VE(VariableEnvironment)
2. LE(LexicalEnvironment)
3. ThisBindings
여기에서 세번째에 해당하는 this에 대해서 알아볼 것이다.
이 말은 'this를 bind한다'고 표현하기도 한다. 다시 말하면 this는 함수를 호출할 때 결정된다.
1) 전역공간에서 this는 전역 객체를 가리킨다.
2) 런타임 환경에 따라 this는 window(브라우저 환경(ex. 크롬)), 또는 global(ex. 노드환경)을 가리킨다.
함수 vs 메서드
함수와 메서드는 상당히 비슷해 보이지만 엄연한 차이가 존재한다. 기준은 독립성이다. 함수는 그 자체로 독립적인 기능을 수행하지만 메서드는 자신을 호출한 대상 객체에 대한 동작을 수행한다.
함수명()
객체.메서드명()
함수에서 할당된 this와 메서드에서 할당된 this는 다음과 같다.
<script>
// CASE1 : 함수
// 호출 주체를 명시할 수 없기 때문에 this는 전역 객체를 의미해요.
var func = function (x) {
console.log(this, x);
};
func(1); // Window { ... } 1
// CASE2 : 메서드
// 호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미해요.
// obj는 곧 { method: f }를 의미하죠?
var obj = {
method: func,
};
obj.method(2); // { method: f } 2
</script>
1) 함수 내부에서의 this
어떤 함수를 함수로서 호출할 경우, this는 지정되지 않는다. 호출 주체가 누군지 알 수 없기 때문에. 실행컨텍스트를 활성화할 당시 this가 지정되지 않은 경우, this는 전역 객체를 의미한다. 따라서, 함수로서 '독립적'으로 호출할 때는 this는 예외없이 항상 전역객체를 가리킨다.
2) 메서드의 내부함수에서의 this
역시 예외없이
메서드의 내부라고 해도, 함수로서 호출한다면 this는 전역 객체를 의미하게 된다.
ex) 이게 이해된다면 전부 이해 된 것.
<script>
var obj1 = {
outer: function() {
console.log(this); // (1)
var innerFunc = function() {
console.log(this); // (2), (3)
}
innerFunc();
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod();
}
};
obj1.outer();
</script>
이 코드의 결과는 (1) : obj1, (2) : 전역객체, (3) : obj2
상기의 법칙에 따라 this에 대해 이해는 할 수 있지만, 개발자의 입장에서 수긍이 가지 않을 때도 있을 것이다. 그렇기 때문에 this를 우회하는 방법에 대해서도 알아보자.
1) 변수를 활용하는 방법
내부 스코프에 이미 존재하는 this를 별도의 변수에 할당하는 방법이다.
<script>
var obj1 = {
outer: function() {
console.log(this); // (1) outer
// AS-IS
var innerFunc1 = function() {
console.log(this); // (2) 전역객체
}
innerFunc1();
// TO-BE
var self = this;
var innerFunc2 = function() {
console.log(self); // (3) outer
};
innerFunc2();
}
};
// 메서드 호출 부분
obj1.outer();
</script>
2) 화살표 함수
ES6부터 도입된 화살표 함수. 함수 내부에서 this가 전역객체를 바라보는 현상, 즉 유실되는 현상때문에 화살표 함수가 도입되었다고 해도 과언은 아닐 것이다.
"일반 함수와 화살표 함수의 가장 큰 차이점은 무엇인가요?"
라고 누군가 묻는다면 가장 적절한 대답은 this binding 여부가 가장 적절한 답일 것이다.
화살표 함수는 this binding 과정을 생략한다.
<script>
var obj = {
outer: function() {
console.log(this); // (1) obj
var innerFunc = () => {
console.log(this); // (2) obj
};
innerFunc();
}
}
obj.outer();
</script>
outer가 호출되는 순간에 outer는 obj에 메소드로써 호출되는 것이기 때문에 (1)단계에서의 this는 obj를 바라보는게 맞다. 하지만 만약 innerFunc가 일반 함수로 선언이 되었다면 this binding과정이 진행되고, 실행이 되는 순간 innerFunc는 전역객체를 바라보기 때문에 (2)단계에서 this는 전역객체로 도출이 되었겠지만, 화살표 함수는 언급했듯 this binding과정을 생략하기 때문에 이전에 binding되었던 obj를 계속 바라보게 되는 것이다.
콜백 함수의 정의는 다음과 같다.
"어떠한 함수, 메소드의 인자(매개변수)로 넘겨주는 함수"
이 때, 콜백함수 내부의 this는 해당 콜백함수를 넘겨받은 함수(메소드)가 정한 규칙에 따라 값이 결정된다. 콜백함수도 함수이기 때문에 this는 전역 객체를 참조하지만, 콜백함수를 넘겨받은 함수에서 콜백함수에 별도의 this를 지정한 경우는 예외적으로 그 대상을 참조하게 되어있다.
<script>
// 별도 지정 없음 : 전역객체
setTimeout(function () { console.log(this) }, 300);
// 별도 지정 없음 : 전역객체
[1, 2, 3, 4, 5].forEach(function(x) {
console.log(this, x);
});
// addListener 안에서의 this는 항상 호출한 주체의 element를 return하도록 설계되었음
// 따라서 this는 button을 의미함
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
console.log(this, e);
});
</script>
<script>
var Cat = function (name, age) {
this.bark = '야옹';
this.name = name;
this.age = age;
};
var choco = new Cat('초코', 7); //this : choco
var nabi = new Cat('나비', 5); //this : nabi
</script>
새로운 인스턴스를 생성해내는 함수 Cat은 인스턴스 내부의 프로퍼티 중 일부를 매개변수로 받아 생성하게 된다. 이러한 생성자로 생성된 서로 다른 인스턴스 'choco'와 'nabi'는 서로 다른 this를 바라보게 된다.