javascript - this

김동하·2020년 9월 19일
0

javascript

목록 보기
16/58

대부분의 객체 지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 의미한다. 하지만 자바스크립트 this는 상황에 따라 바라보는 대상이 달라진다. 함수와 객체(메서드)의 구분이 느슨한 자바스크립트에서 this는 실질적으로 이 둘을 구분해준다.

전역객체

전역 객체는 특수한 객체고 모든 객체는 이 객체의 프로퍼티다.

function func(){
  console.log("print")
}

func()  // "print"
window.func() // "print"

함수 func() window라고 하는 전역 객체의 메소드다.

(원리)

const a = 1;

console.log(a) // 1
console.log(window.a) // 1
console.log(this.a) // 1

자바스크립트의 모든 변수는 특정 객체의 프로퍼티다. 특정 객체란 바로 실행 컨텍스트의 LexicalEnvironment(이하 LE)다. 실행 컨텍스트는 변수를 수집해 LE에 저장한다. 이후 어떤 변수를 호출하면 LE를 조회하여 일치하는 프로퍼티가 있을 경우 그 값을 반환한다.

변수 a에 접근하고자 스코프 체인에서 a를 검색하다가 가장 마지막에 도달하는 전역 스코프의 LE, 즉 전역 객체에서 해당 프로퍼티 a 를 발견해서 그 값을 반환한 것이다.

this

this는 함수를 어떻게 호출하느냐에 따라 지칭하는 대상이 달라진다. 함수 내에서 호출 맥락(context)을 의미한다.

function func(){
  if(window === this){
    console.log(window === this) // true
  }
}

func()

this는 함수 안에서 전역객체를 의미하는 window를 가리킨다.

메소드를 호출했을 때 this

함수와 메서드의 차이는 독립성에 있다. 함수는 독립적으로 기능을 수행! 하지만 메서드는 자신을 호출한 객체에 관해 동작한다.

const obj = {
  func : function(){
    if(obj === this){
      console.log("this = obj") // this = obj
    }
  }
}

obj.func()

객체 안에서 함수가 메서드로 존재할 때 메서드 안 this는 그 객체를 가리킨다.(접근할 수 있다)

메서드의 내부함수에서 this (심화)

함수가 함수로 호출될 때 함수 내부에서 this는 지정되지 않는다. this는 호출한 주체의 정보가 있는데 함수를 함수로 호출하게 되면 개발자가 코드에 직접 관여했기 때문에 호출 주체 정보를 알 수 없다. 따라서 함수에서 this는 전역 객체를 가리킨다.

메서드 내부에서 정의하고 있는 함수에서 this 는 다르다.

const obj1 = {
    outer: function() {
        console.log(this) // 1번 
        const innerFunc = function() {
            console.log(this) // 2번, 3번
        }
        innerFunc();

        const obj2 = {
            innerMethod: innerFunc
        };
        obj2.innerMethod()
    }
}

obj1.outer()

정리하자면 객체 obj1에는 outer라는 프로퍼티 있고 그 프로퍼티 안에서 1번 console.log(this)를 호출한다. 그 안에는 innerFunc 함수와 obj2가 있는데 innerFunc에서 2번, 3번인 console.log(this)를 호출한다. obj2의 메서드로 innerMethod가 있고 innerFunc를 할당했다. 그리고 obj2.innerMethod()를 호출한다. 함수 외부에서 obj1.outer()를 했을 때 각 this의 값이 무엇인지 고민해보자! (내가)

코드 흐름

outer라는 프로퍼티에 익명함수 연결한 객체 생성하고 변수 obj1에 할당 -> obj1.outer() 실행 -> 호이스팅 -> outer는 메서드로 호출된 것이므로 (1)의 this는 obj1에 바인딩 -> 호이스팅된 변수 innerFunc는 outer 스코프에서만 접근 가능한 지역변수 -> innerFunc() 호출 -> 이때 함수는 함수로서 호출된 것이므로 (2) this는 전역 객체를 가리킨다 -> 호이스팅된 obj2 역시 outer 스코프에서만 접근 가능, obj2에는 innerMethod가 있고 변수 innerFunc에 연결된 익명함수가 연결됨 -> obj2.innerMethod() 실행 -> 이때 함수는 메서드로 호출됨 그러므로 (3) this는 obj2다.

