Scope in Javascript

프로그래밍 2006. 12. 20. 15:21

요즘 prototype을 뜯어 보구 있는데 생각보다 벽[각주:1]이 높다..ㅡㅡ;

내가 제일 헤갈린것이 this의 scope였다.
모든 this가 같은 this인줄만 알았는데..완전 잘못알았다.
그래서 구글링을 하던중 아주 좋은 아티클을 본 덕분에 어느정도 감을 잡았다.

그래도 나는 나의 기억력을 알고 있기때문에 위의 아티클을 중심으로
일단 정리를 하도록 한다..ㅡㅡ;

이 아티클에서는 4개의 정도의 this를 설명하고 몇개는 this의 scope의 대한 얘기를 한다.

1.Calling an Object’s Method

<script type="text/javascript">                                             
  var deep_thought = {
   the_answer: 42,
    ask_question: function () {
      return this.the_answer;
    }
  };
 
  var the_meaning = deep_thought.ask_question();
</script>

즉,Object에서 method를 호출할때의 this는 호출한 본인을 뜻한다.
간단히 말하면 호출한 함수의 "."앞에의 Object를 의미한다.
위에서 the_meaning은 42가 나타난다.


2.Constructor

  function BigComputer(answer) {
    this.the_answer = answer;
    this.ask_question = function () {
       return this.the_answer;
    }
  }
 
  var deep_thought = new BigComputer(42);
  var the_meaning = deep_thought.ask_question();

new 키워드를 가지고 생성자를 사용하는 함수인경우의  this의 의미는 새로 생긴
object를 말한다.
위의 예를 들면 BigComputer의 바로 밑에 있는 this는 this.the_answer,this.ask_question들은 deep_thought을 지칭한다.[각주:2]
하지만 this.ask_question안의 this.the_answer는 좀 다른 의미를 가지고 있다.[각주:3]
2차 this는 다른 변수와 달리 scope chain[각주:4]에서 읽어 오는것이 아니라 단순히 deep_thought에서 읽어온다.
즉,2차 this는 현재 기본 context를 참고한다.
위의 같은경우는 하나의 context이기 때문에 상관없지만  아래의 예인 Complications의 경우는 context가 여러개여서 틀려진다.


3.Function Call

  function test_this() {
    return this;
  }
  var i_wonder_what_this_is = test_this();

이번경우는 아무것도 없이 this를 리턴하게 되면 window object가 반환된다.


4.Event Handler


이벤트 핸들러에서의 this는 종류가 두가지 이다.

첫번째로는 아래와 같이 이벤트 핸들러를 인라인으로 사용했을경우

  function click_handler() {
    alert(this); // alerts the window object
  }

...
<button id='thebutton' onclick='click_handler()'>Click me!</button>

이렇게 한다면 click_handler의 this는 생각했던 dom이 아니라 window object가 된다.


2번째 방법은 javascript에서 이벤트 핸들러를 정의한경우.

  function click_handler() {
    alert(this); // alerts the button DOM node
  }
 
  function addhandler() {
    document.getElementById('thebutton').onclick = click_handler;
  }
 
  window.onload = addhandler;
...
<button id='thebutton'>Click me!</button>

위와 같이 정의 한다면 click_handler의 this는 button의 dom객체가 된다.


5.Complications

function BigComputer(answer) {
  this.the_answer = answer;
  this.ask_question = function () {
  alert(this.the_answer);
  }
}
 
function addhandler() {
  var deep_thought = new BigComputer(42),
  the_button = document.getElementById('thebutton');

  the_button.onclick = deep_thought.ask_question;
}
 
window.onload = addhandler;

위는 좀 복잡하지만 자주쓰는 부분이므로 알아보자.
위의 예는 thebutton의 엘리먼트의 onclick이벤트에 addhandler를 추가하는 내용이다.
언뜻보기에는 큰문제가 없다.

예상대로라면 당연히 thebutton을 클릭할경우 '42'가 호출할것을 예상한다.
하지만 undefined가 나온다.위의 내용을 이해했다면[각주:5] 쉽게 알수 있을것이다.
이유는 현재 이벤트 핸들러의 context안에 BigComputer의 context가 있다.
그렇기 때문에 1차this는 scope chain에 의해서 deep_thought을 나타내고 2차this는 현재 기본context인 이벤트핸들러를 지칭한다.그래서
이벤트 핸들러의 the_answer를 찾게된다.하지만 없기 때문에 undefined가 나오는것이다.
(setTimeout의 함수롤 비슷한 행동을 가지고 있다.)


