[JS] 자바스크립트 실행컨텍스트 Execution Context (3)

2023. 4. 13. 01:57개발/HTML+CSS+JS

자바스크립트 this

보통 객체지향언어에서 this는 클래스를 정의한 인스턴스 그 잡채를 의미한다!
그런데 자바스크립트의 this는 그때그때 상황별로 의미하는 바가 달라진다.

(엉뚱한 JS엔진이다!)

우리.. this 는요..

  • 실행컨텍스트가 생성(Create)될 때 결정(bind)된다.
  • 기본적으로 전역공간에서는 런타임환경에 맞는 전역객체를 가르킨다.

함수와 메서드의 비교

자바스크립트의 this는 상황별로 달라지게 되는데 이를 이해하기 위해서 함수와 메서드를 구분해서 이해할 필요가 있다.

  • 공통점 : 기능을 수행한다.
  • 차이점 : 함수는 독립적이지만 메서드는 반드시 객체에 종속되어 동작한다.

함수와 메서드의 this바인딩 비교

function logThis (lineNum){
        console.log("call Line : "+lineNum, Object.keys(this)[0])
}
// 걍 호출
logThis(5)

// 다른 함수 안에서 호출
function testThis(x){
    logThis(9)
}

// 객체 안에서 호출
var includeFunObj = {
    callFun : testThis
}
includeFunObj.callFun(16)


//객체 안에서 호출2
var includeFunObj2 = {
    callFun : 
    function(lineNum){
        console.log("includeFunObj2 call Line : "+lineNum, Object.keys(this)[0])
        var innerFun = 
            function(lineNum){
                console.log("includeFunObj2 call Line : "+lineNum, Object.keys(this)[0])
            }
        innerFun(28)
        var includeFunObj2_1 = {
            callFun : innerFun
        };
        includeFunObj2_1.callFun(32)
    }
}

includeFunObj2.callFun(36);

 

출력결과를 살펴보자.

call Line : 5 global
call Line : 9 global
includeFunObj2 call Line : 36 callFun
includeFunObj2 call Line : 28 global
includeFunObj2 call Line : 32 callFun

 

자바스크립트에서 this는 호출을 누가 했는지! 정보를 가지고 있다.
함수는 독립적으로 실행이 가능하기 때문에 누가 실행했는지 this를 살펴보면 전역객체가 나오게 된다.
함수 자체로 실행될 때 this가 전역객체라는 것을 이해했다면 위 코드와 출력값을 보고 의문이 생길 수 있다.

includeFunObj2 call Line : 28 global

왜 객체 안에서 함수를 호출 했는데 this가 전역객체를 바라볼까?

 

분명히 호출한 놈은.. 객체 includeFunObj2 인데 떡하니 전역객체를 찍고 있다.
아무리 객체 안이더라도 독립적으로 innerFun(28)으로 함수룰 호출 하고 있기 때문이다.

 

콜백함수와 생성자함수에서 this 바인딩

콜백함수(콜백도 함수도 함수다!)

console.log(`::::::: 콜백함수에서 this (콜백함수도 함수다) :::::::`)
var arr = ['a','c','b','d'].sort(function(a,b){
    console.log("콜백 : ", Object.keys(this)[0])
    return a-b
})

 

::::::: 콜백함수에서 this (콜백함수도 함수다) :::::::
콜백 : global
콜백 : global
콜백 : global

콜백함수도 함수로 선언되었기 때문에 얄짤없이 전역객체를 바라보게된다.

 

생성자함수에서 this

console.log(`::::::::::::::: 생성자 함수에서 this :::::::::::::::`)
var Book = function(title, writer){
    this.title = title
    this.writer = writer
    console.log("생성자 : ",this)
}
// 생성자 함수에서 디스는 매번 생성한 그, 뱉어낼 그놈을 가르킨다.....
var braveNewWorld = new Book('멋진신세계', '올더스헉슬리')
var animalFarm = new Book('동물농장', '조지오웰')

::::::::::::::: 생성자 함수에서 this :::::::::::::::
생성자 : Book { title: '멋진신세계', writer: '올더스헉슬리' }
생성자 : Book { title: '동물농장', writer: '조지오웰' }

생성자 함수는 매번 생성한 그 객체를 바라보게 된다.

 

this 우회 방법

자바스크립트 엔진이 그렇다니까.. 그렇겠지만...

아무리 그래도 코드 흐름이라는게 있는데..함수를 선언할때마다 걍 디스가 전역을 냅다 바라보는게 말이가..글이가,,

그래서 우회해서 this가 코드 흐름에 맡게 본인을 호출한 놈의 정보를 가지고 있도록 해보자!

1.  함수내부스코프 바인딩

걍 냅다 때려 박아준다