즉, this의 바인딩은 항수를 실행하는 당시의 주변 환경, 메서드 내부인지 함수 내부인지는 중요하지 않고 어떻게 호출되었는지가 관건이다.

메서드 내부 함수에서 this 우회하기 (심화)

const obj = {
    outer: function() {
        console.log(this)
        const innerFunc1 = function() {
            console.log(this)
        }
        innerFunc1()

        let self = this;
        const innerFunc2 = function() {
            console.log(self)
        }
        innerFunc2()
    }
}
obj.outer()

위 코드에서 innerFunc1 내부 this는 전역 객체를 가리킨다. 하지만 outer 스코프에서 self라는 변수에 this를 지정한 상태에서 innerFunc2 를 호출하면 self에 객체 obj2가 출력된다.

this를 바인딩하지 않는 함수 (심화)

const obj = {
    outer: function() {
        console.log(this)  // { obj: f}
        const innerFunc = () => {
            console.log(this) // { obj :f}
        }
        innerFunc()
    }
}
obj.outer()

함수 내부에서 this가 전역 객체를 바라보는 문제를 보완하기 위해 화살표 함수에는 this가 바인딩 되지 않게 하였다. 화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정이 빠져 상위 스코프에서 this를 그대로 사용할 수 있다.

콜백 함수에서 this (심화)

setTimeout(function() {
    console.log(this)  // (1)
}, 300)

[1, 2, 3, 4, 5].forEach(function(x) {
    console.log(this, x)  // (2)
})

document.body.innerHTML += '<button id="a">클릭</button>'

document.body.querySelector("#a").addeventListener("click", function(e) {
    console.log(this, e)  // (3)
})

setTimeout과 forEach에서 this는 전역 객체다. 한편 (3)의 addeventListner 메서드는 콜백함수를 호출할 때 자신의 this를 상속한다.

생성자와 this

new를 이용하여 생성자를 호출하면 빈 객체를 만들고 그 빈 객체 안에 this가 존재한다. this는 생성자를 통해 만들어진 객체를 가리킨다.

let funcThis = null; 
 
function Func(){
    funcThis = this;
}

const obj1 = Func();
if(funcThis === window){
   console.log('window'); // window
}
 
const obj2 = new Func();
if(funcThis === obj2){
    console.log("obj2");  //obj2
}

함수 Func()에서 지역 변수로 funcThis = this라고 정의했다. 그리고 객체 obj1에 Func()를 할당하고 obj 실행하면 Func() 함수 안의 fucnThis 는 let /const / var가 없기 때문에 전역변수인 null을 가리킨다. 즉, window를 가리킨다.

객체로서 함수


let obj = {}
let person = {}

function func(){
  switch(this){
    case obj:
      console.log("obj")
      break;
    case person:
      console.log("person")  
      break;
    case window:
      console.log("window")
      break;
  }
}

func()  // window
func.apply(obj) // obj

func()을 호출하면 함수로서 호출했기 때문에 this는 전역객체를 가리킨다. 하지만 apply 메서드의 obj를 인자로 넣으면 this가 obj를 가리킨다.

심화

call 메서드

Function.prototype.call(thisArg[,arg1[, arg2], ...]]])

call 메서드는 호출 주체인 함수를 즉시 실행하는 명령이다. 첫 번째 인자를 this로 바인딩 이후 인자는 호출 함수의 매개변수다. call 매서드를 이용하면 객체를 this로 지정할 수 있다.

const func = function(a, b,c){
  console.log(this, a, b, c)
}

func(1, 2, 3) // Window{...} 1 2 3 
func.call({x:1},4,5,6) // {x:1} 4 5 6 

call을 이용해 {x:1}을 this로 지정할 수 있다.

apply 메서드

Function.prototype.apply(thisArg[, argsArray])

apply는 call과 동일한 기능이다. 하지만 두 번째 인자로 배열을 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다.

const func = function(a, b, c) {
    console.log(this, a, b, c) // {x:1} 4 5 6
}

func.apply({ x: 1 }, [4, 5, 6])

const obj = {
    a: 1,
    method: function(x, y) {
        console.log(this.a, x, y)
    }
}
obj.method.apply({ a: 4 }, [5, 6]) //4 5 6

apply 와 call 활용

여기부터 더 공부하기

출처 : 생활코딩, 코어 자바스크립트

profile
프론트엔드 개발

0개의 댓글