this는 모든 함수 스코프 내에 자동으로 생성되는 특수한 식별자다.
tihs는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수다.
this의 값은 함수를 호출하는 방법과 호출 시점에 의해 this에 바인딩할 객체가 동적으로 결정된다.
식별자와 값을 연결하는 과정
→ 변수나 함수와 같은 식별자와 그에 대응하는 값 또는 메모리 주소를 연결하는 것을 의미한다.
변수 선언과 메모리 바인딩
→ 변수선언은 변수이름과 확보된 메모리 공간의 주소를 연결하는 것을 바인딩이라고 한다.
this 바인딩
→ 함수가 실행될 때, this 키워드가 어떤 객체를 참조하는지를 결정하는 것을 this 바인딩이라고 한다.
// 함수 선언식
function globalFun() {
return this;
}
globalFun(); // window 출력
// 함수 표현식
const globalFun = function() {
console.dir(this);
}
globalFun(); // window 출력
화살표 함수는 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정된다.
화살표 함수 안에서 this는 언제나 상위 스코프의 this를 가리킨다.
화살표 함수의 this 바인딩 객체 결정 방식은 함수의 상위 스코프를 결정하는 방식인 렉시컬 스코프와 유사하다. 이를 렉시컬 Lexical this 라고 한다.
화살표 함수는 call, apply, bind 메소드를 사용하여 this를 변경할 수 없다.
// 화살표 함수
const arrowFun1 = () => {
console.log(this);
};
arrowFun1(); // window 출력
const obj = {
arrowFun1: function () {
const arrowFun2 = () => {
console.dir(this);
};
arrowFun2();
},
};
obj.arrowFun1(); // obj 출력
메서드를 호출하면 기본적으로 this는 해당 메서드를 가지고 있는 객체에 바인딩 된다.
메서드가 화살표 함수로 작성되었을 때는 화살표 함수의 this는 상위 컨텍스트의 this를 계승받기 때문에 this가 전역 객체에 바인딩 된다.
const obj = {
aaa: function () {
console.log('aaa this:', this);
},
bbb() {
console.log('bbb this:', this);
},
ccc: () => {
console.log('ccc this:', this);
},
};
obj.aaa(); // aaa this: obj
obj.bbb(); // bbb this: obj
obj.ccc(); // ccc this: window
함수는 전역에 선언된 일반 함수와 객체 안에 메서드로 크게 구분할 수 있다.
내부 함수가 호출되는 시점에 동적으로 this가 결정되며, 이때 내부 함수가 어떻게 호출되었느냐에 따라 this가 결정되고, 기본적으로는 전역 객체에 바인딩된다.
내부함수의 this가 전역객체를 바인딩하지 않는 방법도 있다.
const obj = {
aaa: function () {
function aaaInner() {
console.log('aaaInner this:', this);
}
aaaInner();
},
// that 변수에 bbb 메서드의 this를 할당해 사용
// 그러면 내부함수의 this가 전역객체를 바인딩하지 않음
bbb: function () {
const that = this;
function bbbInner() {
console.log('bbbInner this:', that);
}
bbbInner();
},
ccc: function () {
const cccInner = () => {
console.log('cccInner this:', this);
};
cccInner();
},
};
obj.aaa(); // aaaInner this: window
obj.bbb(); // bbbInner this: obj
obj.ccc(); // cccInner this: obj
const obj = {
aaa: function () {
// 화살표 함수는 상위 스코프의 this를 바인딩 한다.
setTimeout(() => {
console.log('aaa setTimeout this:', this);
}, 1000);
},
bbb: function () {
setTimeout(function () {
console.log('bbb setTimeout this:', this);
}, 1000);
},
};
obj.aaa(); // aaa setTimeout this: aaa
obj.bbb(); // bbb setTimeout this: Window
new 키워드로 생성자 함수를 호출하면 빈 객체가 생성되고, 이 빈 객체는 생성자 함수의 프로토타입과 연결된다.
생성자 함수 내에서 사용되는 this는 새로 생성된 빈 객체로 바인딩 되고, this를 통해 새로운 객체에 프로퍼티와 메서드를 추가할 수 있다.
생성자 함수 내의 코드가 실행되면서 새로운 객체가 초기화 된다.
new 키워드를 빼먹으면 일반함수 호출과 같아지기에, 이 경우에는 this가 window에 바인딩 된다.
function Kim(name) {
this.name = name;
}
// 생성자 함수 호출
const kim = new Kim('park');
console.log(kim.name); // park
call 메소드는 함수를 호출하며, 첫번째 매개변수로 전달된 객체를 함수내에서 this로 사용한다.
추가 매개변수들은 해당 함수에 인자로 전달된다.
function example(num1, num2) {
console.log(this.name, num1 + num2);
}
const person = {
name: 'Kim',
};
example.call(person, 3, 5); // Kim 8
apply 메소드는 call과 유사하지만, 인자들을 배열 형태로 전달한다.
배열의 각 요소가 함수에 순서대로 인자로 전달된다.
function example(num1, num2) {
console.log(this.name, num1 * num2);
}
const person = {
name: 'Kim',
};
example.apply(person, [4, 6]); // Kim 24
bind 메소드는 함수를 호출하지 않고, 새로운 함수를 반환한다.
반환된 함수는 전달된 첫번째 매개변수를 this로 바인딩한 상태로 유지한다.
function example(num1, num2) {
console.log(this.name, num1 - num2);
}
const person = {
name: 'Kim',
};
const newExample = example.bind(person);
newExample(20, 10); // kim 10
<button id="btn">click me!</button>
const btn = document.querySelector('#btn');
btn.addEventListener('click', function () {
console.log(this); // this는 클릭된 button을 가리킴
});
let stairs = {
step: 0,
up() {
this.step++;
return this;
},
down() {
this.step--;
return this;
},
showStep() {
alert( this.step );
}
}
stairs.up().up().up().down().up().up().down().showStep(); // 출력: 3
간단한 코드 설명
stairs.up()
을 호출하면, this는 stairs 객체를 가리키고 step을 1 증가시킨다.
.down()
을 호출하면, this는 여전히 stairs 객체를 가리키고 step을 1 감소시킨다.
.showStep()
을 호출하면 this는 여전히 stairs 객체를 가리키니까 step 값을 alert로 출력한다.
- 여기서는 전부 객체의 메소드를 호출하니까, this는 그 메소드가 속해있는 객체를 가리키고,
up()
메소드 5번,down()
메소드 2번, step 값 즉, 출력된 값은 3이다.