loading

JS - javascript 에서의 this 를 정확히 알아보자 

첫번째(1) 부터 보는걸 권장한다.

 

JS - javascript 에서의 this 를 정확히 알아보자 링크

  1. JS - javascript 에서의 this 를 정확히 알아보자 - 1(기본 바인딩)
  2. JS - javascript 에서의 this 를 정확히 알아보자 - 2(암시적 바인딩)
  3. JS - javascript 에서의 this 를 정확히 알아보자 - 3(명시적 바인딩)
  4. JS - javascript 에서의 this 를 정확히 알아보자 - 4(new 바인딩)
  5. JS - javascript 에서의 this 를 정확히 알아보자 - 5(4가지 규칙 우선순위)
  6. JS - javascript 에서의 this 를 정확히 알아보자 - 6(바인딩 규칙 예외)

 

 

 

이전 글에선 기본 바인딩에 대해서 알아봤었는데 요번 글은 암시적 바인딩에 관련해서 글을 남겨본다.

이전 글에서 this가 어디를 참조하는지 알려면 호출 사이트(Call-Site)를 알아야 한다고 했다. 

이 호출 사이트를 안다는 가정하에 시작하겠다.

 

이전 글을 보고 싶다면 https://knowing-passion.tistory.com/32 

 

이 암시적 바인딩이 어떤 것인지 또 암시적 바인딩이 되면 this가 어떻게 참조되는지 알아보자.

 

예)

function A() {
	// 호출된 스택 : A
    	// 호출된 사이트 : 호출된 사이트는 obj객체의 프로퍼티 fn
    console.log( this.a );
}

var obj = {
    a: 2,
    fn: A // <-- obj 객체가 A라는 함수를 참조하고 있으므로 obj.fn에 대한 호출 사이트와 동일
};

obj.fn(); //  obj.fn에 대한 호출 사이트(A 라는 함수를 참조하고 있으므로 A에 대한 호출 사이트)

 

이 소스코드를 보자.

obj 객체에 fn 프로퍼티의 값이 A라는 함수가 참조가 되어있다. 여기에 주목해야 한다.

이런 경우를 obj 객체가 함수 참조를 '소유' 하거나 '포함' 한다고 할 수 있다.

 

함수 호출은 obj.fn()이라는 곳에서 시작하고 있다. 

fn은 무엇을 참조하는가?

A라는 함수를 참조하고 있다. 그럼 호출 사이트는 무엇인가?

 

소스코드 주석에서 글을 달아놨지만 다시 설명하자면,

obj.fn() 함수를 실행하는 게 obj 객체 안에 있는 fn 프로퍼티의 참조값인

A함수를 실행하는 거랑 같아서 A 함수의 this가 참조하는 건 obj 객체이다.

그러므로 this.a의 값은? 2가 나온다.

 

다시 강조해서 말하지만 여기서 중요한 건 호출 사이트다.

어디에서 어느 시점에서 A라는 함수가 실행이 되었는지

호출 사이트를 보면 A라는 함수의 this가 어디 부분을 참조하고 있는지 알 수 있다.

 

이미지로 설명하자면

 

그럼 A 함수 안에서의 this가 전역에 선언된 obj 객체와 같다고 이미지 설명에서 얘기했다.

그럼 이게 진짜 같은지 확인을 해봐야지 않겠나?

 

function A() {
    console.log( this === obj ); // 값이 true 나오는지 테스트 해보자
}


var obj = {
    a: 2,
    fn: A
};

obj.fn();

위 소스 실행 결과는? true로 나온다.

즉, A 안에 this와 전역에 선언된 obj가 같다는 말이다.

그래서 A안에 this.a 는 obj.a라는 의미인 거다.

 

자 그럼 여기서 무엇이 암시적인 바인딩이 된 걸까?

 

obj.fn() 실행 부분을 보자.

이 함수 실행은 단순 A() 함수를 실행하는 거와 같다.

하지만 obj 객체에 fn 프로퍼티가 A라는 함수를 참조된 걸 실행한다.

 

이렇게 함으로써 발생된 무엇인가? 를 생각해보면 아까도 설명했듯이 A라는 함수가 호출되는 시점이

obj 객체 안에 있는 fn 프로퍼티에서 이니깐 A함수 안에 있는 이 this 가 obj를 참조하는,

 

