자바스크립트의 THIS

📝 TL;DR - this의 확정 규칙

이전 글에서 this의 기본 규칙에 대해 알아봤다. 그렇다면 동시에 여러개가 쓰였을 때 this의 우선순위는 어떻게 될까. 일단 결론은 아래와 같다.

1. new 로 함수를 호출(new 바인딩) 했는가? - 새로 생성된 객체가 this

var bar = new foo();

2. 명시적 바인딩이나 하드 바인딩으로 호출됐는가? - 명시적으로 지정된 객체가 this

var bar = foo.call(obj2);

3. 암시적 바인딩(함수의 콘텍스트) 형태로 호출했는가? - 콘텍스트가 this

var bar = obj1.foo();

4. 그 외의 경우 this는 기본값으로 적용 - 엄격모드는 undefined, 비엄격 모드는 전역객체

var bar = foo();


☕️ 우선순위를 비교해 보자

1. 명시적 바인딩 > 암시적 바인딩

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

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

var obj2 = {
  a: 3,
  foo: foo,
};

obj1.foo(); //2
obj2.foo(); //3
obj1.foo.call(obj2); //3
obj2.foo.call(obj1); //2
  • 결과를 보면 명시적으로 call 을 사용하여 바인딩 했을 때의 우선순위가 높다.

2. new바인딩 > 암시적 바인딩

function foo(something) {
  this.a = something;
}

var obj1 = {
  fooJ: foo,
};

var obj2 = {};

obj1.foo(2);
console.log(obj1.a); //2

obj1.foo.call(obj2, 3);
console.log(obj2.a); //3

var bar = new obj1.foo(4);
console.log(obj1.a); //2
console.log(bar.a); //4

3. new바인딩>명시적 바인딩

new 와 call, apply는 동시에 사용할 수 없으므로 하드 바인딩을 이용해 두 규칙 간 우선순위를 테스트 해보자

function foo(something) {
  this.a = something;
}
var obj1 = {};

//하드바인딩
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); //2

//new 로 바인딩
var baz = new bar(3);
console.log(obj1.a); //2
console.log(baz.a); //3
  1. barobj1에 하드 바인딩 되었는데, new bar(3) 실행 후에도 obj1.a의 값은 3으로 변하지 않았다.
  2. obj1에 하드 바인딩 된 bar() 호출은 new 로 오버라이드 할 수 있다.
  3. new가 적용되어 새로 만들어진 객체가 baz에 할당되고 실제 baz.a의 값은 3이 된다.
  • bind의 new 오버라이드를 가능케 하는 코드 (bind의 폴리필)
this instanceof FNOP && oThis ? this : oThis;

FNOP.prototype = this.prototype;
fBound.prototype = new FNOP();
  • 하드 바인딩 함수가 new 로 호출되어 this 가 새로 생성된 객체로 세팅됐는지 조사해보고 맞으면 하드 바인딩에 의한 this를 버리고 새로 생성된 this를 대신 사용한다.
  • this 하드 바인딩을 무시하는 함수를 생성하여 함수 인자를 전부 또는 일부만 미리 세팅해야 할 때 유용(커링의 일종이다)
    function foo(p1, p2) {
      this.val = p1 + p2;
    }
    // 'null'을 입력한 건 여기서 'this' 하드 바인딩은
    // 어차피 new 호출 시 오버라이드 되므로 신경 쓰지 않겠다는 의미다.
    var bar = foo.bind(null, 'p1');
    var baz = new bar('p2');
    baz.val; //p1p2

☕️ 바인딩 예외

1. this 무시

  • call, aply, bind 메서드 첫 번째 인자로 null 또는 undefined 를 넘기면 this 바인딩이 무시되고 기본 바인딩 규칙이 적용된다.

    function foo() {
      console.log(this.a);
    }
    
    var a = 2;
    foo.call(null); //2
  • apply는 함수 호출 시 다수의 인자를 배열 값으로 죽 펼쳐 보내는 용도로 자주 쓰이고(es6부터는 spread 연산자가 생겨서 그걸로 쓰기도 한다.)

  • bind도 유사한 방법으로 인자들을 커링하는 메서드로 많이 사용된다.

    function foo(a, b) {
      console.log('a:' + a + 'b:' + b);
    }
    // 인자들을 배열 형태로 죽 펼친다
    foo.apply(null, [2, 3]);
    
    // bind 로 커링한다
    var bar = foo.bind(null, 2);
    bar(3); //a:2, b:3
  • null을 넣는 이유는, apply와 bind 모두 첫 인자로 바인딩할 값을 정해야 하는데, 일종의 자리끼움(placeholder) 값으로 null정도의 값을 전달한다.

더 안전한 this

객체 내용이 하나도 없으면서 전혀 위임되지 않은 객체를 바인딩 한다.

function foo(a,b){
	console.log("a:"+ a + ", b" +b);
}

//DMZ 객체 생성
var ø = Object.create(null);

//인자들을 배열 형태로 쭉 펼친다.
foo.apply(ø,[2,3]); //a:2, b:3

//bind로 커링한다.
var bar = foo.bind(ø, 2);
bar(3); //a:2, b:3

더 안전하게 가고자 한다면 프로그램에서 부작용 100% 무관한 객체를 this로 바인딩 하는 방법이다.

간접 레퍼런스

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

var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };

o.foo(); //3
(p.foo = o.foo)(); //2

할당 표현식 p.foo = o.foo의 결과값은 원 함수의 레퍼런스이므로, 실제로 호출한 부분은 p.foo(), o.foo()가 아니고 foo()다... 그래서 기본 바인딩 규칙이 적용된다.

소프트 바인딩

호출시점에 this를 체크하는 부분에서 주어진 함수를 래핑하여 전역 객체나 undefined일 경우 미리 준비한 대체 기본객체로 세팅한다.

그 외의 경우 this는 손대지 않는다.

그리고 선택적인 커링(앞의 bind()부분 참고) 기능도 있다.

function foo(){
	console.log("name" + this.name);
}

var obj = {name:"obj"},
	obj2 = {name:"obj2"},
	obj3 = {name:"obj3"};

var fooOBJ = foo.softBind(obj);
fooOBJ(); //name : obj

obj2.foo = foo.softBind(obj);
obj2.foo(); //name: obj2

fooOBJ.call(obj3); //name: obj3
setTimeout(obj2.foo, 10);
//name: obj

소프트 바인딩이 탑재된 foo()함수는 this를 obj2 나 obj3 로 수동 바인딩 할 수 있고 기본 바인딩 규칙이 적용되어야 할 땐 다시 obj로 되돌린다.

어휘적 this

function foo() {
  return a => {
    // 여기서 this 는 어휘적으로 foo()에서 상속된다
    console.lgo(this.a);
  };
}

var obj1 = {
  a: 2,
};

var obj2 = {
  a: 3,
};

var bar = foo.call(obj1);
bar.call(obj1); //2, 3이 아니다
  • ES6부터는 화살표 함수를 써서 상위 스코프의 this를 바인딩 할 수 있다.
  • 화살표 함수의 어휘적 바인딩은 new로도 오버라이딩 할 수 없다.