jQuery源码阅读(八)---jQuery中的继承extend

xiaoxiao2021-02-28  128

今天看了jQuery源码中关于extend函数的部分,这部分就相当于jQuery继承,后面在整理的过程中说明如何继承。 还是跟之前的分析方法一样,要分析一个函数的实现,首先得知道函数是干什么的,它的功能是啥。

extend方法应用

扩展插件

在jQuery的学习当中,我们学过如何去写一个插件,而且插件分全局插件和实例插件,即一种是直接挂载在jQuery上的,另一种是挂载在jQuery实例对象上的。 比如下面两种实现: 扩展全局插件:

$.extend({ add: function(a, b){ return a + b; } }); $.add(4,9); //返回13

对象插件:

$.fn.extend({ add : function(a, b){ return a + b; } }) $().add(2,8) //返回10

扩展对象

这也可以算是jQuery中实现继承的一种方式。

a = { name : 'liu' } b = { name: 'yang' } var res = $.extend(a, b) //res可以说是继承了a和b的属性和方法 默认浅拷贝,改变a.name不会影响b.name aa = { name: { age: 20 } } bb = { name: { age: 15 } } $.extend(aa, bb) //浅拷贝,只拷贝对象,不会把内部具体的属性也拷贝 //所以修改a.name.age会改变b.name.age aa.name.age = 30; //此时bb.name.age也是30 //如果改用深拷贝的话 $.extend(true, aa, bb) //aa和bb中的name是不同的对象,指向不同的内存区域 //因此改变aa.name.age不会影响bb.name.age

extend方法在扩展对象时,最前面可以传一个布尔值的参数true,表示扩展对象时使用深拷贝,即将所有层级的对象属性都进行拷贝。而不加布尔值的参数,则默认是浅拷贝,即不会复制内层级的属性和方法。

extend源码分析

jQuery.extend = jQuery.fn.extend = function() { };

首先,我想说的是 jQuery.extend和jQuery.fn.extend是同一份代码,也就是说 实现思路是一样的.那如何才能确定是在jQuery上扩展的,还是在jQuery实例对象上扩展的?

JS中有个this的概念,即指向当前的上下文。具体点来说.

//直接调用函数时 function a(){ return this; } a(); //返回的是window对象

//如果通过对象调函数 var obj = { b: function(){ return this; } } obj.b(); //返回的是Object对象

所以如果$.extend(),this指向的是jQuery;,如果$.fn.extend(),this指向jQuery.fn也就是jQuery.prototype,那么在原型上扩展一个方法,就相当于在jQuery实例中可以调用该方法,从而实现扩展的目的。

接着下面的源码:

var options, name, src, copy, copyIsArray, clone, //从这块可以学习到,如果不确定函数中有多少个参数时,可以用arguments来访问。 target = arguments[0] || {}, //如果是深拷贝,target此时为true; //如果不是,target此时是要扩展的第一个参数; i = 1, length = arguments.length, deep = false; // 深拷贝的情况 if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; // 跳过true和要扩展的第一个参数 i = 2; } //如果不是对象,不是函数,target设为空对象,以便和后面对象能够合并 if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } //如果只有一个参数,那么就认为这是扩展插件,所以target就是this对象。前面也讲了什么情况下,this是jQuery,什么情况下this是jQuery.fn if ( length === i ) { target = this; --i; //这个时候就从第一个参数开始处理 } for ( ; i < length; i++ ) { //如果是对象扩展并且是浅拷贝,那么i从1开始 //如果是对象扩展并且是深拷贝,那么i从2开始 //如果是插件扩展,那么i从0开始 //前面已经处理好了 if ( (options = arguments[ i ]) != null ) { 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 : {}; } // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); } else if ( copy !== undefined ) { //如果不是深拷贝,就直接将name属性加到target对象里面了 target[ name ] = copy; } } } } //最后返回扩展后的target对象;或者返回jQuery或者返回jQuery.fn return target;

下来缕一缕上面提到的

if ( target === copy ) { continue; }

比如这么一种情况:

var obj = { age : 20 } var obj1 = { age: obj } var res = $.extend(obj, obj1);

这种情况下,在extend函数内部,target为 obj = { age : 20 }, copy = options[‘age’] = obj = { age : 20 } 所以判断target和copy相等,就会continue,跳过该次循环,那为什么要这样做呢? 我们先把if ( target === copy ) { continue; }注释掉,看结果是什么? 可以看到,返回的是一个含有一个name属性的对象,而name属性又指向该对象,这样无限循环下去了。为什么会出现这种情况呢? 因为如果去掉continue的话,会继续后面的执行,即

target[ name ] = copy;

在这句执行之前,target指向一个Object对象,copy也是指向该对象,那么对target的修改也会影响copy对象,所以target.name修改为copy,相当于copy.name也修改为copy,这样就形成了无限循环的对象,这样实际上没有什么意义,所以jQuery避免了这种情况。可见,jQuery各方面的考虑是非常全面。希望能学到更多,理解更深刻!

转载请注明原文地址: https://www.6miu.com/read-36633.html

最新回复(0)