즉 코드상에서 A함수 안에 있는 이 this가 전역에 선언된 obj 객체를 참조하라고 코드를 짠 적도 없고,

실행하라는 의미의 코드도 없다.

 

하지만 A 함수 안에 이 this는 암시적으로 obj를 참조하고 있다.

이게 암시적으로 일어난 바인딩이다. 

 

자 헷갈릴 수도 있으니 이제 다시 한번 다른 방법으로 예제를 봐 보자.

기본 바인딩이랑 , 암시적 바인딩이랑 비교를 해보자.

 

var a = 100;

function A() {
    console.log(this.a);
}

var obj = {
    a: 2,
    fn: A
};

obj.fn(); // 암시적 바인딩

A(); // 기본 바인딩

위 코드를 복사하여 테스트를 진행해보자.

obj.fn(); 과 A(); 실행 부분을 하나씩 주석 처리해서 번갈아 가면서 무엇이 출력이 되는지 확인해보자.

 

결과는 어떠한가?

 

obj.fn(); 함수를 주석처리를 하고 진행하자.

A(); 함수는 이전 글에서 설명했듯이 기본 바인딩이다.

 

호출 사이트가 전역이고 이때의 A 함수 안에 this가 참조하고 있는 건

전역 개체 이므로 this.a의 결과 값은 100으로 나온다.

 

그리고 A(); 함수를 주석처리를 진행하자.

obj.fn(); 함수를 실행해 보면 2의 결괏값을 얻을 수 있다.

 

 

 

이번엔 암시적으로 바인딩을 잃어버리는 경우를 봐보자.

function A() {
    console.log( this.a );
}

var obj2 = {
    a: 42,
    fn: A
};

var obj1 = {
    a: 2,
    obj2: obj2
};

obj1.obj2.fn(); // 42

 

이 경우를 보면 obj1 객체가 obj2를 참조하고 있고,

obj2의 fn프로퍼티의 값 A 함수를 실행을 하고 있다.

이 경우 함수 A의 this.a 값은 42이다.

 

어느 부분에서 바인딩을 잃어버렸냐면 obj1 객체가 obj2 부분을 참조하면서 잃어버렸다.

만약 obj1이 없고 기존과 동일하게 obj2.fn();

이렇게 호출을 했다면 A함수 안에 있는 this는 obj2를 참조를 했을 거다.

 

하지만 obj1 객체가 obj2 객체를 참조하고 obj2 객체에서 fn 프로퍼티의 값인 A 함수를 실행함으로써,

호출 사이트는 obj2의 fn 프로퍼티가 된다.

 

그러므로 obj1 객체가 obj2 객체를 참조하는 순간 A함수 안에 있는

this의 객체는 obj1을 참조하고 있다가 obj2로 바뀐 거다. 

 

이런 경우가 암시적으로 this의 참조가 바뀐 거다. 무엇을 통해서?

obj1 객체가 obj2 객체를 참조하면서.... 

 

그래서 결과 값은 2가 아닌 42가 나온 것...

 

 

자 이번엔 헷갈릴 수 있는 예제를 봐 보자.

function A() {
    console.log( this.a );
}

var obj = {
    a: 2,
    fn: A
};

var fnG = obj.fn;

var a = "안녕하세요"

fnG();

// console.log( fnG === A); // 궁금하다면 주석을 풀어서 테스트 진행

 

이 코드의 경우는 this.a의 결괏값은 무엇이 나올까?

2가 나올까?, 아니면 string 값인 '안녕하세요'가 찍힐까?

 

답은 '안녕하세요'라는 string 값이 찍힌다. 왜 일까? 

가장 중요한 건 호출 사이트라고 했다. 이 것만 잘 생각하면 답은 나오기 쉽다.

 

전역 변수 fnG는 obj.fn이라는 함수를 참조하고 있다.

그리고 실행 함수는 전역에서 fnG(); 이렇게 함수를 호출하고 있다.

 

obj.fn을 그냥 참조만 하고 있는 거지 obj.fn() <-- 이것처럼 실행을 하고 있는 게 아니기 때문에

이것은 암시적 바인딩이 아닌, 기본 바인딩에 속한다.

 

위 코드에서 암시적 바인딩처럼 되기 위해선

