3 this
this
실행 컨텍스트가 생성될 때 함께 결정된다 = 함수를 호출할 때 결정된다.
전역에서의 this
특별히 this를 지정하지 않으면 this는 전역객체를 가리킨다. (브라우저 환경에선 window, Node.js 환경에서는 global)
자바스크립트의 모든 변수는 특정 객체의 프로퍼티가 된다.
var a = 1; console.log(window.a); // 1 console.log(this.a); // 1실행 컨텍스트가 식별자(변수)들을 수집해서 LexicalEnvironment의 프로퍼티로 저장하기 때문이다.
따라서 사실상
var a와window.a는 동일하다고 할 수 있는데, 다른 점은 delete 명령에서이다.window.a = 1로 선언한 변수는 delete 명령으로 제거할 수 있지만var a = 1로 선언한 변수는 delete 명령으로 제거할 수 없다. 이 차이는 일종의 사용자의 실수를 막기 위한 방어전략인데, 자바스크립트 엔진은 var 로 선언된 변수는 프로퍼티로 저장할 때configurable속성을 false로 설정해서 삭제가 불가능하도록 한다.
메서드로서 호출된 함수의 this
- 함수란 : 독립적으로 호출되는 함수
- 메서드란 : 객체에 속해서 호출되는 함수
중요한 것은 어떻게 호출되느냐이다. 동일한 함수라도 함수의 메서드로서 호출되면 메서드로, 아니면 함수로 동작한다. 함수 이름 앞에 객체가 명시되어 있다면 (점 표기법 / 대괄호 표기법) 그 함수는 메서드로서 호출된 것이다.
this에는 호출 주체에 대한 정보가 담긴다. 메서드의 호출 주체는 함수명(프로퍼티명) 앞의 객체이다. 따라서 메서드로서 호출된 함수의 this는 함수가 속한 객체가 된다.
함수로서 호출된 함수의 this
메서드가 아닌 일반 함수로 호출하면 호출 주체가 명시되지 않는다. this가 명시적으로 지정되지 않은 경우에는 기본적으로 전역 객체를 this로 지정한다고 했다. 따라서 일반 함수의 this는 전역객체가 된다.
더글라스 크락포드 피셜 이 부분은 설계상의 오류라고 한다. (자세한 이유는 아래에…)
this가 명시적으로 지정되지 않은 경우에는 기본적으로 전역객체를 this로 지정하는 것 때문에 같은 함수여도 어떻게 호출하느냐에 따라서 this가 달라지게 되었다. 그냥 무작정 전역객체를 this로 설정하기보다는 호출 당시 주변의 this를 상속받았다면 더 좋았을 것이다. (스코프 체인의 개념과도 어울린다.)
호출 당시 주변의 this를 사용하는 방법은 2가지가 있다.
(ES5 이전) 변수에 this를 저장해서 활용하기.
단순하게 함수 선언 전에 this를
_this나self같은 변수에 저장해두고 함수 내에서 활용하는 방식이다.(ES6 이후) 화살표 함수 활용하기
화살표 함수는 this 바인딩 과정 자체가 없기 때문에 상위 this를 그대로 상속받는다.
콜백 함수의 this
콜백 함수는 메서드를 전달하든 일반 함수로 전달하든 함수로서 호출된다. 그래서 기본적으로 this가 전역객체가 된다. 하지만 addEventListener 같은 함수는 this로 자신을 호출한 element를 this로 갖는데 그건 addEventListener에서 그렇게 설정했기 때문이다. 콜백 함수를 호출하는 측은 this에 대한 제어권도 넘겨받는다. 따라서 제어권을 갖는 코드가 this를 결정하고, 만약 특별히 결정하지 않는다면 전역객체가 this로 설정된다.
자세한 내용 : 4장
생성자 함수의 this
새로 만들 인스턴스(자신)가 this이다.
명시적 this 바인딩
- call : 바로 호출, 첫 번째 인자가 this, 두 번째 이후의 인자들이 함수의 인자가 된다.
- apply : 바로 호출, 첫 번째 인자가 this, 두 번째 인자로 함수의 인자가 담긴 배열을 전달한다.
- bind : this가 바인딩된 함수를 반환한다.
call, apply 활용하기
유사배열 객체에 배열 메서드를 사용하기
유사배열 객체란 키가 0 또는 양의 정수인 프로퍼티가 존재하고 length 프로퍼티가 0 이상의 값인 객체를 말한다. 원래 객체에는 배열 메서드를 사용할 수 없지만, call, apply를 이용하면 유사배열 객체에서 배열 메서드를 차용할 수 있다.
사실 this와 관련은 없고 배열로 형변환하는 것에 가깝다. ES6에서는 배열로 형변환할 수 있는 Array.from 메서드가 도입되어서 본래 목적에 더 맞는 메서드를 사용하는 것이 더 좋을 것 같다.
생성자 내부에서 다른 생성자 호출하기
중복된 내용이 있는 생성자라면 this를 이용해서 다른 생성자의 로직을 호출할 수 있다.
class Person { constructor(name, age) { this.name = name; this.age = age; } } class Student { constructor(name, age, grade) { Person.call(this, name, age); this.grade = grade; } }여러 인수를 묶어서 하나의 배열로 만들고 싶을 때 => apply 메서드 활용
Math.max같은 함수들은 비교할 숫자들을 개별 인자들로 전달해야 한다. 만약에 긴 배열에서 최댓값을 구해야 하는 경우라면 사용하기 곤란한데, 이때 apply 메서드를 활용할 수 있다.var numbers = [1, 2, 3, 4, 5]; var max = Math.max.apply(null, numbers);ES6부터는 spread 연산자를 이용해서 apply를 사용하는 것보다 더 간편하게 동일한 로직을 작성할 수 있다.
var numbers = [1, 2, 3, 4, 5]; var max = Math.max(...numbers);call/apply는 명시적으로 this를 바인딩할 수 있지만 오히려 this를 예측하기 어려워져 코드 해석이 어려워진다는 단점이 있지만, ES5 이전까지는 마땅한 대안이 없었기 때문에 많이 활용되는 메서드였다.
콜백 함수 중에는 this를 별도의 인자로 받는 것들도 있다. (thisArg라는 이름으로 받는다.)