Chinaunix首页 | 论坛 | 博客
  • 博客访问: 780969
  • 博文数量: 372
  • 博客积分: 10063
  • 博客等级: 中将
  • 技术积分: 4220
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-24 11:36
文章分类

全部博文(372)

文章存档

2012年(372)

分类: 虚拟化

2012-04-10 19:03:36

jQuery中的extend()

  extend()函数是jQuery的基础函数之一,作用是扩展现有的对象。例如下面的代码: 
Html代码  收藏代码
  1. <script type="text/javascript" src="jquery-1.5.2.js">script>  
  2. <script>  
  3. obj1 = { a : 'a', b : 'b' };  
  4. obj2 = {  x : { xxx : 'xxx', yyy : 'yyy' },  y : 'y' };  
  5.   
  6. $.extend(true, obj1, obj2);  
  7.   
  8. alert(obj1.x.xxx);  // 得到"xxx"  
  9.   
  10. obj2.x.xxx = 'zzz';  
  11. alert(obj2.x.xxx);  // 得到"zzz"  
  12. alert(obj1.x.xxx);  // 得带"xxx"  
  13. script>  

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

浅复制的实现

  如果仅仅需要实现浅复制,可以采用类似下面的写法: 
Javascript代码  收藏代码
  1. $ = {  
  2.      extend : function(target, options) {  
  3.         for (name in options) {  
  4.             target[name] = options[name];  
  5.         }  
  6.         return target;  
  7.     }  
  8. };  

  也就是简单地将options中的属性复制到target中。我们仍然可以用类似的代码进行测试,但得到的结果有所不同(假设我们的js命名为“jquery-extend.js”): 
Html代码  收藏代码
  1. <script type="text/javascript" src="jquery-extend.js">script>  
  2. <script>  
  3. obj1 = { a : 'a', b : 'b' };  
  4. obj2 = {  x : { xxx : 'xxx', yyy : 'yyy' },  y : 'y' };  
  5.   
  6. $.extend(obj1, obj2);  
  7.   
  8. alert(obj1.x.xxx);  // 得到"xxx"  
  9.   
  10. obj2.x.xxx = 'zzz';  
  11. alert(obj2.x.xxx);  // 得到"zzz"  
  12. alert(obj1.x.xxx);  // 得带"zzz"  
  13. script>  

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

深复制的实现

  如果我们希望实现“深复制”,当所复制的对象是数组或者对象时,就应该递归调用extend。如下代码是“深复制”的简单实现: 
Javascript代码  收藏代码
  1. $ = {  
  2.     extend : function(deep, target, options) {  
  3.         for (name in options) {  
  4.             copy = options[name];  
  5.             if (deep && copy instanceof Array) {  
  6.                 target[name] = $.extend(deep, [], copy);  
  7.             } else if (deep && copy instanceof Object) {  
  8.                 target[name] = $.extend(deep, {}, copy);  
  9.             } else {  
  10.                 target[name] = options[name];  
  11.             }  
  12.         }  
  13.         return target;  
  14.     }  
  15. };  

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

  测试代码如下: 
Html代码  收藏代码
  1. <script type="text/javascript" src="jquery-extend.js">script>  
  2. <script>  
  3. obj1 = { a : 'a', b : 'b' };  
  4. obj2 = {  x : { xxx : 'xxx', yyy : 'yyy' },  y : 'y' };  
  5. $.extend(true, obj1, obj2);  
  6. alert(obj1.x.xxx);  // 得到"xxx"  
  7. obj2.x.xxx = 'zzz';  
  8. alert(obj2.x.xxx); // 得到"zzz"  
  9. alert(obj1.x.xxx); // 得到"xxx"  
  10. script>  

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

更完整的实现

  下面的实现与jQuery中的extend()会更接近一些: 