console.log(`::::::: this 우회방법 1 함수내부스코프 바인딩 :::::::`)
// this 우회방법 직접 걍 셀프에 바인딩 해버림;
let includeFunObj = {
    funcLv1 : function(num){
        // funcLv1 메서드 내부의 펑션
        // this - funcLv1
        console.log("call Line : "+num, Object.keys(this)[0])

        // funcLv1메서드 내부 펑션에서 새로 선언한 펑션1
        // this - 전역
        var funcLv2_1 = function(num){
            console.log("call Line : "+num, Object.keys(this)[0])       
        }
        funcLv2_1(8)

        var self = this;
        //funcLv1메서드 내부 펑션에서 새로 선언한 펑션2
        var funLv2_2 = function(num){
            console.log("call Line : "+num, Object.keys(self)[0])       
        }
        funLv2_2(14)
    }

}

includeFunObj.funcLv1(17)

 

call Line : 17 funcLv1
call Line : 8 global
call Line : 14 funcLv1

동일하게 객체 안에서 함수를 선언한 뒤 this를 확인하는데
Line 8에서 호출해서 찍힌 로그는 전역객체를
Line 14에서 호출해서 찍힌 로그는 객체안의 funcLv1를 콘솔에 찍고 있다.
이는 객체 내부에서 this를 self 변수에 할당해준 다음 self를 넘겨주었기 때문이다.

 

2. this를 바인딩 하지 않는 함수인 화살표 함수를 씀

Function으로 함수를 만들게 되면 바로 전역 컨텍스트로 this가 바인딩 되고 기존 흐름의 this는 걍 초기화 되버린다.
근데 화살표를쓴다? this가 바인딩 되지 않는다. 왜냐하면 화살표함수는 this를 바인딩 하는 과정 자체가 없기 때문이다.
this를 기존 흐름대로 계속 쭈쭉쭉쭈국 유지할 수 있다.

console.log(`:::::::::::: this 우회방법 2 화살표함수 :::::::::::::`)
var includeFunObj2 = {
    funcLv1 : function(num){
        console.log("call Line : "+num, Object.keys(this)[0])
        var funcLv2_1 = (num) =>{
            console.log("call Line : "+num, Object.keys(this)[0])
        }
        funcLv2_1(46)
    }
}
includeFunObj2.funcLv1(49)

 

call Line : 49 funcLv1
call Line : 46 funcLv1

출력결과는 계속해서 정상적으로 흐름상의 this를 유지하는 것을 확인할 수 있다.

 

this 명시적 바인딩

명시적 this bind
디스를 잃어버려서 전역을 바라보고있는 상황을 연출하고 해당 상황에서 어떻게 명시적 바인딩을 통해 디스를 제대로 엮어줄지 알아보자.

 

call 메서드, apply 메서드

call

var testFunc = function(a,b,c){
    console.log("testFunc >",Object.keys(this)[0],`a:${a}, b:${b}, c:${c}`)
    console.log(this.objX)
}

testFunc(1,2,3)

// 명시적 바인딩
// call 메서드를 사용해서 명시적으로 objX 객체가 이 테스트펑션을 호출한거처럼 바인딩 해줄수 잇따.
testFunc.call({objX:'나는 객체 X! testFun을 호출한다. '}, 2,3,4)

 

testFunc > global a:1, b:2, c:3
undefined
testFunc > objX a:2, b:3, c:4
나는 객체 X! testFun을 호출한다.

this 가 반드시 전역객체를 바라볼때 뿐만 아니라 바인딩이 의도되로 되어있는 경우라도 기존의 바인딩된 this를 제거하고 새로 바인딩 할 수 있다.

 

var containFunObj = {
    say : 'hello~~',
    doing : function (x,y){
        console.log(this)
        console.log(this.say," 더하기 합니다!", x+y)
    }
}

containFunObj.doing(2,2)
// 당연히 이친구는 this로 containFunObj 객체를 바라보게 된다.
// 이렇게 당연히 연결되어있는 바인딩도 다 깨버리고 새로 엮어줄수 있음
containFunObj.doing.call({say:'hi~~~~'},2,8)

{ say: 'hello'~~~, doing: [Function: doing] } hello~~~ 더하기 합니다! 4
{ say: 'hi~~~~~'} hi 더하기 합니다! 10

이렇게 원하는 객체로 변경이 가능하다.

 

apply

call 메서드랑 동일한 기능을 하는데 약간의 문법만 약간 다름.
위 코드에선 이렇게 사용할 수 있다.

containFunObj.doing.apply({say:'안녕하쇼!~'},[1,1])

매개변수를 [] 배열로 감싸주면 된다.

 

bind 메서드

  • 즉시실행 함수가 아님
  • this를 바인딩 하는 메서드
  • 함수를 실행하거나 콜하기전 디스를 미리 바인딩 할 수 있다.(즉시 실행함수가 아니므로!)
  • 부분적용 함수다
var func = function(temp){
    console.log(Object.keys(this)[0], temp)
}
func("안녕하세요")

// bind메서드의 주의점은 결과물로 새로운 함수를 리턴하기 때문에 리턴한 함수를 받고
// 해당 함수를 실행해줘야 한다는 점이다!
var bindFunc = func.bind({objX:'이 objX에 디스를 엮겠습니다.'})
bindFunc("안녕하쇼!")

var bindFuncA = func.bind({objY:"디스바인딩"}, "안녕하시렵니까")
bindFuncA()