this에 관한 탐구

FGPRJS·2021년 10월 21일
0

this를 사용하면서 생각날 수 있는 방식에 대해 탐구한다


this


암시적인 바인딩

  • 함수 리턴의 객체 프로퍼티의 this
function makeUser(){
    return {
        name: "John",
        ref: this,
    }
}

let user = makeUser();//(global에 있는) makeUser();

console.log(user.ref.name); //Undefined (or Exception)

userref(this)는 무엇인가? -> 없음. 따라서 global

this호출 될 때 이미 결정되었다. userglobal에 있는 makeUser를 호출하였고, thisglobal이 된다.

다음을 참고하면,

const userFactory = {
    name: "userFactory",
    makeUser: function(){
        return {
            name: "John",
            ref: this,
        }
    }
}

let user = userFactory.makeUser(); 
//userFactory 안에 있는 makeUser();

console.log(user.ref.name); //userFactory

this의 대상을 찾아보았지만 대상이 없을 때 global(node.js)이나 window(browser)로 표시한다. use strict할 경우 undefined이다.

"use strict"

console.log((()=>{
    console.log(this);
})());
//{} 
//(처음의 console.log는 IIFE로부터 아무것도 리턴 안하는 객체를 받아
//비어있는 객체가 된다. 의미 없는 출력)

//undefined 
//(strict한 상태에서 this가 대상을 찾지 못하면
//window나 global이 아니라 undefined가 된다.

useruserFactory에서 호출한 makeUser()의 리턴을 받는다. this는 호출될 때 이미 결정되었다. userFactory에서 makeUser를 호출하였고, thisuserFactory가 된다.

  • IIFE의 this

IIFE는 외부로부터 격리되는

let user = (
    return {
        name: "John",
        ref: this
    }
})();

console.log(user.ref.name); 
//Undefined (or Exception)
console.log(user.indata); 
//{internal_name : "My Internal Name"}

userfunction makeUser() 의 결과물인 객체의 return 이다. makeUser IIFE의 결과인 user의 경우, this는 전역에 의해 호출된다. 따라서 global이나 window를 리턴한다(찾을 수 없음).
그 외에도, IIFE는 this바인딩 함수(call, apply, bind 등...)로 바인드하는것이 아니면, this는 항상 windowglobal이나 undefined가 된다.

const something = {
    name: "Something",
    makeUser:(function(){
        return {
            name:"John",
            ref:this
        }
    })()
}

console.log(something.makeUser.ref);
//undefined (or Exception)

somethingmakeUserthis없다. 따라서, windowglobal을 표시한다. strict의 경우 undefined.

IIFE에서의 this관련 Stackoverflow 링크

  • 화살표 함수의 this

화살표 함수 MDN문서

상기 링크에서 다음과 같이 기재되어 있다.

화살표 함수가 나오기 전까지는, 모든 새로운 함수는, 어떻게 그 함수가 호출되는지에 따라 자신의 this 값을 정의했습니다:
이 함수가 생성자인 경우는 새로운 객체
엄격 모드 함수 호출에서는 undefined
함수가 "객체 메서드"로서 호출된 경우 문맥 객체 등등
이는 객체 지향 스타일로 프로그래밍할 때 별로 좋지않습니다.

화살표 함수는 자신의 this가 없습니다.
대신 화살표 함수를 둘러싸는 렉시컬 범위(lexical scope)의 this가 사용됩니다;
화살표 함수는 일반 변수 조회 규칙(normal variable lookup rules)을 따릅니다.
때문에 현재 범위에서 존재하지 않는 this를 찾을 때, 화살표 함수는 바로 바깥 범위에서 this를 찾는것으로 검색을 끝내게 됩니다.

상기 기재된 렉시컬 범위컴파일레이션 과정 중 렉싱 타임렉서가 코드를 처리할 때 확정적으로 정의되는 스코프이다. 기존 함수의 this가 호출될 때 정해진다면, 화살표 함수의 this는 컴파일레이션 단계에서 정해진 렉시컬 범위를 참조하므로, 컴파일레이션 단계에서 결정되는거나 다름없다고 볼 수도 있다.

let obj = { // does not create a new scope
    i: 10,
    arrow: () => console.log(this.i, this),
    expression: function() {
        console.log( this.i, this)
    }
}
obj.arrow(); // prints undefined, Window {...} (or the global object)
obj.expression(); // prints 10, Object {...}
  • obj는 화살표 함수의 대상이 아니다. arrow함수의 렉시컬 범위의 바로 바깥은 global/window이다. 그러므로 arrow에서 표현하는 this없다.
  • objexpression에서 함수의 호출부는 obj이다. 따라서 this는 호출부인 obj가 된다.
