代码改变世界

(六)jQuery.extend代码段

2012-02-12 15:07  kwjlk  阅读(320)  评论(0编辑  收藏  举报

现在就基本上可以把each中所有的代码弄明白了。

each: {
removeAttr: function( key ) {
this.removeAttribute( key );
},
show: function(){
this.style.display = this.oldblock ? this.oldblock : "";
if ( jQuery.css(this,"display") == "none" )
this.style.display = "block";
},
hide: function(){
this.oldblock = this.oldblock || jQuery.css(this,"display");
if ( this.oldblock == "none" )
this.oldblock = "block";
this.style.display = "none";
},
toggle: function(){
$(this)[ $(this).is(":hidden") ? "show" : "hide" ].apply( $(this), arguments );
},
addClass: function(c){
jQuery.className.add(this,c);
},
removeClass: function(c){
jQuery.className.remove(this,c);
},
toggleClass: function( c ){
jQuery.className[ jQuery.className.has(this,c) ? "remove" : "add" ](this,c);
},
remove: function(a){
if ( !a || jQuery.filter( [this], a ).r )
this.parentNode.removeChild( this );
},
empty: function(){
while ( this.firstChild )
this.removeChild( this.firstChild );
},
bind: function( type, fn ) {
if ( fn.constructor == String )
fn = new Function("e", ( !fn.indexOf(".") ? "$(this)" : "return " ) + fn);
jQuery.event.add( this, type, fn );
},
unbind: function( type, fn ) {
jQuery.event.remove( this, type, fn );
},
trigger: function( type, data ) {
jQuery.event.trigger( type, data, this );
}
}

简略浏览each中的属性,发现使我们在jQuery对象上经常调用的方法。那这个each跟jQuery的对象有着什么关系呢?

第一个函数removeAttr移除指定的属性。谷歌搜索removeAttribute

第二个函数show\hide,在使用jQuery时最经常使用的显隐元素的方法。动态定义了元素上一个叫做oldblock的属性。

第三个函数toggle,里面用到了is函数,实现代码自动判断使用show还是hidde。

第四个函数addClass\removeClass,使用到了jQuery.className里的方法。增加 和删除指定名称的CSS类。

第五个函数toggleClass,自动判断使用addClass和remveClass。

第六个函数remove,使用到了jQuery的filter函数。

第七个empty函数,这里使用this.firstChild的方法判断是否还有子节点很有意思的。在以后的代码里要考虑可以使用this.frstChild进行一些是否有子节点获取第一个节点的操作。

第八个bind函数,这个是对jQuery.event.add的封装,隐含传入了this。注意当传入的fn是字符串时该函数进行的处理

第九个unbind函数,这个是对jQuery.event.remove的封装,隐含传入了this。这里请考虑,我们在之前的分析中获知jQuery.event.remove需要根据指定的fn.guid进行移除。那么这里的封装,传入的fn不一定有guid属性啊?分析可知,经过add添加过的fn就会具有guid属性。这也涉及到了var tfn = bfn = fn 这三个变量其实是同一个fn的知识点。我通过如下代码验证此说法:

function getName(){
var name = 'i was a function';
return name;
}
function assignGuid(fn){
fn.guid = 100;
}
assignGuid(getName);
alert(getName.guid); // 弹窗显示 100

于是在这里,如果你传入的fn没有guid的话,则不会移除任何监听函数。

第十个trigger函数,这个是对jQuery.event.trigger的封装,隐含传入了this。

但是,each中的这些定义是什么作用呢?是不是正如我们猜想,会将each中的属性定义拓展到jQuery对象上呢?

回顾我们之前对jQuery.macros的注解,jQuery.macros中定义了to、attr、axis对象,css、filter数组。注意到这里使用了string.splie(char)的方式创建数组。为了能让我们在理解其它部分代码时更加快速,我们将这几个属性列举一下,并通过其字面意思和设置的值来大概定义他们的作用。

to: {
appendTo: "append",
prependTo: "prepend",
insertBefore: "before",
insertAfter: "after"
},

css: "width,height,top,left,position,float,overflow,color,background".split(","),
filter: [ "eq", "lt", "gt", "contains" ],
attr: {
val: "value",
html: "innerHTML",
id: null,
title: null,
name: null,
href: null,
src: null,
rel: null
},
axis: {
parent: "a.parentNode",
ancestors: jQuery.parents,
parents: jQuery.parents,
next: "jQuery.sibling(a).next",
prev: "jQuery.sibling(a).prev",
siblings: jQuery.sibling,
children: "a.childNodes"
}

to,定义了DOM节点的操作方法,共有四个。但是单凭to字面意思不能获取更进一步的理解。

css,使用string.split的方式来创建数组,字符串中是一些css属性。从css字面理解这是一个css属性数组。但是,这里的css属性并不全面,那为什么只有这几个呢?

filter,定一个是字符串数组,里面是等于、小于、大于、包含的英文或者缩写。综合filter字面意思,这里应该是一些比较过滤的定义,但这怎么起作用的呢?

attr,与之前的css类似,这定义的是html标签上会出现的属性,但是也不全面。

axis,不认识的同学好去translate.google.com了,坐标轴?通过内部值来看定义的是一些子父关系操作啊。里面又是字符串又是直接书写的形式让人很是费解。但是,要对这里面写的字符串有印象,因为大部分和jQuery有关系。

好了,这样的话对jQuery.macros的理解就算告一段落了。继续返回去看init的实现。在第一段jQuery.each的代码中我发现它使用了map和filter方法。map和filter方法就定义在init之后,于是去查看这两个方法的实现。先从map开始:

merge: function(first, second) {
var result = [];

// Move b over to the new array (this helps to avoid
// StaticNodeList instances)
for ( var k = 0; k < first.length; k++ )
result[k] = first[k];
// Now check for duplicates between a and b and only
// add the unique items
for ( var i = 0; i < second.length; i++ ) {
var noCollision = true;

// The collision-checking process
for ( var j = 0; j < first.length; j++ )
if ( second[i] == first[j] )
noCollision = false;

// If the item is unique, add it
if ( noCollision )
result.push( second[i] );
}
return result;
},
map: function(elems, fn) {
// If a string is passed in for the function, make a function
// for it (a handy shortcut)
if ( fn.constructor == String )
fn = new Function("a","return " + fn);

var result = [];

// Go through the array, translating each of the items to their
// new value (or values).
for ( var i = 0; i < elems.length; i++ ) {
var val = fn(elems[i],i);
if ( val !== null && val != undefined ) {
if ( val.constructor != Array ) val = [val];
result = jQuery.merge( result, val );
}
}
return result;
},

而map则调用了merge函数,merge函数没有调用其它函数。分析merge为,将第二个数组中的数据唯一的添加到第一个数组中去。map是对于指定的elems集合,对其中每个元素调用fn方法进行处理。然后将产生的结果放在新的数组里并返回。这里再次对fn = new Function("a","return "+fn)进行讨论分析。之前,在对jQuery.macros.each.bind的函数的分析中并没有对此代码有太多分析。现在通过如下代码对此进行分析:

function myFun(){
return 'ni hao';
}
//假设 fn = 'myFun'
var fFun = new Function('a','return myFun');
fFun();
// 返回 function myFun(){return 'ni hao';}
//
假设 fn = 'myFun()'
var sFun = new Function('b','return myFun()');
sFun();
// 返回 ni hao

通过如上代码,当fn仅仅是函数名时fFun的返回结果为函数 即 return fn。当fn中的函数名之后跟上括号时,返回的为该函数的执行结果即 return fn()。在map中显然该是第二种情形才对,那么传递进来的fn真的有括号吗?呵呵,在这里小小的犯了一下混。没有注意到代码里的

var val = fn(elems[i],i);

map 和 merge的作用就分析成这样了。接下来看一下filter的实现。粗略浏览了一下filter的实现代码,发现我坠入了jQuery另一个最核心的实现。发现jQuery中filter用到了大量的正则表达式。最后发现最后一段代码中用到了jQuery.attr方法,该方法也在当前extend中定义。打开查看,我滴娘啊,我能看懂了:

attr: function(elem, name, value){
var fix = {
"for": "htmlFor",
"class": "className",
"float": "cssFloat",
innerHTML: "innerHTML",
className: "className"
};
if ( fix[name] ) {
if ( value != undefined ) elem[fix[name]] = value;
return elem[fix[name]];
} else if ( elem.getAttribute ) {
if ( value != undefined ) elem.setAttribute( name, value );
return elem.getAttribute( name, 2 );
} else {
name = name.replace(/-([a-z])/ig,function(z,b){return b.toUpperCase();});
if ( value != undefined ) elem[name] = value;
return elem[name];
}
}

以前再使用jQuery写代码的时候,attr方法可以设置属性值也可以获取属性值,而且类似的方法还有几个。现在终于明白attr的这一点是如何实现的了。注意代码

if ( value != undefined ) elem[fix[name]] = value;
return elem[fix[name]];

下面if的三个判断分别是,第一个判断:是否在需要转义的列表里。第二个判断:元素是否具有getAttribute方法。第三个判断:在没有getAttribute方法是设置和获取属性,这大概又是ie浏览器的兼容代码吧。这里比较有意思的内容也有三点,跟这三个判断相对应。

  1. 为什么要由fix呢?
  2. 为什么getAttribute传入了一个参数 '2'?
  3. 第三个判断成立后,对name做了什么?

fix中列举的是elem对象上的属性,getAttribute定义没有第二个参数,name会把background-color变成backgroundColor 。总的来看attr的实现还是比较单纯的。再回头继续看filter实现。filter中引用了jQuery.grep,又巧了grep的实现也在当前的extend代码里。

grep: function(elems, fn, inv) {
// If a string is passed in for the function, make a function
// for it (a handy shortcut)
if ( fn.constructor == String )
fn = new Function("a","i","return " + fn);

var result = [];

// Go through the array, only saving the items
// that pass the validator function
for ( var i = 0; i < elems.length; i++ )
if ( !inv && fn(elems[i],i) || inv && !fn(elems[i],i) )
result.push( elems[i] );

return result;
}

经过千锤百炼之后这个函数的实现代码理解没有什么难度了。然而,在if( !inv && fn(elems[i],i) || inv && !fn(elems[i],i) )的判断里,inv的取值有什么作用呢?fn是什么函数呢?

在参数inv的含义上则让人费解。我在filter中找到了如下代码帮助理解:

filter:function(t,r,not){
...
// Figure out if we're doing regular, or inverse, filtering
var g = not !== false ? jQuery.grep :
function(a,f) {return jQuery.grep(a,f,true);};
...
// :not() is a special case that can be optomized by
// keeping it out of the expression list
if ( m[1] == ":" && m[2] == "not" )
r = jQuery.filter(m[3],r,false).r;
...
}

第一行代码的注释里提到,检查我们是不是依照正则表达是进行筛选。在filter传入not为false时才会使g = function(a,f){return jQuery.grep(a,f,true);}。结合上面第二段代码对jQuery.filter的调用情景,可以得出一般情况下grep返回正则匹配的结果,只有当grep传入的inv=true时(jQuery.filter 的not参数为false时,此时filter中有‘:not’过滤选项)返回的是正则不匹配的结果。而grep中的fn函数则是具有正则表达式函数。