JQuery源码解析-JQuery.extend()方法
extend方法是jQuery中的继承方法,先说一下extend方法的使用,在进行源码解析。
当extend只有一个参数的时候,代表将对象扩展到jQuery的静态方法或实例方法中,如:
$.extend({ a: function () { alert("a"); } }) $.fn.extend({ a: function () { alert("a"); } }) $.a(); $().a();
在上面的代码可以看出不管是jQuery对象还是实例,都可以用extend方法进行继承,在源码中也是调用的同一个方法,之所以可以这么做的原因是因为在源码中,内部绑定时,用到了this。
$.extend的this就是$ 而 $.fn.extend的this是$.fn,也就是代表实例的原型上扩展。
再看一下传入多个参数的情况,当传入多个参数时,如果第一个参数不是bool类型,默认后面的参数的属性都会被添加到一个参数对象上。
如果第一个参数为bool类型且为true,则代表深拷贝,默认为浅拷贝,false。
var a = {}; var b = { tom: { age: 14 } } $.extend(a, b); a.tom.age = 25; console.log(a.tom.age); //25 console.log(b.tom.age);//25
上面的代码的问题可以看到,当继承的对象属性中有引用类型的时候,那么会造成两个两个对象同时指向一个对象,这样如果改变一个的话,另一个也随之改变,所以:
$.extend(true,a, b);
把第一个值给true,进行深拷贝就可以了。
下面看一下extend方法内部的源码。
内部的大体结构如下:
jQuery.extend = jQuery.fn.extend = function() { //定义一些参数 if(){} //看是不是深拷贝的情况。 if(){} //看参数是否正确 if(){} //看是不是插件的情况 for(){ //处理多个对象参数 if(){} //防止循环调用 if(){} //深拷贝 else if(){} //浅拷贝 } }
第一部分定义一些参数:
var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false;
然后进行判断,看是否第一个参数传入的是bool值,如果是,则将其赋值给deep,然后将target赋值为第二个参数。
// Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; }
然后进行判断,看target是否为对象或函数,如果非对象,如字符串等,则将其赋值为空对象。
// Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; }
然后判断是否为扩展工具方法,如果是的话,则直接将target赋值为this
// 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;
首先利用一个for循环来处理多个参数的情况,接着判断当前参数是否为null,如果为null的话,就不向下执行了。
再接着是一个for循环,循环传入的参数,在这个循环里进行对当前这个参数的对象进行解析和扩展。
首先对src和copy进行赋值
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; }
第一个判断,deep为true,也就是第一个参数为true,并且复制的对象不为空,还必须是对象或者数组,才可以进行深拷贝。
接下来是对是否为数组进行判断,最重要的一句是
clone = src && jQuery.isPlainObject(src) ? src : {};
这里先判断src是否为空,如果不为空则把target[name]赋值到clone上,如果为空则传入一个空对象,这里是为了处理这种情况。
var a = { tom: { sex: "man" }}; var b = { tom: { age: 14 } } $.extend(true,a, b); console.log(a);
当扩展对象和参数都有一个共同的对象时,那么正确做法是把参数b中不同的属性附加到a中,而不是进行覆盖。
所以这里需要进行判断。
如果这里将源码改了,也就是将:
clone = src && jQuery.isPlainObject(src) ? src : {};
替换为
clone = {};
只传入一个空对象,那么在次执行的结果为:
可以看到,这里就是将两者的相同属性进行了覆盖操作,这样是不对的。
最后进行递归调用,当深拷贝的时候,因为无法确定有几层,所以需要进行递归,直到最后一层。再次调用这个方法:
// 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; }
最后返回target。
// Return the modified object return target;
extend的方法就结束了。