最近尝试看看jQuery的源码。 版本 version = "1.11.1"
相对于看一本jQuery如何使用的书,看jQuery源码对它可以有更深层次的理解。jQuery中大量使用了正则表达式,对于全面提升JavaScript的能力也有很大帮助。由于考虑到了各种边界情况,以及对不同版本的浏览器的兼容等,jQuery整体代码都很严谨。
前言
jQuery源码中大量使用了&&和|| ,在JS中它们更像操作数选择器,它们的返回值是两个操作数的一个,而不是我们通常认为的返回true或者false。
var a=3;
var b='cc';
var c=null;
console.log(a||b); //3
console.log(a&&b); //cc
console.log(c||b); //cc
console.log(c&&b); //null
&&和||会先对第一个操作数(也就是a||b中的a)做条件判断。
对于||来说如果条件判断结果为true,则返回第一个操作数的值,如果为false,则返回第二个操作数的值。
对于&&来说如果条件判断结果为true,则返回第二个操作数的值,如果为false,则返回第一个操作数的值。
JavaScript中的假值有(undefined、null、false、+0、-0、NaN、“”)
一 整体框架
去掉其他枝叶代码,先来看看jQuery的整体框架
(function( global, factory ) {
if ( typeof module === "object" && typeof module.exports === "object" ) {
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
throw new Error( "jQuery requires a window with a document" );
}
return factory( w );
};
} else {
factory( global );
}
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
jQuery.fn = jQuery.prototype = {
//.........
}
var init = jQuery.fn.init = function( selector, context ) {
//.........
}
init.prototype = jQuery.fn;
if ( typeof noGlobal === strundefined ) {
window.jQuery = window.$ = jQuery;
}
return jQuery;
}));
可以看到jQuery整体是一个立即执行函数,JavaScript是函数作用域而非块作用域,因此其中定义的变量不会污染全局空间。if ( typeof module .......){} else 这部分代码用于检测代码运行环境,如果 module === "object" 说明是在CommonJS或者类似于CommonJS的环境中,使用 module.exports 向外暴露API。但是这样的环境中可能不存在window 对象,如果不存在window对象,就向外提供一个function,可以额外传递window对象作为参数。 在一般浏览器环境中则直接运行factory( global )运行。最后window.jQuery = window.$ = jQuery; 向外部暴露jQuery。
再来看看jQuery的初始化部分。也就是下面这部分。
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
jQuery.fn = jQuery.prototype = {
//.........
}
var init = jQuery.fn.init = function( selector, context ) {
//.........
}
init.prototype = jQuery.fn;
我们很熟练的用类似于$('#cc') $('div')的方式创建jQuery对象,可以看到它返回了new jQuery.fn.init( selector, context ); 使用new实例化的一个对象,由于jQuery.prototype=jQuery.fn=jQuery.fn.init.prototype 因此挂载到jQuery实例上面的方法(定义在jQuery.prototype上的)也就挂载到了使用new jQuery.fn.init()创建的实例对象上。 jQuery.fn.init=jQuery.prototype .init 因此jQuery的实例也可以访问到init方法。
二 链式操作及pushStack
我们在使用jQuery是常常进行一些链式的操作比如这样:$('li').eq(1).css('background','red').end().length
$('li')获取到文档中所有的li, $('li').eq(1).css('background','red')取得第一个li,然后将它的背景色设置为红色,调用end()回到原来的状态,最后length获取文本中li的个数。
下面是一些常用的实例方法:get 、eq、end
jQuery.fn = jQuery.prototype = {
get: function( num ) {
return num != null ?
( num < 0 ? this[ num + this.length ] : this[ num ] ) :
slice.call( this );
},
pushStack: function( elems ) {
var ret = jQuery.merge( this.constructor(), elems );
ret.prevObject = this;
ret.context = this.context;
return ret;
},
first: function() {
return this.eq( 0 );
},
last: function() {
return this.eq( -1 );
},
eq: function( i ) {
var len = this.length,
j = +i + ( i < 0 ? len : 0 );
return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
},
end: function() {
return this.prevObject || this.constructor(null);
}
};
在理解代码之前先看看$('li')长什么样吧,就长下面这样,是一个类数组。包含了所有满足筛选条件的DOM对象,所以我们可以用$('li')[0]这样的方式获取到对应的DOM。
使用$('li').eq(0)可以获取到对应的jQuery对象,其中pushStack 在jQuery的源码中多次使用到。pushStack做了这些事:创建一个新的jQuery对象,将一个DOM数组的内容整合到里面,保存当前的环境和前一个jQuery对象。 因此在调用end的时候就可以由
prevObject属性找到前一个对象并返回。
三 extend扩展
使用jQuery.extend 可以用于扩展jQuery的静态方法。使用 jQuery.fn.extend 可用于扩展jQuery的实例方法。第一个参数是true的话代表深拷贝。如果没有提供目标对象的话则扩展自身,否则扩展目标对象。
jQuery.extend = jQuery.fn.extend = function() {
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
if ( typeof target === "boolean" ) {
deep = target;
// 跳过 boolean 和target
target = arguments[ i ] || {};
i++;
}
// 排除target是string等情况
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// 扩展JQuery本身,如果只传递了一个参数
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
//只处理非null/undefined 值
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// 有环
if ( target === copy ) {
continue;
}
// 递归如果我们合并普通对象或数组
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
target[ name ] = jQuery.extend( deep, clone, copy );
// 排除掉 undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
深拷贝指的是对象属性所引用的对象全部进行新建对象复制,以保证深复制的对象不包含任何原有对象及原对象的引用。在进行深拷贝时,如果子属性是数组或者对象,就进行递归调用直到全部拷贝完毕。
jQuery.extend 和jQuery.fn.extend 使用同一个方法。如果要扩展自身的话,可以通过target = this区别出是对jQuery这个function进行扩展,还是对jQuery的一个实例进行扩展。
四 一些静态方法
jquery自身扩展的一些静态方法,比如jQuery.each 、 jQuery.map等都是常用的方法。这部分的源码都比较好理解,基本上了解这部分可以更加熟练地利用jQuery了。jQuery.merge函数用于合并两个数组内容到第一个数组。在jQuery内部也经常用到这个方法。
jQuery.extend({
each: function( obj, callback, args ) {
//....................
},
merge: function( first, second ) {
var len = +second.length,
j = 0,
i = first.length;
while ( j < len ) {
first[ i++ ] = second[ j++ ];
}
// Support: IE<9
// Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists)
if ( len !== len ) {
while ( second[j] !== undefined ) {
first[ i++ ] = second[ j++ ];
}
}
first.length = i;
return first;
},
map: function( elems, callback, arg ) {
//..........................
}
});
五 冲突检测
无论如何引入jQuery的js包都会在window中创建一个全局的jQuery和$。如果我们自己的代码中定义了同名的全局变量的话,就会引起冲突,可以通过jQuery.noConflict(true)来避免冲突。
var
_jQuery = window.jQuery,
_$ = window.$;
jQuery.noConflict = function( deep ) {
if ( window.$ === jQuery ) {
window.$ = _$;
}
if ( deep && window.jQuery === jQuery ) {
window.jQuery = _jQuery;
}
return jQuery;
};
在window.jQuery = window.$ = jQuery之前,使用变量_jQuery、_$ 存储当前环境的同名变量。在调用noConflict 之后,还原 window.$ = _$;