class OBJ{
    constructor(){
        this.i =  10;
        this.arrow =  () => console.log(this.i, this);
        this.expression = function() {
            console.log( this.i, this);
        };
    }
};

let obj = new OBJ();

obj.arrow();// prints 10, Object {...}
obj.expression();// prints 10, Object {...}
  • objclass(사실, class는 객체지향프로그래밍 기준 언어의 Class추상화 패턴을 JS환경에서 어떻게든 비슷하게 사용할 수 있게만 만들어준 것이다.)를 구현한 객체이며 이곳의 this를 호출하면 this가 객체가 된 obj를 지칭하게 되며, this가 정확히 obj가 됨을 알 수 있다.

그 외의 방법으로 Babel 트랜스파일러와 파이어폭스 브라우저에서 분석한 소스의 결과를 비교해보는 것으로 차이점을 확인해본다.

let arrow = () => {
    console.log("arrow");
}

let expression = function(){
    console.log("expression");
}

다음 코드를 분석한 일부는 다음과 같다.

var arrow = function arrow() {
    console.log("arrow");
};

var expression = function expression() {
    console.log("expression");
};

this가 없어서 그런지 차이를 보이지 않는다. this가 없으면 Babel에서는 화살표함수나 무명함수나 거기서 거기로 판단한다.

하지만, this가 존재하는 다음 코드를 분석하면 차이가 발생하게 된다.

let arrow = () => {
    console.log(this);
}

let expression = function(){
    console.log(this);
}

분석한 결과는 다음과 같다.

var _this = this;

var arrow = function arrow() {
    console.log(_this);
};

var expression = function expression() {
    console.log(this);
};

화살표 함수는 자신을 감싸는 렉시컬 스코프의 this_this 변수를 통해 가져오며, 무명함수는 있는 그대로의 this를 사용함을 알 수 있다.


명시적인 바인딩

명시적인 바인딩은 암시적인 바인딩보다 우선순위가 높다.
우선순위가 높아서, this를 덮어쓸 수 있다.
화살표 함수에서는 쓸 수 없다.

  • call : 0번째 인자는 bind할 object, 그 다음 인자는 순서대로 함수의 인자로 하면서 수행한 결과를 리턴한다.
  • apply : 0번째 인자는 bind할 object, 그 다음 인자는 Array이어야 하며, 함수의 인자로 하면서 수행한 결과를 리턴한다.
function say_hello(){
    console.log("hello!", this.target," and ", arguments);
}

const GreetingFactory = {
    target: "GF",
    hello:say_hello
}

const bar1 = {
    target: "BAR1"
}

const bar2 = {
    target: "BAR2"
}

const bar3 = {
    target: "BAR3"
}

// this를 결정하면서, 함수를 수행한다.
say_hello.call(bar1, "bar1", "bar2", "bar3")
//hello! BAR1  and  [Arguments] { '0': 'bar1', '1': 'bar2', '2': 'bar3' }

// this를 결정하면서, 함수를 수행한다.
say_hello.apply(bar2, ["bar1", "bar2", "bar3"]);
//hello! BAR2  and  [Arguments] { '0': 'bar1', '1': 'bar2', '2': 'bar3' }

//GreetingFactory의 암시적인 바인딩을 수행하였으나,
//명시적인 바인딩으로 this를 바꾸어버렸다.
GreetingFactory.hello.call(bar3);
//hello! BAR3  and  [Arguments] {}

명시적인 바인딩 - 하드 바인딩

bind : this로 할 객체와 argument를 직접 바인딩 한다. 바인딩함수를 리턴한다.
화살표 함수에서는 쓸 수 없다.

function say_hello(){
    console.log("hello!", this.target," and ", arguments);
}

const bar1 = {
    target: "BAR1"
}

const bar2 = {
    target: "BAR2"
}

const GreetingFactory = {
    target: "GF",
  //bind는 this를 bar1로 `하드 바인딩`한 결과 함수를 리턴한다.
    hello:say_hello.bind(bar1, "foo1", "foo2")
}

//bar2를 call하면서 
GreetingFactory.hello.call(bar2);
// hello! BAR1  and  [Arguments] { '0': 'baz1', '1': 'baz2' }
  • bind 함수는 다음과 같이 사용할 수 있다.
function add_a_and_b (a,b){
    return a + b;
}

//a를 2로 정해놓은 함수가 된다.
const add_2_and_b = add_a_and_b.bind(null, 2);

console.log(add_2_and_b(3));

profile
FGPRJS

0개의 댓글