UDN-企业互联网技术人气社区

板块导航

浏览  : 853
回复  : 0

[讨论交流] JavaScript 中对象的深拷贝

[复制链接]
呵呵燕的头像 楼主
发表于 2016-11-21 22:17:09 | 显示全部楼层 |阅读模式
  在JavaScript中,对对象进行拷贝的场景比较常见。但是简单的复制语句只能对对象进行浅拷贝,即复制的是一份引用,而不是它所引用的对象。而更多的时候,我们希望对对象进行深拷贝,避免原始对象被无意修改。

  对象的深拷贝与浅拷贝的区别如下:

  浅拷贝:仅仅复制对象的引用,而不是对象本身;

  深拷贝:把复制的对象所引用的全部对象都复制一遍。

  一. 浅拷贝的实现

  浅拷贝的实现方法比较简单,只要使用是简单的复制语句即可。

  1.1 方法一:简单的复制语句

  1.   /* ================ 浅拷贝 ================ */

  2.   function simpleClone(initalObj) {

  3.   var obj = {};

  4.   for ( var i in initalObj) {

  5.   obj[i] = initalObj[i];

  6.   }

  7.   return obj;

  8.   }
复制代码

  1.   /* ================ 客户端调用 ================ */

  2.   var obj = {

  3.   a: "hello",

  4.   b: {

  5.   a: "world",

  6.   b: 21

  7.   },

  8.   c: ["Bob", "Tom", "Jenny"],

  9.   d: function() {

  10.   alert("hello world");

  11.   }

  12.   }

  13.   var cloneObj = simpleClone(obj); // 对象拷贝

  14.   console.log(cloneObj.b); // {a: "world", b: 21}

  15.   console.log(cloneObj.c); // ["Bob", "Tom", "Jenny"]

  16.   console.log(cloneObj.d); // function() { alert("hello world"); }

  17.   // 修改拷贝后的对象

  18.   cloneObj.b.a = "changed";

  19.   cloneObj.c = [1, 2, 3];

  20.   cloneObj.d = function() { alert("changed"); };

  21.   console.log(obj.b); // {a: "changed", b: 21} // // 原对象所引用的对象被修改了

  22.   console.log(obj.c); // ["Bob", "Tom", "Jenny"] // 原对象所引用的对象未被修改

  23.   console.log(obj.d); // function() { alert("hello world"); } // 原对象所引用的函数未被修改
复制代码


  1.2 方法二:Object.assign()

  Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

  1.   var obj = { a: {a: "hello", b: 21} };

  2.   var initalObj = Object.assign({}, obj);

  3.   initalObj.a.a = "changed";

  4.   console.log(obj.a.a); // "changed"
复制代码


  二. 深拷贝的实现

  要实现深拷贝有很多办法,有最简单的 JSON.parse() 方法,也有常用的递归拷贝方法,和ES5中的 Object.create() 方法。

  2.1 方法一:使用 JSON.parse() 方法

  要实现深拷贝有很多办法,比如最简单的办法是使用 JSON.parse():

  1.   /* ================ 深拷贝 ================ */

  2.   function deepClone(initalObj) {

  3.   var obj = {};

  4.   try {

  5.   obj = JSON.parse(JSON.stringify(initalObj));

  6.   }

  7.   return obj;

  8.   }
复制代码

  1.   /* ================ 客户端调用 ================ */

  2.   var obj = {

  3.   a: {

  4.   a: "world",

  5.   b: 21

  6.   }

  7.   }

  8.   var cloneObj = deepClone(obj);

  9.   cloneObj.a.a = "changed";

  10.   console.log(obj.a.a); // "world"
复制代码


  这种方法简单易用。

  但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。

  这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝。

  2.2 方法二:递归拷贝

  代码如下:

  1.   /* ================ 深拷贝 ================ */

  2.   function deepClone(initalObj, finalObj) {

  3.   var obj = finalObj || {};

  4.   for (var i in initalObj) {

  5.   if (typeof initalObj[i] === 'object') {

  6.   obj[i] = (initalObj[i].constructor === Array) ? [] : {};

  7.   arguments.callee(initalObj[i], obj[i]);

  8.   } else {

  9.   obj[i] = initalObj[i];

  10.   }

  11.   }

  12.   return obj;

  13.   }
