이전 포스팅에서 실행컨텍스트에대해 정리했었다.
오늘은 실행컨텍스트를 정리하면서 나온 개념인 This에 대해 포스팅하고자 한다.
앞서 실행컨텍스트에 대해 간략히 복습하고 넘어가자면,
실행컨텍스트는 “코드실행을 위해 필요한 환경정보를 담은 객체”라고 정의했었다.
코드실행을 위해서 실행컨텍스트는 대표적으로 “식별자에관한 정보”, “식별자의 유효범위에 관한 정보”를 지니고 있으며, “코드실행 주체에관한 정보”를 지닌다고 했다.
여기서 “코드실행 주체에 관한 정보”가 바로 this에 담긴다.
따라서, this는 기본적으로 실행컨텍스트가 생성될때 결정되며, 실행컨텍스트는 함수가 호출될때 생성되는 객체이므로 즉, this는 함수가 호출될때 결정된다고 할 수 있다.
일반적으로 this는 어떻게 호출되느냐에따라 달라진다.
브라우저 환경에서의 전역객체는 Window, Node.js환경에서의 전역객체는 global이 된다.
브라우저환경에서 전역변수를 선언하면 자바스크립트 엔진은 전역객체 Window의 프로퍼티에 할당시킨다.
전역공간에서의 this는 전역객체를 가리키므로 즉, 전역공간에서의 this는 Window객체이다.
var a = 1;
console.log(a) // 1
console.log(window.a) // 1
console.log(this.a) // 1
그렇다면, 전역변수를 선언하는 것과 전역객체(window)에 직접 프로퍼티를 할당하는 것은 완전히 동일한 것일까?
⇒ 대체적으로 유사하게 동작하지만, 완전 동일한 것은 아니다!
⇒ 1. configurable(변경 및 삭제 가능성) 과 2. 호이스팅여부에서 차이가 존재한다.
전역변수 선언 | 전역객체에 프로퍼티 할당 | |
---|---|---|
변경 및 삭제 가능성 | 삭제(delete) 불가 | 삭제(delete) 가능 |
var a = 1;delete a; //false | window.b = 1;delete b;//true | |
호이스팅 여부 | 호이스팅 됨 | 호이스팅 안됨 |
함수를 메서드라 부르고, 메서드를 함수라 부르며 많이들 구분짓지 않고 통칭하곤 하지만 엄밀히 말하면 “함수”와 “메서드”는 다르다.
어떠한 코드를 실행하는 방법엔 “함수로서 호출하는 방법”과 “메서드로서 호출하는 방법”이 있다. 두 방식 모두 미리 정의한 동작을 수행하도록하는 코드인것은 맞지만,
쉽게 말해 “함수로서 호출하는 경우”는 대상객체를 명시하지 않고(함수명앞에 . 없이) 코드를 실행시키는 방법이고
“메서드로서 호출하는 경우”는 함수명앞에 . 으로 대상객체를 명시하고 코드를 실행시킨다.
그렇다면, 메서드로서 코드를 실행할때 그 메서드 내부에서의 this는 무엇이 될까?
바로 . 앞의 객체가 this가 된다.
( 당연한 얘기를 풀어서 말하는 듯 하지만.. this는 함수를 호출할때 결정되는 객체이므로 함수를 호출한 대상이 .앞의 객체이니 .앞에 명시된 객체가 this가 된다.)
그렇다면, 함수로서 호출할때는 대상객체를 명시하지 않는데, 이 경우엔 무엇이 this가 될까?
바로 전역객체(window)이다.
함수로서 호출할때 달리 this를 지정하지 않는다면 전역객체가 this가 되는데,
함수로서 호출하더라도 코드의 맥락상 this를 전역객체로 두고싶지않은 경우도 있을 것이다.
( 예를들어, 호출 당시 주변환경의 this를 상속받아 사용하고 싶은 경우이다.)
이런 경우엔 어떻게 하면 좋을까?
함수 B에 콜백함수 A를 넘긴경우, A의 제어권은 B가 관장하게된다.
따라서, 제어권을 가진 B함수에서 콜백함수A에 별도의 this를 지정할수 (지정하지 않을 수도)있다.
콜백함수 호출 시 대상이 될 this를 지정하지 않으면 this는 전역객체가 되고 지정하면 그 대상이 this가 된다.
setTimeout(function() { console.log(this) }, 300); // this -> 전역객체
[1,2,3,4,5].forEach(function(x) {
console.log(this, x); // this -> 전역객체
});
document.body.querySelector('#a')
.addEventListner('click', function(e) {
// this -> 지정한 엘리먼트 (클릭이벤트가 발생할때 콜백함수를 실행한 주체는 앞서 지정한엘리먼트가 되므로)
console.log(this, e);
});
‘생성자 함수’란 객체를 생성하는데 사용하는 함수를 말한다. new 키워드를 사용하여 호출하면 해당 함수가 생성자로 동작한다.
생성자 함수로 호출된 경우 내부에서의 this는 인스턴스 자신이 된다.
함수를 어떻게 호출하는지 상황에 따라 this에 바인딩되는 값이 달라지는 케이스들을 살펴봤다.
(그런데 이런 규칙을 깨고) 명시적으로 this에 별도의 대상을 바인딩하는 방법도 존재한다.
가 그러하다.
Function.prototype.call(thisArg[,arg1[,arg2[,...]]])
Function.prototype.apply(thisArg[,argsArray]);
1) 유사배열객체(array like object)에 배열메서드를 적용
: 객체에는 배열 메서드를 직접 적용할 수 없는데, 배열의 구조와 유사한 객체(유사배열객체)는 call 또는 apply메서드를 이용해 배열 메서드를 차용할 수 있다.
뿐만아니라 배열처럼 인덱스와 length프로퍼티를 지닌 문자열에 대해서도 가능하다.
⇒ 난 개인적으로 굳이 call과 apply를 이용해서 배열이 아닌 것에 배열 메서드를 이용하려고 들지는 않을 것 같다. 책에서도 언급했지만 숨은 뜻을 알고있는 사람이 아닌이상 코드만 봐서는 의도를 파악하기 어렵고 ES6에서는 순회가능한 모든 종류의 데이터타입을 배열로 바꾸는 Array.from 메서드를 제공하고 있기 때문에 call과 apply보다는 Array.from
메서드를 선호하는 편이다!
2) 생성자 내부에서 다른 생성자를 호출하고 싶을 때
: 생성자 내부에 다른 생성자와 공통된 내용이 있을 경우 call 이나 apply메서드를 이용하면 반복을 줄일 수 있다.
3) apply 활용예 - 여러 인수를 묶어 하나의 배열로 전달하고 싶을때
: 여러개의 인수를 받는 메서드에 하나의 배열로 인수를 전달하고 싶을 때 apply메서드를 사용하면 좋다.
⇒ 그런데 ES6에 spread operator(펼치기 연산자)가 나와버려서 굳이 지금도 사용할 필요는 없을 듯하다. ( 그럼에도 ES5이하의 환경에서는 마땅한 대안이 없어서 실무에서 광범위하게 활용되었다고..)
Function.prototype.bind(thisArg,[,arg1[,arg2[,...]]])