[JS] 코어자바스크립트 - this
코어 자바스크립트를 꼼꼼하게 정독하고
복습하며 정리한 내용을 토대로 F-lab 멘토링을 받고있다.
이 글은 멘토링으로 알게된 것들을 다시한번 정리해보는 글이다.
this
대부분의 객체지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 의미한다.
대부분 클래스에서만 사용할 수 있기 때문에 혼란의 여지가 별로 없는데,
자바스크립트에서의 this는 어디서나 사용할 수 있고, 상황에 따라 this가 바라보는 대상이 달라진다.
그래서 정확한 작동방식을 이해하지 못하면 예외사항황 발생 시 원인을 찾기 어렵다.
상황에 따라 달라지는 this
this는 기본적으로 실행컨텍스트가 생성될때 함께 결정된다.
실행컨텍스트는 함수를 호출할때 생성되므로, this는 함수를 호출할때 결정되며
함수를 어떤 방식으로 호출하느냐에 따라 값이 달라질 수 있다.
함수를 어떤 객체의 메소드로서 호출하면 this는 객체를 참조하고,
함수를 일반함수로 호출하면 this는 전역객체를 참조하게된다.
전역공간에서의 this
전역공간에서 this는 전역객체를 가리킨다.
브라우저 환경에서는 window를 말하고, Node.js 환경에서는 global을 말한다.
전역 공간에서의 특이한 성질
전역변수를 선언하면 자바스크립트 엔진은 이 변수를 전역객체의 속성으로도 할당한다.
변수이면서 객체의 속성이기도 하게된다. a === window.a === this.a
자바스크립트의 모든 변수는 사실 특정 객체의 속성으로서 동작한다.
특정 객체란, 실행컨텍스트의 LexicalEnvironment를 말한다.
실행컨텍스는 변수를 수집해서 LE의 속성으로 저장한다.
이후 변수를 호출하면 해당 공간의 LE를 조회해서 일치하는 속성이 있으면 그 값을 반환한다.
전역변수 VS 전역객체의 속성
전역공간에서 window.a
로 할당하는 것과 a
변수를 선언하는 것은 똑같이 동작한다.
하지만, 삭제 명령에서는 동작이 달라진다.
처음부터 전역객체의 속성으로서 a를 할당하면 delete 연산자 사용이 가능하지만
그냥 a변수를 전역공간에 선언하면
전역객체에 속성으로 할당되면서 변경 및 삭제가 가능한 속성을 false로 정의하게 된다.
함수와 메서드 구분하기
함수를 실행하는 가장 일반적인 방법은 함수로서 호출하는 경우와 메서드로 호출하는 경우이다.
이 둘은 독립성에서 차이가 난다.
함수
는 그 자체로 독립적 기능을 수행하는 반면
메서드
는 자신을 호출한 대상 객체에 관한 동작만을 수행한다.
어떤 함수를 객체의 속성으로 할당한다고 해서 그 자체로 무조건 메서드가 되는 것이 아니라
obj.method()
의 형태처럼 호출하는 경우에만 메서드로 동작한다.
메서드 내부에서의 this
this는 호출한 주체에 대한 정보를 담는다.
어떤 함수를 메서드로 호출하는 경우, 호출의 주체는 메서드를 호출한 객체가 된다.
1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
methodA() {
console.log(this);
},
inner: {
methodB() {
console.log(this);
},
},
};
obj.methodA(); // this는 obj 출력
obj.inner.methodB(); // this는 inner 출력
함수 내부에서의 this
함수를 함수로서 호출하는 경우에는 this가 지정되지 않는다.
함수로서 호출하는 것은 호출 주체를 명시하지 않고
개발자가 코드에 직접 관여해서 실행한 것이기 때문에 호출 주체의 정보를 알 수 없기 때문이다.
(함수는 독립적으로 기능을 수행한다.)
this가 지정되지 않은 경우 this는 전역객체를 바라본다고 했으므로
함수에서의 this는 전역객체를 참조한다.
메서드 내부 함수에서의 this
메서드 내부에서 정의하고 실행한 함수에서의 this는 혼란스러울 수 있으나
해당 함수를 호출하는 구문 앞에 어떤 객체가 있는지만 확인하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const obj = {
outer: function () {
console.log(this);
// this는 obj 출력
const innerFunc = function () {
console.log(this);
};
innerFunc();
// 메소드 내부지만 일반 함수로 실행되었기 때문에 this는 전역객체
const obj2 = {
innerMethod: innerFunc,
};
obj2.innerMethod();
// this는 obj2 출력
},
};
obj.outer();
위의 코드처럼 되면 this에 대한 구분은 명확히 할 수 있지만
innerFunc() 내의 this처럼 this가 가지는 의미 자체가 어색해지는 경우가 생긴다.
호출 주체가 없을때 자동으로 전역객체를 바인딩 하지 않고
함수 호출 당시 주변환경의 this를 그대로 상속받아 사용할 수 있도록 우회할 수 있다.
변수를 검색하면 스코프체인에 의해 가장 가까운 스코프의 LE를 찾고
없으면 상위 스코프를 탐색하는 것과 같이,
this도 현재 컨텍스트에 바인딩된 대상이 없으면 직전 컨텍스트의 this를 바라보도록 하는 것이다.
ES5까지는 this의 우회를 위해 스코프내에서 var self = this
등의 방법으로
변수에 this를 저장한 다음 self를 this 대신 사용하기도 했다.
this를 바인딩하지 않는 함수, 화살표 함수
ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 해결하고자
this를 바인딩하지 않는 화살표함수 기능을 새로 도입했다.
화살표함수를 사용하면 실행컨텍스트를 생성할때 this가 전역객체를 바인딩하는 과정이 빠지게되어서
상위 스코프의 this를 그대로 활용할 수 있게 된다.
1
2
3
4
5
6
7
8
9
10
const obj = {
outer: function () {
console.log(this); // this는 obj 출력
const innerFunc = () => {
console.log(this); // 화살표 함수의 this는 상위스코프의 obj를 출력
};
innerFunc();
},
};
obj.outer();
콜백함수 호출 시 this
함수 A의 제어권을 다른함수에게 넘겨주는 경우, 함수 A를 콜백함수라고 한다.
A는 B의 로직에 따라 실행되며 this도 B에서 정한 규칙에 따라 값이 결정된다.
콜백함수도 함수이기때문에 기본적으로 this가 전역객체를 바라보게 되지만
콜백함수 B에서 별도로 this의 대상을 지정한 경우 그 대상을 참조하게 된다.
주로 같은 동작을 반복해야하는 배열 메서드에서 this값을 지정할 수 있으며
Set
map
filter
forEach
등이 있다.
1
2
3
document.querySelector("#a").addEventListener("click", function (e) {
console.log(this, e);
});
addEventListener
라는 메서드는 콜백함수를 호출할 때 자신의 this를 상속하도록 정의되어 있다.
따라서 메서드를 호출한 객체인 a 엘리먼트가 this의 대상이 된다.
생성자 함수에서의 this
생성자 함수는 공통된 성질을 지니는 객체들을 생성하는데 사용하는 함수이다.
생성자함수를 통해 만들어진 객체는 인스턴스라고 부른다.
여러 공통 속성의 집합을 정의하는 생성자함수를 만들고,
생성자함수를 통해 만들어진 인스턴스는 생성자함수의 속성을 가지면서 인스턴스 각각의 속성도 가질 수 있다.
결국 생성자는 구체적인 인스턴스를 만들기 위한 일종의 틀이라고 할 수 있다.
자바스크립트는 함수에 생성자로서의 역할을 함께 부여했다.
new
명령어와 함께 함수를 호출하면 생성자로서 동작하게 된다.
그리고 함수가 생성자로서 호출된 경우, 내부의 this는 새로 만들어지는 인스턴스를 가리킨다.
명시적으로 this를 바인딩하는 방법
기본적으로는 앞에서 설명한 것과 같이 this가 정의되지만
규칙을 깨고 this에 별도의 대상을 바인딩하는 방법들도 있다.
call 메서드
call 메서드는 호출 주체인 함수를 즉시 실행하도록 하는 명령이다.
이때 call 메서드의 첫번째 인자를 this로 바인딩하고, 이후의 인자들을 각각 매개변수로 사용한다.
1
2
3
4
5
const sum = function (a, b) {
console.log(this, a + b);
};
sum(1, 2); // this는 전역객체
sum.call({ x: 1 }, 1, 2); // this는 { x: 1 }
apply 메서드
apply 메서드는 call 메서드와 기능적으로는 완전히 동일한데
두번째 인자를 배열로 받아서 그 배열의 요소들을 매개변수로 지정한다.
1
2
3
4
const sum = function (a, b) {
console.log(this, a + b);
};
sum.apply({ x: 1 }, [1, 2]);
bind 메서드
bind 메서드를 이용하면 함수를 어떻게 호출했는지에 상관없이 this값을 설정해줄 수 있다.
1
2
3
4
5
const sum = function (a, b) {
console.log(this, a + b);
};
const bindSum = sum.bind({ x: 1 });
bindSum(1, 2); // this는 { x: 1 }
call
apply
bind
의 활용은 여러가지 가능성을 갖지만
오히려 이로인해 this를 예측하기 어렵게 되는 측면도 있다.
하지만 ES5 이하의 환경에서는 마땅한 대안이 없어 실무에서 활용되었다고하니 알아두자.
this 규칙 정리
this가 가지는 기본 규칙
- 전역공간에서 this는 전역객체 참조
- 메서드로 호출된 함수의 this는 호출한 객체를 참조
- 일반 함수가 호출되었을때 this는 전역객체 참조
- 콜백함수 내부의 this는 콜백함수를 부른 함수가 정의한 바에 따른다
- 따로 정의한 바가 없다면 전역객체 참조
- 생성자함수에서 this는 생성자를 통해 만들어지는 인스턴스를 참조
명시적 this 바인딩
- call apply bind 메서드를 사용하면 this를 명시적으로 지정할 수 있다
- 주로 배열 메서드 중 일부는 별도의 인자로 this가 참조할 객체를 받기도 한다
Leave a comment