jQuery.extend()的实现方式详解及实例

来源:互联网 发布:淘宝有的不能极速退款 编辑:程序博客网 时间:2024/06/05 17:53

extend()函数是jQuery的基础函数之一,作用是扩展现有的对象

<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>  <script>    obj1 = { a : 'a', b : 'b' };    obj2 = {  x : { xxx : 'xxx', yyy : 'yyy' },  y : 'y' };    $.extend(true, obj1, obj2);    console.log(obj1.x.xxx);  // 得到"xxx"    obj2.x.xxx = 'zzz';    console.log(obj2.x.xxx);  // 得到"zzz"    console.log(obj1.x.xxx);  // 得到"xxx"  </script>

$.extend(true, obj1, obj2)表示以obj2中的属性扩展对象obj1,第一个参数设为true表示深复制
虽然obj1中原来没有”x”属性,但经过扩展后,obj1不但具有了”x”属性,而且对obj2中的”x”属性的修改也不会影响到obj1中”x”属性的值,这就是所谓的“深复制”了。

浅复制的实现

如果仅仅需要实现浅复制,可以采用类似下面的写法:

$ = {     extend : function(target, options) {        for (name in options) {            target[name] = options[name];        }        return target;    }}

也就是简单地将options中的属性复制到target中。我们仍然可以用类似的代码进行测试,但得到的结果有所不同(假设我们的js命名为“jquery-extend.js”):

<script type="text/javascript" src="jquery-extend.js"></script><script>obj1 = { a : 'a', b : 'b' };obj2 = {  x : { xxx : 'xxx', yyy : 'yyy' },  y : 'y' };$.extend(obj1, obj2);alert(obj1.x.xxx);  // 得到"xxx"obj2.x.xxx = 'zzz';alert(obj2.x.xxx);  // 得到"zzz"alert(obj1.x.xxx);  // 得带"zzz"</script>

obj1中具有了”x”属性,但这个属性是一个对象,对obj2中的”x”的修改也会影响到obj1,这可能会带来难以发现的错误。

深复制的实现

如果我们希望实现“深复制”,当所复制的对象是数组或者对象时,就应该递归调用extend。如下代码是“深复制”的简单实现:

$ = { extend : function(deep, target, options) {  for (name in options) {   copy = options[name];   if (deep && copy instanceof Array) {                target[name] = $.extend(deep, [], copy);            } else if (deep && copy instanceof Object) {                target[name] = $.extend(deep, {}, copy);   } else {    target[name] = options[name];   }  }  return target; }};

具体分为三种情况:
1. 属性是数组时,则将target[name]初始化为空数组,然后递归调用extend;
2. 属性是对象时,则将target[name]初始化为空对象,然后递归调用extend;
3. 否则,直接复制属性

测试代码如下:

<script type="text/javascript" src="jquery-extend.js"></script><script>obj1 = { a : 'a', b : 'b' };obj2 = {  x : { xxx : 'xxx', yyy : 'yyy' },  y : 'y' };$.extend(true, obj1, obj2);alert(obj1.x.xxx);  // 得到"xxx"obj2.x.xxx = 'zzz';alert(obj2.x.xxx); // 得到"zzz"alert(obj1.x.xxx); // 得到"xxx"</script> 

现在如果指定为深复制的话,对obj2的修改将不会对obj1产生影响了;不过这个代码还存在一些问题,比如“instanceof Array”在IE5中可能存在不兼容的情况。jQuery中的实现实际上会更复杂一些。

更完整的实现
下面的实现与jQuery中的extend()会更接近一些:

