我们来填上次$.extend基础用法挖的坑,来逐行分析一下jQuery中$extend方法是怎样实现的。
首先先贴源码:
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] {};
// skip the boolean and the target
i = 2;
}
// 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 ( length === i ) {
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.extend = jQuery.fn.extend = function() { //给jQuery对象和jQuery原型对象都添加了extend扩展方法。
一:初始化变量过程:
var //每一个变量具体代表什么,我们往下分析就会得到结果。
options,
name,
src,
copy,
copyIsArray,
clone,
target = arguments[0] {}, // Return the modified object 返回的最后修改的对象
//这里初始化为第一个参数,如果没有就是空对象 (但这都是在多于一个参数且第一个参数不是boolean类型的情况,后面马上会说明)
i = 1, //这里是表示传入参数访问的下标
length = arguments.length, //参数的长度
deep = false; //是否深度复制
---------------------------------------------华丽的分割线---------------------------------------
二:初步判断参数过程:
这里是分三个简单的小判断:
(1)判断target是否是boolean,也就是判断第一个参数传入的是否布尔类型。
// Handle a deep copy situation 操作深度复制的情况
if ( typeof target === "boolean" ) { //如果这里的target类型是boolean
deep = target; //这里就让深度复制的flag等于target
target = arguments[1] {}; //这里让target接收第二个传入的参数。
// skip the boolean and the target
i = 2; //跳过布尔值和目标 (通过调整下标)
}
测试结果:
console.log( $.extend(true,{name:1}) ); //jQuery对象
/*
function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
}
*/
console.log( $.extend(true,{name:1},{name:2,age:18}) ); //Object {name: 2, age: 18}
问:这里为什么在传两个参数的时候,target返回的是jQuery对象呢?
答:因为这里满足了下面要说的第三个小判断,别着急,继续看下去。
(2)控制当target不是object或者function的情况。
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {}; //不是object或function类型的时候,让target为空{}。
}
(3)当参数列表长度等于i的时候,扩展jQuery对象自身。
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i; //这里--i是控制下标溢出的,因为i初始化是1,如果穿一个参数下标就会超出长度,如果第一个参数是boolean类型,这样传两个参数,同样也会溢出,具体原因可以去看第(1)个小判断。
}
测试结果:
$.extend({
b:1,
a:function(){
alert(1);
}
});
console.log($.b); //1 (注释掉--i) undefined
$.a(); //alert(1); (注释掉--i) Uncaught TypeError: $.a is not a function
-------------------------------------晕晕的分割线-----------------------------------------------
三:遍历参数列表给target赋值的阶段。
for ( ; i < length; i++ ) {
//这里是循环的执行体
}
在研究内部逻辑之前,首先我们先看一下for循环下i的问题:
这个i在这次的分析中,是一个还没有解决的问题,看下图:
这里会多了一次循环,目前在源码里我没有找到是什么原因出现的,而且后面多遍历出了jQuery中的offsetSupport和support对象,感兴趣的同学可以深入研究一下,也欢迎大神们留评论指导(^__^)。
内部逻辑:
(1)浅层复制:
下边这段代码是深层复制的条件判定,我们先跳过,过一会再讨论,我们现在需要关注的是else if中的内容:
if ( deep && copy && ( jQuery.isPlainObject(copy) (copyIsArray = jQuery.isArray(copy)) ) ) {
// deep必须指定为真,copy在循环中不为undefined的情况,copy必须是new出的Object或者{}格式,或者copy是一个数组,用变量copyIsArray 来接收copy是不是数组 存储的是boolean
来看else if部分的代码(很简单的一句):
else if ( copy !== undefined ) {
target[ name ] = copy;
//这里用到了name和copy两个我们还没有赋值的变量,而看这个形式obj[name] = value 你想到了什么?
}
接下来我们来向上找,name和copy所对应的key和value在哪里:
if ( (options = arguments[ i ]) != null ) {
//循环中arguments[i]赋值给options变量 而且要求这个值不能为null
// Extend the base object
for ( name in options ) { //这里for in循环遍历每一个options
src = target[ name ]; //src存改变之前target对象上每一个key对应的value
copy = options[ name ]; //copy存传入对象上每一个key对应的value
这里不是很好理解,单独写一个例子便于理解:
比如我们这样调用:$.extend({},{name:1},{name:2,age:18});
参数列表中的每一项,分别是{} 、{name:1} 、{name:2,age:18}
这个时候由于i初始化是1 所以options是访问不到{},而同时的状态下target指向的是{}。
下标0的元素不会访问到的demo:
function rep(){
var name,
options,
copy,
len = arguments.length,
i = 1;
for(; i<len; i++){
if( (options = arguments[i]) != null ){
alert(1);
}
}
}
rep({}); //这个时候不会alert
rep({},{}); //这时才会
一个简单抽离出来的浅层复制代码实现:
function rep(){
var name,
options,
copy,
len = arguments.length,
i = 1,
target = arguments[0],
src,
copy;
for(; i<len; i++){
if( (options = arguments[i]) != null ){
for(name in options){
src = target[name];
copy = options[name];
console.log(src); //undefined undefined 18 undefined
console.log(copy);//1 18 '16' Object {eat: "eat", run: "run"}
//这个结果就很明显的说明了 name、src、copy都指的是什么了。
//最后扩展的过程就是把copy上获取到的不为undefined的值,复制到 target的同一个key上。(同key会覆盖)
if(copy !== undefined){
target[name] = copy;
}
}
}
}
return target;
}
var res = rep({},{name:1,age:18},{age:'16',params:{eat:'eat',run:'run'}});
console.log(res); //Object {name: 1, age: "16", params: Object} 也就意味着把第二个之后的参数对象内容都扩展到第一个空{}上了。
(2)深层复制:
第一句判断,前边注释过,就不多说了,直接看内部的逻辑:
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) { //这里是copy是数组的情况
copyIsArray = false; //先从新指定为false,要不然下次循环判定就会出问题
clone = src && jQuery.isArray(src) ? src : [];
//如果src存在且是数组的话就让clone副本等于src否则等于空数组。
} else { //copy是对象的情况
clone = src && jQuery.isPlainObject(src) ? src : {};
//同数组的操作
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
//这里用递归的形式在走一次entend方法,来判断经过一次复制之后,是否还存在数组或对象形式的copy,如果有则再次走这个if分支,没有则走上面浅层复制的分支。
}
测试代码:
//这里在fon in 循环中加了两条console用来帮助我们查看结果。
console.log('src:'+JSON.stringify(src));
console.log('copy:'+JSON.stringify(copy));
//这里是调用的测试代码部分:
var res = $.extend(
true,
{
params:{
add:'no'
}
},
{
name:1,
params:{
add:'add',
remove:'remove'
}
}
);
console.log(res);
测试结果截图:
数组的格式同理,这里就不再进行测试了,到这里$.extend源码的解析基本就结束了。
最后来总结一下我们刚开始抛出的每一个变量含义的问题:
下次会更新$.extend的最终章,偏向于应用,会简单的实现一个$.extend扩展组件,敬请期待,( ^_^ )/~~拜拜喽!