rhanziy

javascript. Arrow Function의 this 바인딩(펌) 본문

Html_css_js

javascript. Arrow Function의 this 바인딩(펌)

rhanziy 2022. 2. 23. 16:30

일반 함수는 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니고, 어떻게 호출 되었는지에 따라 this 에 바인딩할 객체가 동적으로 결정되었다.

하지만 ES6의 Arrow Function은 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정된다.

화살표 함수는 자신의 this가 없다. 대신 화살표 함수가 둘러싸는 렉시컬 범위(lexical scope)의 this가 사용된다. 즉, 언제나 화살표 함수의 상위 스코프, 화살표 함수가 선언된 스코프의 this를 가리킨다.

 

const name = 'rhan';

const rhanyi = {
  name: 'yi',
  getName: function () {
    console.log(this.name);
  },
};

rhanyi.getName();

위의 코드를 실행하면 console창에는 어떤이름이 찍히게 될까?

  • 정답은 'yi'이다.
  • 객체의 메서드에서의 this는 자신을 호출한 객체 바인딩되기 때문이다.

 

그렇다면 아래의 코드에서 console창에 나오는 이름은 뭘까?

const name = 'rhan';

const rhanyi = {
	name: 'rhan',
    getName : () => console.log(this.name),
};

rhanyi.getName();

저 위의 코드와 같이 yi가 출력될까?

답은 아니다. 이 포스팅의 맨 위에서 말했듯이 ArrowFunction의 this는 상위 스코프의 this(여기서는 rhanyi 객체의 상위 스코프인 window)를 가리킨다!

즉 'rhan'이 출력된다!

 

마지막으로 이 코드에서는 어떤 name이 출력될까?

const name = 'rhan';

const rhanyi = {
  name: 'yi',
  getName: function () {
    const name = 'hi';
    (() => {
      console.log(this.name);
    })();
  },
};

rhanyi.getName();

바로 getName()메서드의 상위 스코프인 rhanyi객체의 this에 바인딩되서 'yi'이라는 이름을 출력한다.

 


바인딩 우선순위

  • new 바인딩>=명시적 바인딩>암시적 바인딩>=기본 바인딩

new 바인딩은 실질적으로 새로운 객체를 생성하고 그 객체를 (명시적) 바인딩을 한 것이기때문에 new 바인딩>=명시적 바인딩이라고 하였고, 기본 바인딩은 암시적 바인딩의 일부여서 암시적 바인딩>=기본 바인딩이라고 표현하였다.

명시적 바인딩

함수(함수 객체)는 call, apply, bind 메소드를 가진다.이 함수들을 이용해서 this는 이걸로 바인딩 해줘!! 하고 명시할 수 있다.

function test() {
   console.log(this);
}

const obj = { name: "noma" };
test.call(obj); // { name: 'noma' }
test.call("원시 네이티브 자료들은 wrapping 된다."); // [String: '원시 네이티브 자료들은 wrapping 된다.']

그냥 test를 호출했으면 this는 Window를 가리켰겠지만, call 함수로 obj를 전달함으로써 this는 obj를 가리키도록 고정되었다.

call, apply, bind 이 세 함수는 공통적으로 첫번째 인자로 넘겨주는 것을 this로 바인딩 하지만, 조금씩 차이가 있다.

1. call() vs. apply()

function say(msg1,msg2){
    console.log(`Hello, I'm ${this.name}.\n${msg1}\n${msg2}`);
}
const me={name:'noma'};
say.call(me,'Nice to meet you!','Bye💩'); 
say.apply(me,['Nice to meet you!','Bye💩']);

/*
Hello, I'm noma.
Nice to meet you!
Bye💩  
*/

둘다 this를 바인딩하면서 함수를 호출한다. call과 apply의 유일한 차이점은, 첫 번째 인자(this를 대체할 값)를 제외하고 실제 함수를 실행시키는데 필요한 인자를 입력하는 방식에 있다. call()은 이를 하나씩 받고, apply()는 배열로 받는다.

2. bind()

bind는 call, apply와 달리 함수를 호출하지 않고, 인자로 받은 객체를 this로 하는 새로운 함수를 리턴한다. (두 번째 인자는 call과 같이 하나씩 받는다.)

say.bind(me,'Nice to meet you!','Bye💩')();

함수를 리턴하기때문에 바로 ()를 이용해 호출할 수도 있다.

 

화살표 함수(Arrow function)

화살표 함수는 자신의 this가 없다. 대신 화살표 함수가 둘러싸는 렉시컬 범위(lexical scope)의 this가 사용된다. 즉, 언제나 화살표 함수의 상위 스코프, 화살표 함수가 선언된 스코프의 this를 가리킨다.