$ = function() {    var copyIsArray,        toString = Object.prototype.toString,        hasOwn = Object.prototype.hasOwnProperty;    class2type = {        '[object Boolean]' : 'boolean',        '[object Number]' : 'number',        '[object String]' : 'string',        '[object Function]' : 'function',        '[object Array]' : 'array',        '[object Date]' : 'date',        '[object RegExp]' : 'regExp',        '[object Object]' : 'object'    },    type = function(obj) {        return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";    },    isWindow = function(obj) {        return obj && typeof obj === "object" && "setInterval" in obj;    },    isArray = Array.isArray || function(obj) {        return type(obj) === "array";    },    isPlainObject = function(obj) {        if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {            return false;        }        if (obj.constructor && !hasOwn.call(obj, "constructor")                && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {            return false;        }        var key;        for (key in obj) {        }        return key === undefined || hasOwn.call(obj, key);    },    extend = function(deep, target, options) {        for (name in options) {            src = target[name];            copy = options[name];            if (target === copy) { continue; }            if (deep && copy                    && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {                if (copyIsArray) {                    copyIsArray = false;                    clone = src && isArray(src) ? src : [];                } else {                    clone = src && isPlainObject(src) ? src : {};                }                target[name] = extend(deep, clone, copy);            } else if (copy !== undefined) {                target[name] = copy;            }        }        return target;    };    return { extend : extend };}();

注意

1.

   type = function(obj) {        return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";   },   isArray = Array.isArray || function(obj) {        return type(obj) === "array";    }

如果浏览器有内置的Array.isArray 实现,就使用浏览器自身的实现方式,否则将对象转为String,看是否为”[object Array]”。
问题:(1)为什么将对象转为String
(2)该判断是否为数组的方法考虑了浏览器的兼容性
2.
最后逐句地看看isPlainObject的实现:

if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {    return false;}

如果定义了obj.nodeType,表示这是一个DOM元素;这句代码表示以下四种情况不进行深复制:
1. 对象为undefined;
2. 转为String时不是”[object Object]”;
3. obj是一个DOM元素;
4. obj是window。
之所以不对DOM元素和window进行深复制,可能是因为它们包含的属性太多了;尤其是window对象,所有在全局域声明的变量都会是其属性,更不用说内置的属性了。

接下来是与构造函数相关的测试:

 if (obj.constructor && !hasOwn.call(obj, "constructor")                && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {        return false;    }

如果对象具有构造函数,但却不是自身的属性,说明这个构造函数是通过prototye继承来的,这种情况也不进行深复制。这一点可以结合下面的代码结合进行理解:

对以上有疑问:经测试,对于普通的对象,var obj ={k1:"a"},obj.constructor && !hasOwn.call(obj, "constructor"),这两个条件也都满足,只有第三个条件具备独特性,能筛选出通过构造函数声明的对象。

var key;for (key in obj) { }return key === undefined || hasOwn.call(obj, key);

对以上的疑问,对于上述的反例是什么???

这几句代码是用于检查对象的属性是否都是自身的,因为遍历对象属性时,会先从自身的属性开始遍历,所以只需要检查最后的属性是否是自身的就可以了。
这说明如果对象是通过prototype方式继承了构造函数或者属性,则不对该对象进行深复制;这可能也是考虑到这类对象可能比较复杂,为了避免引入不确定的因素或者为复制大量属性而花费大量时间而进行的处理,从函数名也可以看出来,进行深复制的只有”PlainObject”。

3.对hasOwnProperty和isPrototypeOf的理解

function O() {    this.yyy = 'yyy';  }function X() {  this.xxx = 'xxx';}X.prototype = new O();x = new X();obj1 = { a : 'a', b : 'b' };obj2 = { x : x };var copyIsArray,  toString = Object.prototype.toString,  hasOwn = Object.prototype.hasOwnProperty;console.log(x.constructor); //[Function: O]console.log(x.hasOwnProperty("constructor"));//falseconsole.log(x.constructor.prototype)// O {}console.log(x.constructor.prototype.hasOwnProperty("isPrototypeOf"));//falseconsole.log(x.constructor.hasOwnProperty("isPrototypeOf"));//falseconsole.log(hasOwn.call(x, "constructor"));//false(??)console.log(hasOwn.call(x.constructor.prototype, "isPrototypeOf"));//false(??)console.log(hasOwn.call(Object, "constructor"));//false(??)console.log(hasOwn.call(Object, "isPrototypeOf"));//false(???)

总结:如果对象(比如x)具有构造函数,但却不是自身的属性,说明这个构造函数是通过prototye继承来的,这种情况也不进行深复制。

4.constructor是否是对象自身属性的理解

function X() {  this.xxx = 'xxx';}var x = new X();var toString = Object.prototype.toString,  hasOwn = Object.prototype.hasOwnProperty;console.log(x.constructor);//[Function: X]console.log(hasOwn.call(x, "constructor"));//falseconsole.log(hasOwn.call(x.constructor.prototype, "isPrototypeOf"));//false

//通过第三个条件可以确定x不是一个普通的对象,而是通过构造函数声明的。从而不进行深拷贝。

对于以下代码,可知constructor和isPrototypeOf是Object.prototype自身的属性。

var obj = {k1:1};console.log(obj.constructor);//[Function: Object]console.log(hasOwn.call(obj, "constructor"));//falseconsole.log(hasOwn.call(obj.constructor.prototype, "constructor"));//trueconsole.log(hasOwn.call(obj.constructor.prototype, "isPrototypeOf"));//true

5.如果我们用如下代码进行测试:

<script type="text/javascript" src="jquery-1.5.2.js"></script><script>function O() { this.yyy = 'yyy';}function X() { this.xxx = 'xxx';}X.prototype = new O();x = new X();obj1 = { a : 'a', b : 'b' };obj2 = { x : x };$.extend(true, obj1, obj2);alert(obj1.x.yyy);  // 得到"xxx"obj2.x.yyy = 'zzz';alert(obj1.x.yyy);  // 得到"zzz"</script>

可以看到,这种情况是不进行深复制的。
总之,jQuery中的extend()的实现方式,考虑了兼容浏览器的兼容,避免性能过低,和避免引入不可预料的错误等因素。
转自:http://www.jb51.net/article/39288.htm