Javascript代码  收藏代码
  1. $ = function() {  
  2.     var copyIsArray,  
  3.         toString = Object.prototype.toString,  
  4.         hasOwn = Object.prototype.hasOwnProperty;  
  5.   
  6.     class2type = {  
  7.         '[object Boolean]' : 'boolean',  
  8.         '[object Number]' : 'number',  
  9.         '[object String]' : 'string',  
  10.         '[object Function]' : 'function',  
  11.         '[object Array]' : 'array',  
  12.         '[object Date]' : 'date',  
  13.         '[object RegExp]' : 'regExp',  
  14.         '[object Object]' : 'object'  
  15.     },  
  16.   
  17.     type = function(obj) {  
  18.         return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";  
  19.     },  
  20.   
  21.     isWindow = function(obj) {  
  22.         return obj && typeof obj === "object" && "setInterval" in obj;  
  23.     },  
  24.   
  25.     isArray = Array.isArray || function(obj) {  
  26.         return type(obj) === "array";  
  27.     },  
  28.   
  29.     isPlainObject = function(obj) {  
  30.         if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {  
  31.             return false;  
  32.         }  
  33.   
  34.         if (obj.constructor && !hasOwn.call(obj, "constructor")  
  35.                 && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {  
  36.             return false;  
  37.         }  
  38.   
  39.         var key;  
  40.         for (key in obj) {  
  41.         }  
  42.   
  43.         return key === undefined || hasOwn.call(obj, key);  
  44.     },  
  45.   
  46.     extend = function(deep, target, options) {  
  47.         for (name in options) {  
  48.             src = target[name];  
  49.             copy = options[name];  
  50.   
  51.             if (target === copy) { continue; }  
  52.   
  53.             if (deep && copy  
  54.                     && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {  
  55.                 if (copyIsArray) {  
  56.                     copyIsArray = false;  
  57.                     clone = src && isArray(src) ? src : [];  
  58.   
  59.                 } else {  
  60.                     clone = src && isPlainObject(src) ? src : {};  
  61.                 }  
  62.   
  63.                 target[name] = extend(deep, clone, copy);  
  64.             } else if (copy !== undefined) {  
  65.                 target[name] = copy;  
  66.             }  
  67.         }  
  68.   
  69.         return target;  
  70.     };  
  71.   
  72.     return { extend : extend };  
  73. }();  

  首先是 $ =  function(){...}();这种写法,可以理解为与下面的写法类似: 
Java代码  收藏代码
  1. func = function(){...};  
  2. $ =  func();  
  也就是立即执行函数,并将结果赋给$。这种写法可以利用function来管理作用域,避免局部变量或局部函数影响全局域。另外,我们只希望使用者调用$.extend(),而将内部实现的函数隐藏,因此最终返回的对象中只包含extend: 
Java代码  收藏代码
  1. return { extend : extend };  

  接下来,我们看看extend函数与之前的区别,首先是多了这句话: 
Java代码  收藏代码
  1. if (target === copy) { continue; }  
  这是为了避免无限循环,要复制的属性copy与target相同的话,也就是将“自己”复制为“自己的属性”,可能导致不可预料的循环。 

  然后是判断对象是否为数组的方式: 
Java代码  收藏代码
  1. type = function(obj) {  
  2.      return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";  
  3. },  
  4. isArray = Array.isArray || function(obj) {  
  5.      return type(obj) === "array";  
  6.  }  
  如果浏览器有内置的Array.isArray 实现,就使用浏览器自身的实现方式,否则将对象转为String,看是否为"[object Array]"。 

   最后逐句地看看isPlainObject的实现: 
Java代码  收藏代码
  1. if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {  
  2.     return false;  
  3. }  
  如果定义了obj.nodeType,表示这是一个DOM元素;这句代码表示以下四种情况不进行深复制: 
  1. 对象为undefined; 
  2. 转为String时不是"[object Object]"; 
  3. obj是一个DOM元素; 
  4. obj是window。 
  之所以不对DOM元素和window进行深复制,可能是因为它们包含的属性太多了;尤其是window对象,所有在全局域声明的变量都会是其属性,更不用说内置的属性了。 

  接下来是与构造函数相关的测试: 
Javascript代码  收藏代码
  1. if (obj.constructor && !hasOwn.call(obj, "constructor")  
  2.               && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {  
  3.       return false;  
  4.   }  
  如果对象具有构造函数,但却不是自身的属性,说明这个构造函数是通过prototye继承来的,这种情况也不进行深复制。这一点可以结合下面的代码结合进行理解: 
Javascript代码  收藏代码
  1. var key;  
  2. for (key in obj) {  
  3. }  
  4.   
  5. return key === undefined || hasOwn.call(obj, key);  
   这几句代码是用于检查对象的属性是否都是自身的,因为遍历对象属性时,会先从自身的属性开始遍历,所以只需要检查最后的属性是否是自身的就可以了。 

   这说明如果对象是通过prototype方式继承了构造函数或者属性,则不对该对象进行深复制;这可能也是考虑到这类对象可能比较复杂,为了避免引入不 确定的因素或者为复制大量属性而花费大量时间而进行的处理,从函数名也可以看出来,进行深复制的只有"PlainObject"。 
  如果我们用如下代码进行测试: 
Javascript代码  收藏代码
  1. "text/javascript" src="jquery-1.5.2.js">  
  2.   
  可以看到,这种情况是不进行深复制的。 

  总之,jQuery中的extend()的实现方式,考虑了兼容浏览器的兼容,避免性能过低,和避免引入不可预料的错误等因素。
 
阅读(542) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~