이번 강좌는 PJS의 11장에 해당되는 Function에 대해서 알아볼까 합니다.
PJS의 중간에 다른 챕터들도 있지만 일단 다른 소스를 분석하려면 필수 개념들이 몇개 필요하다고 전 강좌에서 말씀드렸습니다. 그중에 하나인 bind의 개념을 소개하고자 챕터 11인 Function을 먼저 이야기하고자 합니다.

소스의 길이가 그리 길지 않으므로 원소스를 일단 보도록 하겠습니다.

Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
}


이것과 비슷하게 생긴 bindAsEventListener 라는 것도 있지만 대동소이하므로 아랫부분에서 간단하게
알아보겠습니다.

뭐 이렇게 생긴놈입니다. ㅎㅎ 이해가 잘 안되신다구요? 잠시 기다려주세요. 일단 문제부터 풀어봐야 합니다. 문제는 아래와 같습니다.

window.name = "window object"

var foo = {
 name : "foo object",
 scope : function(){
  alert(this.name);
 }
}

function fx(f){
 f();
}

var f = foo.scope.bind(foo);

fx(f); ----------------------------- ①
fx(foo.scope); -------------------- ②

1번의 답과 2번의 답. 예상되시는지요? 만약 정확하게 맞추시고 이유도 아신다면, bind의 개념은 이미
정립되었다고 봐도 무방합니다.

1번의 답은 "foo object"이고 2번의 답은 "window object"입니다. 답을 맞추셨나요? 그렇다면 이유도 설명할 수 있나요? 있다면~ 패스~! 없다면 아래의 글을 봐주세요. ㅎㅎ

var foo 는 name이라는 멤버 프로퍼티를 가지고 있으며 scope라는 멤버 메소드를 가지고 있습니다. 멤버라는 것은 객체에 속한 오브젝트라는 뜻입니다. 다들 아시죠? scope라는 멤버 메소드는 this.name이라는 값을 출력하는 행동을 담당하고 있군요.

function fx()는 인자로 함수를 받아 실행하는 단순구조로 되어있습니다. 아마 처음보신 syntax일 수도 있겠습니다. 처음보신 분은 "저런게 가능하다"정도로만 이해주시면 될듯합니다. 저런 syntax가 가능한 이유는
자바스크립트에서는 인자로 오브젝트를 넘길 수 있으며, 자바스크립트에서 함수또한 함수 오브젝트 이므로
함수도 인자로 넘길 수 있는 것입니다
. 계속가죠~

var f = foo.scope.bind(foo); 라고 되어 있는데, 일단 뭔가를 하는놈인지 알 수 없지만, 그렇다 치고
넘어가시고 다음줄을 봐주세요.
일단 2번부터 살펴보면, 위에서 설명한것 처럼 객체의 멤버 메소드인 foo.scope를 함수의 인자로 넘기고
있습니다. 여기서 부터 함정이 있는데요, 그것은 다음과 같습니다.

fx(foo.scope)의 답이 "foo  object"라고 생각하신 분은,
  1. 함수가 실행된다? 함수는 this.name을 출력한다.
  2. 함수안에 name이라는 놈이 있다? 그놈을 출력한다
  3. 그리하여 답은 "foo object"가 출력된다.


제가 예상한게 맞았나요? 하지만 위에서 말씀드렸듯이 답은 아닙니다. 정확한 답은 이 한줄로 표현할 수
있을것 같습니다.
"함수의 scope는 구현시점이 아닌 실행시점의 scope를 가진다" 무슨말이냐면,

fx(foo.scope)를 수학에서의 대입법을 통해 알아보면, fx(alert(this.name)) 이렇게 됩니다.
즉, 단순한 alert(this.name)을 출력하는데, 제가 묻겠습니다. this.name은 누구입니까? "foo object"입니까? 아닙니다. 바로 "window object"입니다. 이해가 잘 안가시는 분은 제가 밑에서 강의한 "변수"부분을 다시 읽어주시기 바랍니다. 그 글을 읽으시면 충분히 이해하시리라 믿습니다. 그래도 안읽고 이것만 보시는 분을 위해
간략하게 설명하면 "변수의 scope는 전역변수보다 지역변수가 우선하며, 지역변수가 존재하지 않을시 그위의
객체의 변수(더이상 없으면 전역변수)를 참조한다"
라고 말할 수 있습니다.

그렇다면 어떻게 하면 이런 scope의 변경없이 원하는 답을 얻도록 함수를 실행할 수 있을까요? 그렇습니다.
답은 bind 메소드 입니다. bind의 형태를 자세히 보시면 bind의 인자로써 참조객체를 넘기고 있습니다.

var f = foo.scope.bind(foo);

Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
}