6.Manipulating Context With .apply() and .call()

그럼 위와 같이 event 나 setTimeout을 동작할때 native context[각주:6]
일단은 해결할수있는 메소드가 2개있다[각주:7]
이메소드는 this를 명확히 지칭해주므로 context을 고정할수 있다.

일단 간단한 예로 call,apply의 사용법을 알아보자.

var first_object = {
  num: 42
};
var second_object = {
  num: 24
};
 
function multiply(mult) {
  return this.num * mult;
}
 
multiply.call(first_object, 5); // returns 42 * 5
multiply.call(second_object, 5); // returns 24 * 5
......
multiply.apply(first_object, [5]); // returns 42 * 5
multiply.apply(second_object, [5]); // returns 24 * 5

먼저 call을 보면 의미는 multiply를 호출하는데 this의 대상은 first_object이고 파라메터는 5이다.[각주:8]
그렇기 때문에 그냥 multiply(5)를 하면 this.num 이 없어서 오류가 나지만 this를 first_object로 정했기 때문에 에러가 나지 않는다.
두분째의 apply는 call과 모드 같은 말이지만 파라메터에 차이가 있다.apply의 두번째인자는 해당함수의 파라메터를 뜻하는데 배열형태를 이룬다.
즉,파라메터가 유동적으로 변하거나 할때에 유용히 사용된다.

그럼  call을 가지고 아까의 문제를 해결해보자.

function addhandler() {
  var deep_thought = new BigComputer(42),
  the_button = document.getElementById('thebutton');
 
  the_button.onclick = deep_thought.ask_question.call(deep_thought);
}
위와 같이 call을 할때 this를 대신할 object를 넘겨주므로서 2차 this는 deep_thought을 참조하게 되고 정상적으로 출력이 된다.


7.The Beauty of .bind()

이와 같은 문제점을 해소하기 위해서 Prototype JavaScript framework 에서는
편리한 방법으로 아래와 같은 멋진 메소드를 제공한다.[각주:9]
Function.prototype.bind = function(obj) {
  var method = this,
  temp = function() {
    return method.apply(obj, arguments);
  };
 
  return temp;
}

multiply.call(first_object, 5); 이렇게 표현했던 방식이 multiply.bind(first_object); 으로 바뀐다.

원문:Scope in JavaScript

요즘 prototype.js을 공부하고 있는데 생각보다 어렵다.
할건 너무 많은데..생각만큼 속도가 안나네..

  1. 그누가 자바스트립트가 쉬운 언어라고 했던가... [본문으로]
  2. 해당this는 1차 this로 표현 [본문으로]
  3. 해당this는 2차 this로 표현 [본문으로]
  4. 참고:scope chain [본문으로]
  5. 결코 이해했을거란 생각을 안한다.내가 생각해도 글을 잘못쓰는것 같다.ㅡㅡ; 이해가 잘안된다면 원문을 읽기를 추천합니다. [본문으로]
  6. 자신의 context,위에서 보면 이벤트가 아닌 BigComputer의 context을 말함. [본문으로]
  7. 이것 이외에도 this 바꿔치기가 있습니다. [본문으로]
  8. 다 알고있겠지만 저같은 초보자분들을 위해.. [본문으로]
  9. bind 이외에도 bindAsEventListener가 있음. [본문으로]
Posted by 전용우

댓글을 달아 주세요

  1. 자나가다 2010.05.05 11:22  댓글주소  수정/삭제  댓글쓰기

    예제가 잘못됐네요.

    function addhandler() {
    var deep_thought = new BigComputer(42),
    the_button = document.getElementById('thebutton');

    the_button.onclick = deep_thought.ask_question.call(deep_thought);
    }
    실행해보시면

  2. ㅇㅇㅇ 2012.07.19 23:07  댓글주소  수정/삭제  댓글쓰기

    본문의 주제와는 관계없는 여담이지만
    본문에 나오는 '42'의 의미는 있기도 하고 없기도 하지요. ㅋㅋㅋ