
this 키워드 와 자바스크립트
자바스트립트 에서 사용되는 keyword 중에 this란 것이 있다. 자바스트립트 뿐만아니라, 다른 많은 언어들도 this keyword를 사용한다. 대부분에 언어에서 this keyword는 어떤 객체의 메소드 내에서, 해당 메소드를 실행한 객체 인스턴스를 반환하는 값이다. 하지만 자바스트립트 에서 this값이 결정되는 방식이 다르다.
context 와 this
자바스크립트에서 this keyword가 가지는 값은 해당 this가 사용된(실행된) 코드 부분 에서 context 값이다. context는 코드가 실행 될 수 있는 환경, 상황 을 의미한다. 자바스크립트 엔진이 코드를 실행하려면 여러 정보들이 필요하다. scope, 선언된 변수등에 대한정보가 필요한데, 이런 것들을 한데 모아 관리하게 된다.
실행 될 수 있다? 라는 말이 잘 안 와닿을 수있는데, 실행하는 주체를 컴퓨터(자바스크립트 엔진)라고 생각하면 조금 더 이해하기 쉽다. 자바스크립트 엔진 입장에서 눈 앞에 코드를 실행하기 전에 판단을 해야한다. 지금 실행하려는 어떤 코드가 해당 코드가 작성된 환경에서 실행 가능한지? context가 코드 실행 판단의 기준이 된다. 그리고 이 각각의context 정보를 하나의 객체값으로 가지고 있는 것이 this 이다. context에는 변수, 함수 선언, scope와 같은 정보들과 this값이 저장되어있다. 변수, 함수가 있나 없나?(접근 가능한 스코프 내에 해당 변수, 함수가 있는가?) 등을 판단하는 기준이 된다. 이 context가 준비가되어야 코드를 실행할 수 있는 준비가 된다.
실제로 자바스크립트 엔진은 context들을 객체로서 관리한다. 그리고 이 context들은 별도의 stack구조에 저장되어 관리된다. 각각의 context는 변수정보, 스코프정보, this값을가지고 여기서 this는 context 자기 자신에 대한 변수정보를 반환한다.
context는 언제 생성되는가?
자바스크립트 코드가 실행될 때 최초 1회 전역 컨텍스트가 생성된다. 그리고 함수가 실행 될 때 마다 해당 함수에 대한 context가 생성 되고 stack에 올라간다. 그리고 그 함수가 종료되면 해당 context는 사라진다.
함수 호출 방식이 context에 미치는 영향
바로 위에서 context는 최초 자바스크립트 실행 때 한번, 그리고 함수가 실행될 때마다 생성된다고 했다. 그렇다면 자연스럽게 this 값에 대해 고민할 때, 함수가 실행됬는가 아닌가 로만 생각하면 되지 않을까? 라는 생각을 할 수 있다. 결론부터 말하면 아니다.
function func(){
console.log("func ", this);
}
let obj = {
objFunc: function(){
console.log("objFunc ", this)
}
}
func();
//this 값으로 window 객체출력
obj.objFunc();
//this 값으로 objFunc 객체 출력
둘다 전역 스코프에서 함수를 실행했는데 this 값은 다르다. 즉 둘의 context는 다르다.
생성되는 context에 영향을 끼치는 중요한 요소가 있는데, 함수의 호출 방법 이다. 같은 함수라도 함수의 호출방법이 다르다면, this 값이 달라진다.
let func = function() {
console.log(this);
}
let obj ={
objFunc: func
}
console.log(func === obj.objFunc)
//true
func();
//this 값: window 객체
obj.objFunc();
//this 값: obj 객체
분명 같은 함수인데, 호출 방식에 따라 this 값이 다르다!
자바스크립트에서 함수 호출 방식
javscript에서의 함수 호출 방식은 4가지가 있다.
각각의 호출 방식은 context를 다르게 정의한다. 여기에 더해서 'strict mode'(엄격 모드) 사용여부도 context에 영향을 끼친다.
- 함수호출(function invocation)
일반 적인 함수 호출의 경우에는 this 값이 전역 컨텍스트의 this 값과 같다. this 값이 Window 객체를 반환한다.
function global(){
console.log("global ",this);
}
let obj ={
objFunc: function(){
console.log("objFunc ", this);
}
}
//객체 메소드를 변수에 할당
let copyedObjFunc = obj.objFunc;
//객체 메소드에 내부함수
let innerFuncObj = {
outer: function(){
console.log("outer ", this);
function inner(){
console.log("inner ", this);
}
inner();
}
}
//콜백함수
function getFunc(callback){
callback()
}
global();
//this: 전역 객체(window 객체)
copyedObjFunc();
//this: 전역 객체(window 객체
innerFuncObj.outer();
//outer의 this: innerFuncObj 객체
//inner의 this: 전역 객체(window 객체)
getFunc(obj.objFunc);
//this: 전역 객체(window 객체)
콜백함수의 경우 추가로 설명을 하자면 메소드를 함수 인자로 넘기는 것은 다음 코드와 같다.
let obj ={
objFunc: function(){
console.log("objFunc ", this);
}
}
function getFunc(callback){
callback()
}
//객체 메소드를 변수에 할당
let copyedObjFunc = obj.objFunc;
getFunc(obj.objFunc)()
//// 위의 코드는 아래와 같다.
let copyedMethod = obj.objFunc;
getFunc(copyedMethod);
함수가 인자값을 받을 때 호출시 전달된 파라메터의 값을 복사하므로 실제로 실행되는 함수의 호출 방식은 메소드 형태가 아니라 일반 함수 호출의 형태가 된다.
- 메소드 호출(method invocation)
속성 접근자(Property accessors) 를 이용한 함수 호출 방식이다. 이경우에 메소드 내부에서 this 값은 해당 메소드를 호출한 객체 이다.
let obj = {
objFunc: function(){
console.log(this);
}
}
obj.objFunc();
obj['objFunc']()
//두 호출 모두 this: obj 객체
생성자 호출(constructor invocation)
new keyword와 함께 사용된 함수 or class의 constructor 함수 내부에서 this 값은 this 값은 해당 호출로 생성된 객체값을 가진다.
function foo(name){
this.name = name;
}
let poo = new foo('koom')
console.log(poo.name);
//koom
class Sleep{
constructor(){
this.state = 'zzz';
}
}
let koom = new Sleep();
console.log(koom.state);
//zzz
- call, apply, bind를 이용한 경우(indirect invocation)
함수 호출 or 할당시, 명시적으로 this의 값을 할당하는 함수이다. 해당 함수들은 자바스크립트의 Function 객체의 프로토타입에 정의되어 있는 속성이다. 자바스크립트의 모든 함수들은 객체이고, 따라서 모든 함수는 call, apply, bind 함수를 속성으로서 가지고 있다.
call 과 apply 함수는 특정 함수를 호출 하면서 동시에 추가로 파라메터를 받아 해당값을 this에 할당한다. call 과 apply 함수의 차이는 기존 함수에 전달할 파라메터의 전달 방식이다. call 은 , 로 구분하여 전달하고 apply는 하나의 배열에 담아 전달한다.
let thisObj = {
someProperty: 1
}
function helloWorld(name){
console.log(this);
console.log(`hello ${name}!`);
}
helloWorld('koom');
// Window 객체, hello koom!
helloWorld.call(thisObj, 'koom');
//thisObj 객체, hello koom!
helloWorld.apply(thisObj, ['koom']);
//thisObj 객체, hello koom!
bind 함수는 특정 함수내부의 this 값과 파라메터 값들을 할당하여 새로운 함수로 반환한다. 그리고 한번 bind 된 this 값과 파라메터 값들은 변경할 수 없다.
let obj ={
func: function(){
console.log(this);
}
}
let obj2={}
let copy = obj.func;
let copyWithBind = obj.func.bind(obj);
copy();
// window 객체
copyWithBind();
//obj 객체
copyWithBind = obj.func.bind(obj2);
copyWithBind();
//obj 객체
this와 화살표 함수 (Arrow Function) 호출
자바스크립트의 scope 정책중 상위 scope 결정 방식은 Lexical Scope를 따르고 있다.
Lexical Scope란 특정 scope의 상위 scope를 결정할 때, 코드가 선언된 위치를 기준으로 결정한다.
화살표 함수 내부에서 this 값은, 가장 가까운 상위 scope의 this값으로 할당된다.
let obj = {
innerArrow: function(){
setTimeout(() => {
console.log(this);
}, 1000)
},
innerNotArrow: function(){
setTimeout(function(){
console.log(this);
}, 1000)
}
}
obj.innerArrow();
//this: obj 객체
obj.innerNotArrow();
//this: 전역 객체 ( window 객체)
엄격 모드 ('use strict')와 this
엄격모드 사용시, 일반 함수 호출에서 전역 객체의 접근을 막는다. 이에 따라서
엄격모드 사용시 일반 함수 호출 내부에서 this 값은 undefined 가 된다.
결론
자바스크립트에서 this 값을 정확히 파악 하기 위해서는, this가 어디에서 왔는지? 가 아니라 해당 this를 호출한 함수가 어떻게 호출됬는지? 를 파악할 필요가 있다. 이것을 정확히 파악한다면 this 값을 혼동하는 일은 없을 것이다. 반대로 내가 원하는 형태로 함수를 호출해서 원하는 this 값을 할당할 수도 있을 것이다.
참조:
https://dmitripavlutin.com/gentle-explanation-of-this-in-javascript/#71-this-in-arrow-function
https://poiemaweb.com/js-execution-context