그리고 bind를 설명드리면 var __method = this는 bind가 참조하고 있는 메소드가 되겠습니다. 위에서는
var __method = scope함수객체 가 되겠군요. 그리고 args = $A(arguments)라고 되어있습니다. $A메소드는
전 강좌에서 알아봤듯이 인자로 넘어온 객체를 배열객체로 만들어 리턴합니다. 여기서는 foo 객체자체가 배열로 들어오겠군요. 왜냐구요? .bind(foo)로 넘겼잖아요 ^^
object = args.shift()는 args가 가진 배열의 요소중 첫번째것을 object에 담고 나머지는 다시 args에
셋팅하게 됩니다. 예를 들면, args가 3개의 배열요소[1,2,3]을 가지고 있다고 합시다 그럼 object = [1]이
되고, args는 [2,3]이 됩니다. 이해하셨는지요? shift라는 메소드가 그런 역할을 하고 있습니다.
그럼 여기서는 어떻게 되나요? arguments로 넘어온 것은 foo객체 하나이므로 args의 크기는 1인 배열이
되겠군요, 그리고 바로 shift로 잘라내고 나니 object에는 foo객체가, args에는 빈배열
이 되어버렸군요. ㅎㅎ

여기까지는 쉽게 이해하셨지요? 구분을 좀더 보시면 return을 함수로 하고 있군요. 이런 구문은 Class.create() 에서 다뤄봐서 알겠지요? ㅎㅎ 그럼 계속 설명을 하겠습니다. 여기서 오해가 좀 생길 수 있으니 정신을
집중하세요 ^^

function() {
    return __method.apply(object, args.concat($A(arguments)));
  }


위 소스가 바로 리턴되는 함수의 원모습인데, 여기서 보시면, __method는 scope 인것을 이미 확인했고, object는 foo객체가 되고 args에는 아무것도 들어 있지 않습니다. 빈 배열이죠. 그다음 논란이 되는
구문인 args.concat($A(arguments)) 입니다.

많은 사람들이 왜 args에 다시 $A(arguments)를 다시 concat(두개의 배열을 합쳐 새로운배열을 리턴)하느냐고 묻습니다. 하지만 단호하게 대답하겠습니다. 위 구문에서의 arguments와 args = $A(arguemnts)에서의 arguments와는 다릅니다. 왜일까요? 네, 그렇습니다.
바로 return __method.apply(object, args.concat($A(arguments))) 가 정의된, 구현된 시점에서의  arguments가 아닌 실행시점의 arguments이기때문에 다른 것 입니다. 이해가 되셨는지요?

좀더 부연설명을 드리면, arguments는 실행시점에서 함수가 인자로 넘어온 값을 처리하는 native 컬렉션
오브젝트입니다. 그러므로 var __method = this, args = $A(arguments), object = args.shift(); 여기에서
정의된 $A(arguments)와는 다른것입니다
.  여기까지 이해하셨다면 bind의 개념은 이미 다 정립된 것입니다.

bind를 말로 풀어내면, "함수가 다른 함수안에서 실행될때 자기자신의 scope가 없어지는 것(멤버 프로퍼티에 접근하는 this 키워드 사용시)을 방지하기 위해 실행될 함수가 가지는 객체의 모든 것을 인자로 넘겨 실행시에 scope가 변경되지 않도록 하기 위함" 이라고 정의할 수 있겠습니다.

약간은 어려운 개념일 수도, 이해가 잘 안가실 수도 있는데 수학에서의 대입법을 통해 직접 확인해 보심이
좋을 것 같습니다. 백문이 불여일타 라고 하지않습니까? ㅎ 직접 해보시면서 제글을 보시면 더 확실히 이해가 가실거라 생각합니다.

위에서 잠시 언급한 bindAsEventListener의 모습은

Function.prototype.bindAsEventListener = function(object) {
  var __method = this, args = $A(arguments), object = args.shift();
  return function(event) {
    return __method.apply(object, [event || window.event].concat(args));
  }
}


이렇게 생겼는데, bind와 거의 유사하고 다른점이 하나있다면, apply할때 event객체를 args배열의 앞쪽에 먼저 붙여준다는 것외엔 다를것이 없습니다. 따로 설명은 드리지 않겠습니다.

이상으로 PJS의 11장에 해당하는 Function에 대해서 알아봤습니다. 자바스크립트의 syntax에 약하신분은 좀
버거운 내용이 아니였나 싶은데, 몇번을 읽고 또 읽고, 직접 실행을 해보면서 하시면 충분히 될 것으로
생각합니다.  그럼 다음 강좌때 뵙기로 하고 오늘은 여기까지 하겠습니다.

posted by blankus

Ajax  |  2007/06/16 20:06
이 글의 트랙백 주소 :: http://www.blankus.net/trackback/13
이름 ::   비밀번호 :: 홈페이지 :: 비밀글
등록