jQuery 源码学习(二)jQuery.fn和jQuery.extend()
先贴源码,这部分源码都在factory函数体内。
var deletedIds = [];
var slice = deletedIds.slice;
var concat = deletedIds.concat;
var push = deletedIds.push;
var indexOf = deletedIds.indexOf;
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
var support = {};
var
version = "1.11.1",
// Define a local copy of jQuery
jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init( selector, context );
},
// Support: Android<4.1, IE<9
// Make sure we trim BOM and NBSP
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
// Matches dashed string for camelizing
rmsPrefix = /^-ms-/,
rdashAlpha = /-([\da-z])/gi,
// Used by jQuery.camelCase as callback to replace()
fcamelCase = function( all, letter ) {
return letter.toUpperCase();
};
jQuery.fn = jQuery.prototype = {
// The current version of jQuery being used
jquery: version,
constructor: jQuery,
// Start with an empty selector
selector: "",
// The default length of a jQuery object is 0
length: 0,
toArray: function() {
return slice.call( this );
},
// Get the Nth element in the matched element set OR
// Get the whole matched element set as a clean array
get: function( num ) {
return num != null ?
// Return just the one element from the set
( num < 0 ? this[ num + this.length ] : this[ num ] ) :
// Return all the elements in a clean array
slice.call( this );
},
// Take an array of elements and push it onto the stack
// (returning the new matched element set)
pushStack: function( elems ) {
// Build a new jQuery matched element set
var ret = jQuery.merge( this.constructor(), elems );
// Add the old object onto the stack (as a reference)
ret.prevObject = this;
ret.context = this.context;
// Return the newly-formed element set
return ret;
},
// Execute a callback for every element in the matched set.
// (You can seed the arguments with an array of args, but this is
// only used internally.)
each: function( callback, args ) {
return jQuery.each( this, callback, args );
},
map: function( callback ) {
return this.pushStack( jQuery.map(this, function( elem, i ) {
return callback.call( elem, i, elem );
}));
},
slice: function() {
return this.pushStack( slice.apply( this, arguments ) );
},
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);
},
// For internal use only.
// Behaves like an Array's method, not like a jQuery method.
push: push,
sort: deletedIds.sort,
splice: deletedIds.splice
};
jQuery.extend = jQuery.fn.extend = function() {
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
// skip the boolean and the target
target = arguments[ i ] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
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 : {};
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
首先,jQuery 从 JavaScript 的原生数组和对象 API 里抽取了一些 API,如数组的 slice 、concat、push 和 index 方法,对象的 toString 和 hasOwnProperty 方法。
之后,开始声明 jQuery 的构造函数,或者叫 jQuery 类。可以看到 jQuery 的构造函数是很简单的,只是调用了他的 prototype 的 init 方法,去 new 一个实例。这个也很好理解,我们平时去生成一个 jQuery 对象的时候。从来没有 new JQuery(),而是 $() 或者 jQuery() ,其中的玄机就在于此。之后开始在原型上定义一些方法:
- toArray 方法:调用数组的 slice 方法,将 jQuery 数组转化为真正的数组。
- get 方法:如果传入了 num,则返回相应的 DOM 对象。如果不传入 num,则相当于调用了 toArray 方法,把自己变成了一个真正的数组。num 如果是负的,则从数组后面开始取值。
- pushStack 方法:这个方法一个新的元素数组和一个新创建的 jQuery 对象通过 jQuery.merge 方法合并到一起,再把这个合并之后的 jQuery 对象返回,并且把之前的 jQuery 对象用新对象的 prevObject 保存起来。
- each 方法,两个参数,分别是 callback 和 传入的参数,实际上调用的是 jQuery.each 方法,把实例对象传进去而已。
- map 方法,调用了 jQuery.map 方法,有一点复杂,等看到 jQuery.map 方法时再说。
- 将实例用原生的 slice 方法分割后,再用 pushStack 方法变回 jQuery 对象。
- first 和 last:都是调用 eq 方法。
- eq:一个参数 i,对 i 的取值做了一下处理(负值的话加数组的长度变正),如果处理后的 i 还在 0 到 length 之间,那么用 this[i] 取到对应的值,再用 pushStack 变成 jQuery 对象。
- end:返回 prevObject,如果没有的话返回一个空的 jQuery 对象。
- push, sort , splice:直接用的数组的原生方法,不返回 jQuery 对象,所以是当做内部方法使用的。
接下来,是 jQuery 中一个很重要的方法,extend 的定义,extend 的作用是可以很方便的合并多个对象,这在给对象赋值的时候十分方便。由于 jQuery 的方法是不传对象进去的,因此函数内部对于变量的类型和个数,做了很多种判断:
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
// skip the boolean and the target
target = arguments[ i ] || {};
i++;
}
target 默认取的第一个参数,如果 target 是一个布尔值的话,那么就变成了是否是深拷贝的 flag ,赋值给真正的 flag deep。随后 target 转移到第二个参数上。
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
当target既不是对象,而且也不是函数的时候,将 target 变成一个空对象。
// extend jQuery itself if only one argument is passed
if ( i === length ) {
target = this;
i--;
}
如果 i 和参数的个数相同,这说明只传入一个参数,或者两个参数,第一个是布尔值。 这个时候 target 就转换成了 jQuery 实例本身。
判断好了这些,接下来是一个大循环。跳出循环的条件是 i >= length,那么对于上面那种情况,这个循环是进入不了的,于是就直接把 target 给 return 了,所以直传一个对象进去,只会返回jQuery的实例,或者 jQuery 对象本身,并没什么用。要是什么都不传的话,按照上面的判断方式,length=0,i=1,所以返回的 target 应该是一个空对象。
当不是这些奇葩的情况时,那么循环开始,首先 options 取到 target 后一个参数,并且对 options 中的每一个属性开始遍历。这里开始区分是否是深拷贝的情况。如果是深拷贝的话使用到了递归,来逐级赋值。如果不是深拷贝的话,则是直接覆盖 target 对应的属性。
最后返回 target,值得注意的是 extend 方法是直接修改 target 对象的。