코어 자바스크립트 책을 읽고 배운 내용을 바탕으로 작성되었다.
📌 상황에 따라 달라지는 this
this
는 기본적으로 실행 컨텍스트가 생성될 때 결정된다.
- 실행 컨텍스트는 함수가 호출될 때 생성되기 때문에 this는 함수를 호출할 때 결정된다.
- 따라서 함수를 어떤 방식으로 호출하느냐에 따라 this가 달라진다.
📙 전역 공간에서의 this
- 실행 컨텍스트에는 전역 컨텍스트도 있으며, 전역 컨텍스트에서의 this는
전역 객체
를 가리킨다.
- 이때 브라우저 환경에서의 전역 객체는
window
이며, Node.js 환경에서는 global
이다.
💡 전역 변수와 전역 객체의 프로퍼티
- 전역변수를 선언하면 자바스크립트 엔진은 전역변수를 전역 객체의 프로퍼티로도 할당한다.
- 따라서 전역 변수는 변수이자 전역 객체의 프로퍼티이다.
- 이유: 자바스크립트의 모든 변수는 특정 객체의 프로퍼티로서 동작한다.
- 이때 특정 객체는 실행 컨텍스트의
LexicalEnvironment
를 말한다.
- 실행 컨텍스트는 변수를 수집하여 L.E의 프로퍼티로 저장한다.
- 어떤 변수에 접근하면 L.E를 조회해서 일치하는 프로퍼티가 있을 경우 그 값을 반환한다.
- '삭제' 명령에 대해 전역변수 선언과 전역 객체의 프로퍼티 할당 사이의 차이점 존재
- 처음부터 전역 객체의 프로퍼티로 할당한 경우에는 delete
연산자를 이용하여 삭제가 되지만, 전역변수로 선언한 경우에는 삭제가 되지 않는다.
- 그 이유는 전역변수를 선언하면 자바스크립트 엔진이 이를 자동으로 전역 객체의 프로퍼티로 할당하면서 추가적으로 해당 프로퍼티의
configuable 속성
(변경 및 삭제 가능성)을 false
로 정의하기 때문이다.
📙 메서드로서 호출할 때 그 메서드 내부에서의 this
❗ 함수 vs 메서드
- 함수와 메서드의 차이는 독립성에 있다.
- 함수는 독립적인 기능을 수행하는 반면, 메서드는 자신을 호출한 객체에 대한 동작을 수행한다.
- 어떤 함수를 객체의 프로퍼티에 할당한다고 해서 그 자체로 무조건 메서드가 되는 것이 아니라 객체의 메서드로서 호출할 경우에만 메서드로 동작하고, 그렇지 않으면 함수로 동작한다.
- 같은 함수라도 메서도로 호출하느냐, 함수로 호출하느냐에 따라 내부 this가 달라진다.
💡 메서드 내부에서의 this
- 메서드 내부의 this는 자신을 호출한 주체에 대한 정보가 담긴다.
📙 함수로서 호출할 때 그 함수 내부에서의 this
💡 함수 내부에서의 this
- 어떤 함수를 함수로서 호출하는 경우에는 this가 지정되지 않는다. (호출 주체가 없음.)
- 만약 실행 컨텍스트를 활성화할 당시에 this가 지정되지 않는 경우에 this는 전역객체를 바라본다.
💡 메서드의 내부 함수 내부에서의 this
- 메서드의 내부 함수 역시 내부 함수를 객체의 메서드로서 호출하느냐, 함수로서 호출하느냐에 따라 this가 달라진다.
- 메서드로 호출하는 경우에는 자신을 호출한 주체가 this가 될 것이고, 함수로 호출하는 경우에는 전역객체가 this가 될 것이다.
💡 메서드의 내부 함수 내부에서의 this를 우회하는 방법
- 호출 주체가 없을 때는 자동적으로 전역 객체를 바인딩하지 않고 호출 당시 주변 환경의 this를 상속받아 사용하도록 하는 것이 자연스럽다.
- 변수를 검색하면 가장 가까운 스코프의 L.E를 찾고 없으면 상위 스코프를 탐색하듯이, this 역시 현재 컨텍스트에 바인딩된 대상이 없으면 직전 컨텍스트의 this를 바라보도록 하는 것이 설득력이 있는 방식이다.
변수 이용
let obj = {
outer: function(){
console.log(this);
const innerFunc1 = function(){
console.log(this);
};
innerFunc1();
const self = this;
cosnt innerFunc2 = function(){
console.log(self);
};
innerFunc2();
}
};
obj.outer();
- innerFunc1 내부에서 this는 전역객체를 가리킨다.
- 반면, outer 스코프에서 self라는 변수에 this를 저장한 상태에서 innerFunc2 함수를 호출한 경우에는 객체 obj가 출력된다.
- 💡 상위 스코프의 this를 변수에 저장하여 그 변수를 내부 함수에서 사용하는 방법
화살표 함수 (ES6)
- ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표 함수를 새로 도입했다.
- 💡 화살표 함수가 호출되어 실행 컨텍스트가 생성될 때 this 바인딩 과정 자체가 빠지게 되어 상위 스코프의 this를 그대로 활용할 수 있다.
- 화살표 함수 내부에 this가 아예 존재하지 않으므로, this에 접근하고자 하면 스코프체인 상 가장 가까운 this에 접근하게 된다.
📙 콜백함수 호출 시 그 함수 내부에서의 this
- 콜백 함수 내부에서의 this는 해당 콜백함수의 제어권을 넘겨받은 함수(메서드)가 정의한 바에 따르며, 정의하지 않은 경우에는 전역객체를 참조한다.
- 예시
- setTimeout 함수와 forEach 메서드는 그 내부에서 콜백함수를 호출할 때 대상이 될 this를 지정하지 않는다.
- addEventListener 메서드는 콜백함수를 호출할 때 this를 상속하도록 정의되어 있다.
📙 생성자 함수 내부에서의 this
💡 생성자 함수란?
- 여러 개의 동일한 프로퍼티를 가진 객체를 생성하는 데 사용하는 함수이다.
- 생성자:
class
- 클래스를 통해 만든 객체:
instance
- 생성자는 구체적인 인스턴스를 만들기 위한 일종의 틀이다.
- 이 틀에는 해당 클래스의 공통 속성들이 미리 준비돼 있고, 여기에 각 속성들에 대한 값을 할당하여 개별 인스턴스를 만들 수 있다.
- 자바스크립트는 함수에 생성자로서의 역할을 함께 부여했다.
new
명령어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작하게 된다.
💡 생성자 함수 내부에서의 this
- 어떤 함수가 생성자 함수로 호출된 경우 내부에서의 this는 곧 생성될 인스턴스가 된다.
📌 명시적으로 this를 바인딩하는 방법
📙 call 메서드
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
- call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령이다.
- 이때 call 메서드의 첫 번째 인자를 this로 바인딩하고, 이후의 인자들을 호출할 함수의 매개변수로 한다.
📙 apply 메서드
Function.prototype.apply(thisArg[, argsArray])
- call 메서드와 기능적으로 완전 동일하다.
- apply 메서드는 첫 번째 인자를 this로 바인딩하고, 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다.
❗ call / apply 메서드의 활용
💡 유사배열객체에 배열 메서드 적용
- 유사배열객체
array-like-object
: 키가 0 또는 양의 정수인 프로퍼티가 존재하고, length 프로퍼티의 값이 0 또는 양수인 객체
- 배열의 구조와 유사한 유사배열객체의 경우 call 또는 apply 메서드를 이용해서 배열 메서드를 적용할 수 있다.
- 첫 번째 인자로 유사배열객체 지정
- 문자열에 대해서도 call / apply 메서드를 이용해 배열 메서드 적용 가능
- 하지만, 문자열의 경우 length 프로퍼티가 읽기 전용이기 때문에 원문 문자열에 변경을 가하는 메서드는 에러를 던지며, concat처럼 대상이 반드시 배열이어야 하는 경우에 에러는 나지 않지만, 제대로 된 결과를 얻을 수 없다.
Array.from()
: ES6에서는 유사배열객체 또는 순회 가능한 모든 종류의 데이터 타입을 배열로 전환하는 메서드 도입
💡 생성자 내부에서 다른 생성자 호출
- 생성자 내부에 다른 생성자와 공통된 내용이 있을 경우 call 또는 apply를 이용해 다른 생성자를 호출하면 간단하게 코드 반복을 줄일 수 있다.
💡 여러 인수를 묶어 하나의 배열로 전달하고 싶을 때
- 여러 개의 인수를 받는 메서드에게 하나의 배열로 인수를 전달하고 싶을 때 apply 메서드를 사용하면 좋다.
- 참고로 ES6에서는
spread operator
(스프레드 연산자)를 이용하면 apply를 적용하는 것보다 더욱 간편하게 작성할 수 있다.
📙 bind 메서드
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])
- bind 메서드는 call과 비슷하지만, 즉시 함수를 호출하지 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환한다.
- 즉, bind 메서드는 함수에 this를 미리 적용하고 부분 적용 함수를 구현한다.
💡 name 프로퍼티
- bind 메서드를 적용해서 새로 만든 함수의 name 프로퍼티에는 동사 bind의 수동채인
bound
라는 접두어가 붙는다.
- 원본 함수에 bind 메서드를 적용한 새로운 함수라는 의미가 되므로 기존의 call이나 apply보다 코드를 추적하기에 더 수월하다.
💡 상위 컨텍스트의 this를 내부함수나 콜백 함수에 전달
- 메서드의 내부 함수의 this가 상위 컨텍스트의 this를 바라보게 하는 방법으로 변수를 이용했는데 call, apply, bind 메서드를 이용하여 더 깔끔하게 처리할 수 있다.
📙 별도로 인자로 this를 받는 경우 (콜백 함수 내에서의 this)
- 콜백 함수를 인자로 받는 메서드 중 일부는 추가로 this로 지정할 객체(
thisArg
)를 인자로 지정할 수 있는 경우가 있다.
Array.prototype.forEach(callback,[, thisArg])
Array.prototype.map(callback,[, thisArg])
Array.prototype.filter(callback,[, thisArg])
Array.prototype.some(callback,[, thisArg])
Array.prototype.every(callback,[, thisArg])
Array.prototype.find(callback,[, thisArg])
Array.prototype.findIndex(callback,[, thisArg])
Array.prototype.flatMap(callback,[, thisArg])
Array.prototype.from(arrayLike[, callback,[, thisArg]])
Set.prototype.forEach(callback,[, thisArg])
Map.prototype.forEach(callback,[, thisArg])
📌 정리
- 전역 공간에서의 this는 전역객체 (브라우저에서는 window, Node.js에서는 global)를 참조한다.
- 어떤 함수를 메서드로 호출한 경우 this는 메서드 호출 주체를 참조한다.
- 어떤 함수를 함수로서 호출한 경우 this는 전역객체를 참조한다. 메서드의 내부함수에서도 동일함.
- 콜백함수 내부에서의 this는 해당 콜백함수의 제어권을 넘겨받은 함수나 메서드가 정의한 바에 따르며, 정의하지 않은 경우에 전역객체를 참조한다.
- 생성자 함수에서의 this는 생성될 인스턴스를 참조한다.
- call, apply 메서드는 this를 명시적으로 지정하면서 함수 또는 메서드를 호출한다.
- bind 메서드는 this 및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 반환한다.
- 요소를 순회하면서 콜백 함수를 반복 호출하는 내용의 일부 메서드는 별도의 인자로 this를 받기도 한다.