아래 코드의 경우 내부 함수에서 this가 전역 객체인 Window를 가리키는 바람에 의도와는 다른 결과가 나왔다.

const Person=function(name){
    this.name=name;
    this.hello=function(){
    	console.log(this); // Person {name: 'noma', hello: ƒ}
        setTimeout(function(){
            console.log(this); // Window
            console.log(`Hello, I'm ${this.name}!`); // Hello, I'm !
        },1000);
    };
};
const her=new Person('noma');
her.hello(); 

이럴 때 화살표 함수를 사용하도록 바꾸면 해당 화살표 함수의 상위 스코프의 this 즉, her 객체가 되므로 제대로 된 결과가 나오는 걸 볼 수 있다.

const Person=function(name){
    this.name=name;
    this.hello=function(){
    	console.log(this); // Person {name: 'noma', hello: ƒ}
        setTimeout(()=>{
        	console.log(this); // Person {name: 'noma', hello: ƒ}
            console.log(`Hello, I'm ${this.name}!`); // Hello, I'm noma!
        },1000);
    };
};
const her=new Person('noma');
her.hello(); 

+ 바인딩 이슈엔 화살표 함수 !

화살표 함수가 나오기 전까지는, 모든 새로운 함수는 어떻게 그 함수가 호출되는지에 따라 자신의 this 값을 정의했다.

  • 함수가 생성자인 경우 -> 새로운 객체
  • 엄격 모드에서 함수를 호출할 경우 -> undefined
  • 함수가 '객체 메서드'로서 호출된 경우 -> 컨텍스트 객체
  • 등등

이는 객체지향 프로그래밍시 별로 좋지 않다. 아래 예제를 보자.

class Person{
    constructor(name,age){
        this.name=name;
        this.age=age;
        this.mouth=document.querySelector('.mouth');
        this.mouth.addEventListener('click',this.onClick); //⛔
    }
    onClick(){
        console.log(this);
        alert(`Hi, I'm ${this.name}.`); //⛔
    }
}
const me=new Person('noma',24);
// class가 mouth인 HTML요소를 클릭하면 'Hi, I'm undefined.'이 alert 되는 것을 확인할 수 있다.

함수 호출시 lexical environment라는 것이 생성되는 데 이 안에는 호출에 필요한 정보들이 함께 들어 있다. 그런데 함수 내부에서 this를 사용하는 클래스의 멤버 함수를 명시적 바인딩을 하지 않고 어딘가에 이 함수의 참조를 전달하게 되면 this가 해당 객체를 가지고 있지 않는 바인딩 이슈가 발생하게 된다.

따라서 멤버 함수를 다른 콜백으로 전달할 때 클래스 정보가 같이 전달되도록 하기 위해선 바인딩을 해줘야 한다. (멤버 함수 내에서 this를 사용하지 않으면 상관 없음)

여기서 방법은 크게 3가지다.

  1. 명시적 바인딩

이는 arrow function 도입 이전에 쓰던 방법으로 비추천된다.

constructor(){
   this.onClick=this.onClick.bind(this);
   this.field.addEventListener('click',this.onClick);
}
  1. Arrow Function 사용 (많이 사용됨)

위에서 언급했듯이 화살표 함수를 사용하면 해당 함수의 상위 스코프의 this가 바인딩된다고 했다. 여기서는 원하던 대로 me 객체를 this로 고정시킨다.

constructor(){
   this.field.addEventListener('click',()=>this.onClick());
}
  1. 화살표 함수를 사용하는 멤버 변수로 만들기 (추천👍)
constructor(){
   this.field.addEventListener('click',this.onClick); 
}
onClick=()=>{
   console.log(this);
   alert(`Hi, I'm ${this.name}.`); 
}

그러면 최종적으로 class가 mouth인 HTML요소를 클릭하면 'Hi, I'm noma.'라고 alert 되는 것을 확인할 수 있다.

물론 명시적으로 bind 해서 코딩하는 것을 좋아하는 사람도 있지만, arrow function은 이러한 바인딩 이슈를 개선하고 좀더 짧고 간단한 함수 표현을 위해 나온 ES6 문법인만큼 이를 사용하는 것을 추천한다.

 

 

 

출처1: https://dkwjdi.tistory.com/193

출처2: https://velog.io/@wiostz98kr/JavaScript-this%EC%9D%98-4%EA%B0%80%EC%A7%80-%EB%B0%94%EC%9D%B8%EB%94%A9-%EC%BC%80%EC%9D%B4%EC%8A%A4%ED%99%94%EC%82%B4%ED%91%9C-%ED%95%A8%EC%88%98Arrow-Function

Comments