지난 시간에 이어 제이쿼리 분석을 계속하겠습니다.
사실 아래에 jQuery.extend라고 숨겨진 코드가 더 있었습니다.
jQuery.extend = jQuery.fn.extend = function(obj,prop) {
if ( !prop ) { prop = obj; obj = this; }
for ( var i in prop ) obj[i] = prop[i];
return obj;
};
jQuery.extend({
...
find: function( t, context ) {
// Make sure that the context is a DOM Element
if ( context && context.nodeType == undefined ) // (1)
context = null;
// Set the correct context (if none is provided)
context = context || jQuery.context || document; // (2)
if ( t.constructor != String ) return [t]; // (3)
if ( !t.indexOf("//") ) { // (4)
context = context.documentElement;
t = t.substr(2,t.length);
} else if ( !t.indexOf("/") ) {
context = context.documentElement;
t = t.substr(1,t.length);
// FIX Assume the root element is right :(
if ( t.indexOf("/") >= 1 )
t = t.substr(t.indexOf("/"),t.length);
}
}
var ret = [context];
var done = [];
var last = null;
while ( t.length > 0 && last != t ) { // (5)
var r = [];
last = t;
t = jQuery.trim(t).replace( /^\/\//i, "" ); // (6)
var foundToken = false;
for ( var i = 0; i < jQuery.token.length; i += 2 ) { // (7)
if ( foundToken ) continue;
var re = new RegExp("^(" + jQuery.token[i] + ")");
var m = re.exec(t);
if ( m ) {
r = ret = jQuery.map( ret, jQuery.token[i+1] );
t = jQuery.trim( t.replace( re, "" ) );
foundToken = true;
}
}
if ( !foundToken ) { // (8)
if ( !t.indexOf(",") || !t.indexOf("|") ) { // (9)
if ( ret[0] == context ) ret.shift();
done = jQuery.merge( done, ret );
r = ret = [context];
t = " " + t.substr(1,t.length);
} else { // (10)
var re2 = /^([#.]?)([a-z0-9\\*_-]*)/i;
var m = re2.exec(t); // (11)
if ( m[1] == "#" ) { // (12)
// Ummm, should make this work in all XML docs
var oid = document.getElementById(m[2]); // (13)
r = ret = oid ? [oid] : [];
t = t.replace( re2, "" );
} else {
if ( !m[2] || m[1] == "." ) m[2] = "*";
for ( var i = 0; i < ret.length; i++ )
r = jQuery.merge( r,
m[2] == "*" ?
jQuery.getAll(ret[i]) :
ret[i].getElementsByTagName(m[2])
);
}
}
}
if ( t ) { // (14)
var val = jQuery.filter(t,r);
ret = r = val.r;
t = jQuery.trim(val.t);
}
}
if ( ret && ret[0] == context ) ret.shift(); // (15)
done = jQuery.merge( done, ret ); // (16)
return done;
},
extend 메소드는 jQuery.fn.extend이기 때문에 jQuery의 prototype에 메소드를 추가하는 함수입니다. 기존 find 메소드를 덮어썼기 때문에 jQuery.find는 바로 위의 find 메소드입니다.
find 메소드가 정말 기네요. 그래서 또 (1), (2), (3)... 으로 구분했습니다. 지금 현재 jQuery.find('#target')
을 한 상황이라고 가정해보죠. context는 undefined입니다. (1)은 그래서 넘어가고요. (2)에서 context는 document가 됩니다. jQuery.context
는 뭔지 모르니까요. (3)은 string이 맞으니까 넘어가고요. (4)의 if문은 문자열에 /가 들어있지 않으니까 넘어갑니다.
(5)에서 while문에 걸리는데요. (6)은 문자열에 공백이나 //가 있으면 제거하는 거고요. (7)에서는 jQuery.token이 나오는데 그냥 문자열이 ['.', '..', '<', '/', '+', '~']
중 하나로 시작하는 지 검사하는 겁니다. 모두 아니니까 넘어갑니다.
(8)에서 아까 foundToken이 false이기 때문에 걸립니다. (9)는 문자열에 ,(쉼표)나 |가 없기 때문에 (10) else로 넘어갑니다. (11)에서 m은 ['#target', '#', 'target']
이 됩니다. 따라서 (12)가 true가 되고 (13)에서 document.getElementById('target')
을 하게 되네요. 그 결과는 oid 변수에 저장되고, t는 빈 문자열이 됩니다. 따라서 (14)는 false로 넘어가죠. (15)도 넘어가고요.
최종적으로 (16)에서 jQuery.merge가 나오는데 그냥 배열을 합치는 메소드입니다. 결국에는 return done
으로 찾을 결과를 반환합니다.
후... 정말 복잡하네요. 이런 식이 있기 때문에 모든 선택자를 다 검색할 수 있었던 겁니다. 여러분도 이 정도의 라이브러리를 혼자 만들 수 있겠나요??? 마지막 시간에는 아직 알아보지 못한 한 가지, 메소드 체이닝이 어떻게 이루어지는 지 알아보겠습니다!