概述
我看的是3.1.0版本的,先上一段代码吧,不要版本都不一样那就尴尬了,这段代码看着没有多少,但我相信这基本上是这个世界上执行的最多的代码了,再不济也是一个之一。
直接看代码有一点绕,所以我们通过先黑盒的方式,先看实现的功能,在来分析功能实现的代码是怎么实现的。
jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[ 0 ] || {}, i = 1, length = arguments.length, deep = false; // 如果第一个参数类型是boolean类型,处理深拷贝 if ( typeof target === "boolean" ) { deep = target; // 跳过第一个Boolean参数 target = arguments[ i ] || {}; i++; } // 如果target类型不是Object或者function就把目标对象设为空 if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { target = {}; } // 如果只有一个参数就扩展jQuery本身 if ( i === length ) { target = this; i--; } for ( ; i < length; i++ ) { // 只处理非空值 if ( ( options = arguments[ i ] ) != null ) { for ( name in options ) { src = target[ name ]; copy = options[ name ]; // 避免无限循环 if ( target === copy ) { continue; } // 递归拷贝 plain objects({},new Object()) 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 : {}; } // 不移动原始对象,只是拷贝 target[ name ] = jQuery.extend( deep, clone, copy ); // 不拷贝undefined值 } else if ( copy !== undefined ) { target[ name ] = copy; } } } } return target;};
添加jQuery静态属性和实例属性
这个标题可能有一些迷惑,那我们可以使用另一个说法,为jQuery添加属性和为jQuery的原型prototype添加属性(这个属性可能是一个function类型的方法) 。
假设我们都知道jQuery是一个函数对象,所以它是有prototype属性的,也知道它们有什么区别,如果不知道可以先看一下这篇文章。
js不讲道理的地方就在于它太灵活了,以至于很多库你不看源码根本不知做了什么处理,比如jQuery.extend 和 jQuery.fn.extend这个方法,参数个数,参数类型不一样处理都不一样,然后对于我这样的后端来说,每一次用就看一下,结果每一次可能都不一样,结果接混乱了。
这里顺便提一下为什么是这个不是这2个,是因为它们方法体是同一个,js就是这么神奇。
这里就总结一下:
- 如果jQuery.extend只有一个参数就是给jQuery添加静态属性(一般这个属性是一个或者一下函数),就是给jQuery函数对象添加属性。
- 如果jQuery.fn.extend只有一个参数就是给jQuery添加实例属性(一般这个属性是一个或者一下函数),就是给jQuery函数对象的prototype属性添加属性。
下面我们就来从源码的角度来分析一下是怎样实现的: 关键就在于下面的代码:
if ( i === length ) { target = this; i--;}
根据上下文很容易判读要满足i===length 那么i===length === 1,接下来就好理解了,只有一个参数的时候target就是this。jQuery.extend调用的时候this是jQuery函数对象,jQuery.fn.extend调用的时候this是jQuery.prototype因为jQuery.fn == jQuery.prototype
所以很容易就能得出上面的结论了。
深拷贝与浅拷贝
deep = false;if ( typeof target === "boolean" ) { deep = target; target = arguments[ i ] || {}; i++;}
从上下文可以看出默认的是浅拷贝方式,如果第一个参数是一个boolean值,那么就是指示的是深拷贝或者浅拷贝,第二个参数就变为了目标对象target。
我们先来看浅拷贝,拷贝操作的核心代码就是for循环了,我们先来看一下浅拷贝,如果只是浅拷贝的话,我们可以把代码简化为下面的样子:
for ( ; i < length; i++ ) { // 只处理非空值 if ( ( options = arguments[ i ] ) != null ) { for ( name in options ) { src = target[ name ]; copy = options[ name ]; // 避免无限循环 if ( target === copy ) { continue; }if ( copy !== undefined ) { target[ name ] = copy; } } } }
这个是不是好理解多了,就是把参数对象中的所有属性设置到target中,target已经有的就执行的是覆盖操作。
再来看一下深拷贝的核心代码:
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 );}
这个其实也还好理解,就是对于被拷贝对象copy如果是数组和PlainObject(就是{}和new Object方式生成的对象)就再对这个个copy对象执行一下依次拷贝(递归)。
举个简单的例子:如果有2个对象:
{ name: "tim", location:{city: "Boston",county:"USA"} }
{ name: "tom",age:20, location: {state: "MA",county:"China"}}
jQuery.extend({ name: "tim", location: { city: "Boston", county: "USA" } }, { name: "tom", age: 20, location: { state: "MA", county: "China" } });
的结果是:
{"name":"tom","location":{"state":"MA","county":"China"},"age":20}
jQuery.extend(true,{ name: "tim", location: { city: "Boston", county: "USA" } }, { name: "tom", age: 20, location: { state: "MA", county: "China" } });
的结果是:
{"name":"tom","location":{"city":"Boston","county":"China","state":"MA"},"age":20}
所以可以总结一下:浅拷贝只是简单的执行的是添加或者覆盖。而深拷贝对于PlainObject或者是数组对象就再递归的执行添加拷贝操作,而不是简单的覆盖。
jQuery的extend方法的应用
说了这么多,主要还是看应用了,我遇到最多的就是插件中了。我们知道jQuery插件的开发一般是下面这个套路:
;(function ($, window) { $.fn.myPlugin = function() { //实现代码 };}(jQuery, window));
;分号是避免多个文件压缩前一个文件中的代码最后一个语句没有添加;引起的错误。最外层的()小括号是匿名函数自执行。传入jQuery和window是避免每一次在函数内部使用jQuery和window的时候都要依次查找到最顶层,优化速度。
至于:
$.fn.myPlugin = function() { //实现代码 };
这个套路,你如果仔细读了这篇文章,应该就能理解,其实本质上就是给jQuery的函数对象的prototype属性添加方法。
既然说道extend那么当然除了上面的套路还有extend的方式了:
(function($){ $.fn.extend({ f1: function(op){}, f2:function(op){} })})(jQuery);
国产的ui框架jui就是使用的这个套路。其实本质到一样,就是给jQuery的函数对象的prototype属性添加方法。
除了$.fn.extned还有$.extend方法了,比如要扩展String类型的方法就可以:
$.extend(String.prototype, { isPositiveInteger:function(){ return (new RegExp(/^[1-9]\d*$/).test(this)); }, isInteger:function(){ return (new RegExp(/^\d+$/).test(this)); }})
其实本质都是一样的,最重要的是弄清楚js的函数对象,prototype,原型链和对象属性。其他的基本万变不离其宗。