obj.fn(); 이렇게 실행했다면 암시적 바인딩이 되었을 거고

 

fnG(); 는 전역에 있는 fng라는 변수에 obj.fn을 그냥 참조만 하고 있을 뿐

호출 사이트 자체는 전역이기 때문에 기본 바인딩에 속한 것.

 

저걸 확인해 보고 싶으면 fnG와 A 함수가 같은지 위 코드에서

console.log( fnG === A) 이 부분을 주석을 풀어서 테스트를 해보면 안다.

결과는 true로 나온다. 결국 fnG와 A는 같다는 것. 

 

 

이번에도 헷갈릴 수 있는 경우를 보자

function A() {
    console.log( this.a );
}

function B(fn) {
    // fn은 objFn프로퍼티의 값 A 함수를 참조.

    fn(); // 호출 사이트
}

var obj = {
    a: 2,
    objFn: A
};

var a = "안녕하세요.";

B( obj.objFn );

 

이 경우는 결괏값이 무엇이 나오겠는가?

이것도 마찬가지로 this.a 는 '안녕하세요' 값을 찍는다.

위 예제와 비슷하다.

이것도 기본 바인딩에 속한다.

 

하나씩 살펴보자.

B함수를 실행할 때 매개변수로 obj.objFn이라는 그냥 참조된 함수를 넘기는 것뿐이다.

 

그럼 B 함수의 매개변수를 받을 fn 은 말 그대로 obj 객체의 objFn 프로퍼티의 값인 A 함수를

받은 것뿐이고, 이렇게 obj 객체의 참조인 objFn의 프로퍼티의 값인 A 함수를 B 함수 내부에서 실행할 뿐이다.

 

결국 호출 사이트를 따지고 보면 B 함수 내부의 fn() 이 실행 시점이 호출 사이트이다.

 

위 예제를 잠깐 돕기 위해서 이전 글에서 호출 사이트 설명할 때 나온 코드이지만

다시 설명 차원에서 아래 코드를 보면

 

function A(){
  // 호출된 스택 : A
  // 호출된 사이트는 전역범위에 있음
  B(); // <-- B에 대한 호출 사이트
}

function B(){
  // 호출된 스택 A -> B
  // 호출된 사이트는 A
  C(); // <-- C에 대한 호출 사이트
}

function C(){
  // 호출된 스택 A -> B -> C
  // 호출된 사이트는 B
  console.log(this.a); // this는 전역개체를 참조
}

var a = 'ok';

A(); // <-- A에 대한 호출 사이트

 

A(); 실행 결괏값은 ok, 결국 위와 같은 형태의 실행은 기본 바인딩이다.

암시적으로나, 명시적으로나, 참조된 게 아무것도 없는 그냥 전역 개체에서

 

실행한 함수 A() ->

전역 개체에 존재한 함수 A안에서 B()를 실행 ->

전역 개체에 존재한 함수 B안에서 C()를 실행 ->

전역 개체에 존재한 함수 C 안에 this의 참조는 전역 개체 이므로

C 함수 안에 this.a 는 당연 전역 변수 a에 대한 참조를 하게 된다.

 

 

이제 마지막 예제이다. 헷갈리는 다른 경우를 보자.

function A() {
    console.log( this.a );
}

var obj = {
    a: 2,
    fn: A
};

var a = "안녕하세요";

setTimeout( obj.fn, 1000 );

 

이 경우는 어떻게 나올 것 인가? 이것도 결괏값은 '안녕하세요'라고 나온다.

 

이 예제는 이전 예제와 동일한 예제이다. 아래 코드를 풀어서 다시 작성해 보겠다.

function A() {
    console.log( this.a );
}

var obj = {
    a: 2,
    fn: A
};

var a = "안녕하세요";

setTimeout( obj.fn, 1000 );

/*
function setTimeout(fn, delay){
  fn();
}
*/

 

보이는가? setTimeout 함수를 주석 처리된 부분처럼 풀면 저리 되어 잇다.

위 예제와 동일한 패턴 아닌가?

 

결국 setTimeout 이란 함수 내부에서 fn() 함수를 실행하고 있으므로

이것도 호출된 사이트가 전역 개체 범위에 있으므로 A 함수 안에 this는 전역을 참조하고 있고,

this.a 는 '안녕하세요'가 찍힌다.

반응형

+ Recent posts