Javscript this, call, apply, bind

Javascript 에 오신것을 환영합니다. 낯선이여! 이 포스트는 글에 기반하여 작성되었습니다.


1. http://dailyjs.com/2012/06/18/js101-this/
2. http://dailyjs.com/2012/06/25/this-binding/
3. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Method, Function

먼저 메소드와 함수의 차이에 대해서 간단히 알아보자. ES5 Spec 에 의하면 메소드는 Object 에 붙어있는 Function 이다. new 로 Object 를 생성하고 이 오브젝트의 Method 내에서 this 는 해당 오브젝트의 인스턴스를 가리킨다. (자바스크립트에서는 클래스가 따로 없으므로 오브젝트라 부르는 것 같다.). 반면 Method 가 아니라 Function 내부에서 thiswindow, Node.js 라면 global 이다. 그리고 strict mode 라면 Function 내부에서 thisundefined 다.


요약하자면 아무데에도 붙어있지 않고 홀로 정의되어 있는 것은 함수요, 오브젝트의 프로퍼티로 정의된 함수는 메소드이며,  함수 내에서 this 는 global, window 또는 undefined 가 될 수 있다. 반면 오브젝트의 메소드 내에서 this 는 해당 오브젝트의 인스턴스를 가리킨다. 

Self

아래는 흔히 하는 실수와, 변수 self 를 통해서 해결하는 방법이다.


function Shape() {
  this.x = 0;
  this.y = 0;
}

Shape.prototype = {
  move: function(x, y) {
    this.x += x;
    this.y += y;

    function checkBounds() {
      if (this.x > 100) {
        console.error('Warning: Shape out of bounds');
      }
    }

    checkBounds();
  }
};

var shape = new Shape();
shape.move(101, 1);

console.error 가 호출되지 않는 이유는, checkBounds Function 은 Shape.move property 내에서 정의되어 있지만, 그 자체가 Object 의 메소드는 아니기 때문이다.


Shape.prototype = {
  move: function(x, y) {
    var self = this;

    this.x += x;
    this.y += y;

    function checkBounds() {
      if (self.x > 100) {
        console.error('Warning: Shape out of bounds');
      }
    }

    checkBounds();
  }
};

아무리 Object 에 붙어있는 Method 라도, 그 자체로 따로 변수로 저장하면 단순한 Function 이다. 무슨말인고 하니


this.x = 9; 
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var getX = module.getX;
getX(); // 9, because in this case, "this" refers to the global object

getX 는 단순한 Function 이다. new ContsructorInstance 를 만들고, Instance.method() 와 같이 호출하거나, Object.method() 처럼 호출되는 것이 아니면 해당 함수는 전역 컨텍스트에서 실행되므로 thisglobal 이거나 window 일거다.

call, apply

Function.prototype.call 을 이용해서도 문제를 해결할 수 있다. call 은 파라미터 중 첫 번째 인자를, 함수 내부에서 사용할 this 로 만들어 준다.


Shape.prototype = {
  move: function(x, y) {
    this.x += x;
    this.y += y;

    function checkBounds() {
      if (this.x > 100) {
        console.error('Warning: Shape out of bounds');
      }
    }

    checkBounds.call(this);
  }
};

apply 도 마찬가지다. 그러나 call 과는 달리 apply 는 파라미터를 배열(Array) 형태로 받는다.


Shape.prototype = {
  move: function(x, y) {
    this.x += x;
    this.y += y;

    function checkBounds(min, max) {
      if (this.x < min || this.x > max) {
        console.error('Warning: Shape out of bounds');
      }
    }

    checkBounds.call(this, 0, 100);
    checkBounds.apply(this, [0, 100]);
  }
};

아래의 jQuery API 예제에서, 일반적이라면 우리가 넘겨준 콜백은 단순한 Function 이므로 thiswindow 또는 global 이어야 하지만, jQuery 가 내부적으로 콜백 내에서 사용하는 thisDOM 객체가 될 수 있도록 call, apply 를 적용해 주었을 것이다. (소스를 뒤져봤는데 내공 부족으로 원하는 내용을 찾을 수 없었음)

bind

Function.prototype.bind 는 원하는 Function 에 인자로 넘긴 this 가 바인딩 된 새로운 함수를 리턴한다.


Shape.prototype = {
  move: function(x, y) {
    this.x += x;
    this.y += y;

    function checkBounds(min, max) {
      if (this.x < min || this.x > max) {
        console.error('Warning: Shape out of bounds');
      }
    }

    var checkBoundsThis = checkBounds.bind(this);
    checkBoundsThis(0, 100);
  }
};


Shape.prototype = {
  move: function(x, y) {
    this.x += x;
    this.y += y;

    var checkBounds = function(min, max) {
      if (this.x < min || this.x > max) {
        console.error('Warning: Shape out of bounds');
      }
    }.bind(this);

    checkBounds(0, 100);
  }
};

bind : default parameters

bind() 를 이용하면, default parameters 를 가진 함수를 만들 수 있다. 그 전에 잠깐 이후에 사용할 Array.prototype.slice 에 대해 간단히 살펴보자.


var arr = [1, 2, 3];
arr.slice() // [1, 2, 3]
arr.slice(1) // [2, 3]
arr.slice(2) // [3]

기본적으로 slice 는 인자가 아무것도 없으면, this 를 배열로 만들어 돌려준다. 그래서 call 을 이용하면 이런 활용이 가능하다.


function list() {
  return Array.prototype.slice.call(arguments);
}

list(1, 2, 3) // [1 ,2 3]

그리고 bind 를 이용하면, 이렇게 default parameter 를 가진 함수를 만들 수 있다.


function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

//  Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

bind : setTimeout

setTimeout 의 callback 의 this 는 전역 컨텍스트에서 사용되므로 window 또는 global(Node.js) 이다. 따라서 setTimeout 의 callback 으로 오브젝트의 메소드를 주고, 그 메소드 내에서 오브젝트의 인스턴스를 this 로 참조하려면, bind가 꼭 필요하다.


function LateBloomer() {
  this.petalCount = Math.ceil( Math.random() * 12 ) + 1;
}

// declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
  window.setTimeout( this.declare.bind( this ), 1000 );
};

LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with 
              ' + this.petalCount + ' petals!');
};

만약 bind() 가 없다면, declare()this.petalCount 를 얻지 못해 undefined 가 출력된다.


  ...
  ...
LateBloomer.prototype.bloom = function() {
  window.setTimeout( this.declare, 1000 );
};
  ...
  ...

var lb = new LateBloomer();
lb.bloom();

// "I am a beautiful flower with undefined petals!"

bind : Advanced

고급 기법을 설명하는 링크. 여기로



comments powered by Disqus