复制代码


  上述代码确实可以实现深拷贝。但是当遇到两个互相引用的对象,会出现死循环的情况。

  为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。

  改进版代码如下:

  1.   /* ================ 深拷贝 ================ */

  2.   function deepClone(initalObj, finalObj) {

  3.   var obj = finalObj || {};

  4.   for (var i in initalObj) {

  5.   var prop = initalObj[i];

  6.   // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况

  7.   if(prop === obj) {

  8.   continue;

  9.   }

  10.   if (typeof prop === 'object') {

  11.   obj[i] = (prop.constructor === Array) ? [] : {};

  12.   arguments.callee(prop, obj[i]);

  13.   } else {

  14.   obj[i] = prop;

  15.   }

  16.   }

  17.   return obj;

  18.   }
复制代码


  2.3 方法三:使用Object.create()方法

  直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。

  1.   /* ================ 深拷贝 ================ */

  2.   function deepClone(initalObj, finalObj) {

  3.   var obj = finalObj || {};

  4.   for (var i in initalObj) {

  5.   var prop = initalObj[i];

  6.   // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况

  7.   if(prop === obj) {

  8.   continue;

  9.   }

  10.   if (typeof prop === 'object') {

  11.   obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);

  12.   } else {

  13.   obj[i] = prop;

  14.   }

  15.   }

  16.   return obj;

  17.   }
复制代码


  三. 参考:jQuery.extend()方法的实现

  jQuery.js的jQuery.extend()也实现了对象的深拷贝。下面将官方代码贴出来,以供参考。

  官方链接地址:https://github.com/jquery/jquery/blob/master/src/core.js

  1.   jQuery.extend = jQuery.fn.extend = function() {

  2.   var options, name, src, copy, copyIsArray, clone,

  3.   target = arguments[ 0 ] || {},

  4.   i = 1,

  5.   length = arguments.length,

  6.   deep = false;

  7.   // Handle a deep copy situation

  8.   if ( typeof target === "boolean" ) {

  9.   deep = target;

  10.   // Skip the boolean and the target

  11.   target = arguments[ i ] || {};

  12.   i++;

  13.   }

  14.   // Handle case when target is a string or something (possible in deep copy)

  15.   if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {

  16.   target = {};

  17.   }

  18.   // Extend jQuery itself if only one argument is passed

  19.   if ( i === length ) {

  20.   target = this;

  21.   i--;

  22.   }

  23.   for ( ; i < length; i++ ) {

  24.   // Only deal with non-null/undefined values

  25.   if ( ( options = arguments[ i ] ) != null ) {

  26.   // Extend the base object

  27.   for ( name in options ) {

  28.   src = target[ name ];

  29.   copy = options[ name ];

  30.   // Prevent never-ending loop

  31.   if ( target === copy ) {

  32.   continue;

  33.   }

  34.   // Recurse if we're merging plain objects or arrays

  35.   if ( deep && copy && ( jQuery.isPlainObject( copy ) ||

  36.   ( copyIsArray = jQuery.isArray( copy ) ) ) ) {

  37.   if ( copyIsArray ) {

  38.   copyIsArray = false;

  39.   clone = src && jQuery.isArray( src ) ? src : [];

  40.   } else {

  41.   clone = src && jQuery.isPlainObject( src ) ? src : {};

  42.   }

  43.   // Never move original objects, clone them

  44.   target[ name ] = jQuery.extend( deep, clone, copy );

  45.   // Don't bring in undefined values

  46.   } else if ( copy !== undefined ) {

  47.   target[ name ] = copy;

  48.   }

  49.   }

  50.   }

  51.   }

  52.   // Return the modified object

  53.   return target;

  54.   };
复制代码


原文作者:佚名  来源:开发者头条

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部