이 블로그는 광고 클릭 수익으로 운영됩니다!
괜찮으시다면 광고 차단을 풀어주세요 ㅠㅠ

게시글

강좌33 - JavaScript - 2년 전 등록 / 일 년 전 수정

jQuery 분석(3)

조회수:
0
이 블로그는 광고 클릭 수익으로 운영됩니다!
괜찮으시다면 광고 차단을 풀어주세요 ㅠㅠ
이 블로그는 광고 클릭 수익으로 운영됩니다!
괜찮으시다면 광고 차단을 풀어주세요 ㅠㅠ

마지막으로 제이쿼리의 메소드 체이닝에 대해 알아보겠습니다!

제이쿼리를 보면 메소드를 연속으로 사용하는 경우가 많죠. 이번 시간은 $('#app-root').children().parent()가 어떻게 동작하는 지 알아보겠습니다! children 메소드를 호출한 후 parent를 하면 다시 원래의 $('#app-root')가 나와야겠죠? childrenparent 메소드는 아래 코드에 들어 있습니다. 왜 굳이 이렇게 만들었는지는 모르겠습니다. (이후 버전을 보면 바뀝니다)

jQuery.extend(
  init: function(){
    jQuery.initDone = true;
    jQuery.each( jQuery.macros.axis, function(i,n){ // (1)
      jQuery.fn[ i ] = function(a) {
      var ret = jQuery.map(this,n); // (2)
      if ( a && a.constructor == String )
        ret = jQuery.filter(a,ret).r;
      return this.pushStack( ret, arguments ); // (8)
    };
  });
  ...
});
jQuery.macros = {
  axis: {
    parent: "a.parentNode",
    ancestors: jQuery.parents,
    parents: jQuery.parents,
    next: "jQuery.sibling(a).next",
    prev: "jQuery.sibling(a).prev",
    siblings: jQuery.sibling,
    children: "jQuery.sibling(a.firstChild)"
  },
  ...
};
jQuery.init();

함수들을 jQuery.macros.axis 객체에 넣어둔 후에 jQuery.each로 각각 prototype에 추가했습니다. (1)을 보면, 매개변수 i는 parent, children 같은 속성명이고, n은 "a.parentNode", "jQuery.sibling(a.firstChild)" 같은 속성값입니다. (2)에서 jQuery.map은 n이 문자열이면 return + n으로 바꿔서 map합니다. 따라서 parent 경우는 function() { return a.parentNode; };가 되죠. 그리고 결과를 배열에 담아 반환합니다. children도 마찬가지고요. sibling 메소드 코드를 볼까요?

sibling: function(elem, pos, not) {
  var elems = [];
  var siblings = elem.parentNode.childNodes; // (3)
  for ( var i = 0; i < siblings.length; i++ ) { // (4)
    if ( not === true && siblings[i] == elem ) continue; // (5)
    if ( siblings[i].nodeType == 1 ) // (6)
      elems.push( siblings[i] );
    if ( siblings[i] == elem )
      elems.n = elems.length - 1;
  }
  return jQuery.extend( elems, { // (7)
    last: elems.n == elems.length - 1,
    cur: pos == "even" && elems.n % 2 == 0 || pos == "odd" && elems.n % 2 || elems[pos] == elem,
    prev: elems[elems.n - 1],
    next: elems[elems.n + 1]
  });
},

그리고 $('#app-root').children()을 했다고 생각해봅시다. elem 변수는 "jQuery.sibling(a.firstChild)"에 따라 [#app-root의 첫 번째 자식노드]입니다. (3)에서 siblings[#app-root의 자식들] 이 되고요. (4)에서 반복문을 돌며 siblings 변수를 검사하는데 (5)는 not이 undefined니까 넘어가고요. 

(6)에서 siblings[i]의 nodeType이 1(1이면 Element)입니다. 따라서 모든 자식들이 elems 배열에 push됩니다. (7)은 그냥 elems가 return 된다고만 보면 됩니다. 다시 (2)로 가면 ret[#app-root의 자식들] 이죠. (8)(제일 위의 코드에 있습니다)에서 this.pushStack(ret, arguments)으로 children 메소드는 끝이납니다. 

이제 this.pushStack 메소드를 봅시다. a는 지금 ret이, args에는 []가 담겨있습니다.

pushStack: function(a, args) {
  var fn = args && args[args.length-1]; // (8)
  if ( !fn || fn.constructor != Function ) { // (9)
    if ( !this.stack ) this.stack = [];
    this.stack.push( this.get() ); // (10)
    this.get( a ); // (11);
  } else {
    var old = this.get();
    this.get( a );
    if ( fn.constructor == Function )
      return this.each( fn );
    this.get( old );
  }
  return this;
}

(8)은 args가 빈 배열이라 fn은 undefined가 됩니다. 따라서 (9)에서 this.get()을 한 것을 stack 배열에 넣게 되는데요. get 메소드도 봐야할 것 같네요. num은 undefined입니다.

get: function( num ) {
  // Watch for when an array (of elements) is passed in
  if ( num && num.constructor == Array ) { // (12)
    // Use a tricky hack to make the jQuery object
    // look and feel like an array
    this.length = 0;
    [].push.apply( this, num );
    return this;
  } else { // (13)
    return num == undefined ?
      // Return a 'clean' array
      jQuery.map( this, function(a){ return a } ) : // (14)
      // Return just the object
      this[num];
  }
}

num이 undefined이니까 (13), (14)으로 가는데, jQuery.map이 그냥 그대로 this를 return하기 때문에 (10)에서는 this.stack.push([#app-root])가 됩니다. (11)에서 한 번 더 this.get을 호출하는데 이버에는 num이 a(ret)입니다. 따라서 (12)로 가죠. 이번에는 [].push.apply( this, num) 이라는 코드가 나오네요.

this 객체에 배열처럼 num을 push하라는 건데 이게 가능은 합니다. 이렇게 하면 결과는 { 0: #app-root의 자식노드, length: 1, stack: [#app-root], __proto__: jQuery } 가 됩니다. stack은 방금 전에 this.stack에 push한 결과입니다. 결국 이것이 결과가 되어 반환됩니다. prototype이 jQuery 객체이기 때문에 다시 모든 jQuery 메소드를 사용할 수 있습니다.

정말 복잡하죠? 저도 분석하는 데 오랜 시간이 걸렸습니다. 이제 다시 parent 메소드를 하게 되면 return a.parentNode로부터 시작하여 다시 이 과정을 거친 뒤 #app-root가 반환됩니다.

이렇게 제이쿼리 분석을 마칩니다. 제이쿼리는 3버전까지 오면서 코드가 많이 바뀌었습니다. 하지만 그 기본 원리는 같다는 걸 기억하세요! 다음 강좌는 함수형 프로그래밍 기법 중 하나인 커링에 대해 알아보겠습니다!

투표로 게시글에 관해 피드백을 해주시면 많은 도움이 됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright © 2016- 무단 전재 및 재배포 금지

댓글

아직 댓글이 없습니다.