如何在js中得到一个克隆对象,能够说是喜闻乐见的话题了。相信你们都了解引用类型与基本类型,也都知道有种叫作深拷贝的东西,传说深拷贝能够得到一个克隆对象!那么像我这样的萌新天然就去学习了一波,咱们能找到的代码基本都是这样的:数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var deepClone = function(currobj){ if(typeof currobj !== 'object'){ return currobj; } if(currobj instanceof Array){ var newobj = []; }else{ var newobj = {} } for(var key in currobj){ if(typeof currobj[key] !== 'object'){ newobj[key] = currobj[key]; }else{ newobj[key] = deepClone(currobj[key]) } } return newobj } |
啧啧真是很精巧啊!对于Array和普通Object都作了区分。可是显然,借助递归实现的深拷贝若是要克隆层级不少的复杂对象,容易形成内存溢出,咱能够作出一个小小改进:缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
var deepClone = function(currobj){ if(typeof currobj !== 'object'){ return currobj; } if(currobj instanceof Array){ var newobj = []; }else{ var newobj = {} } var currQue = [currobj], newQue = [newobj]; //关键在这里 while(currQue.length){ var obj1 = currQue.shift(),obj2 = newQue.shift(); for(var key in obj1){ if(typeof obj1[key] !== 'object'){ obj2[key] = obj1[key]; }else{ if(obj1[key] instanceof Array ){ obj2[key] = []; }else{ obj2[key] = {} }; // 妙啊 currQue.push(obj1[key]); newQue.push(obj2[key]); } } } return newobj; }; |
这里利用了两个队列,还算优雅的避免了递归的弊端。函数
还有一种方法是利用JSON的内置方法,即所谓的JSON序列化:学习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var deepClone = function(obj){ var str, newobj = obj.constructor === Array ? [] : {}; if(typeof obj !== 'object'){ return; } else if(window.JSON){ str = JSON.stringify(obj), //系列化对象 newobj = JSON.parse(str); //还原 } else { for(var i in obj){ newobj[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]; } } return newobj; }; |
不过不打紧,它与上面方法的效果基本相同。this
拜托,你们都很懂对象,上面的方法有几个很大的问题:prototype
因而,咱们就要开始改造上面的深拷贝方法来进行完美的克隆了!………….么?code
克隆克隆,咱们日常把它挂在嘴上,但面对一个对象,咱们真正想克隆的是什么?我想在99%的状况下,咱们想克隆的是对象的数据,而保留它的原型引用和方法引用,所以上面提到的局限中的第二点,基本能够不考虑。如今咱再来看看怎么解决剩下两点。对象
首先搞清什么是循环引用,常见的循环引用有两种:递归
1 2 |
var a = {}; a._self = a; |
这种循环引用能够说非常常见。队列
1 2 3 4 |
var a = {}; var b = {}; a.brother = b; b.brother = a; |
也不是没见过,不过这是典型致使对象内存没法被回收的写法,自己就不推荐。
目前只找到了针对第一种引用的解决方法,来自于Jquery源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
jQuery.extend = jQuery.fn.extend = function() { // options是一个缓存变量,用来缓存arguments[i] // name是用来接收将要被扩展对象的key // src改变以前target对象上每一个key对应的value // copy传入对象上每一个key对应的valu // copyIsArray断定copy是否为一个数组 // clone深拷贝中用来临时存对象或数组的src var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // 处理深拷贝的状况 if (typeof target === "boolean") { deep = target; target = arguments[1] || {}; //跳过布尔值和目标 i++; } // 控制当target不是object或者function的状况 if (typeof target !== "object" && !jQuery.isFunction(target)) { target = {}; } // 当参数列表长度等于i的时候,扩展jQuery对象自身 if (length === i) { target = this; --i; } for (; i < length; i++) { if ((options = arguments[i]) != null) { // 扩展基础对象 for (name in options) { src = target[name]; copy = options[name]; // 防止永无止境的循环,这里举个例子,如var i = {};i.a = i;$.extend(true,{},i);若是没有这个判断变成死循环了 if (target === copy) { continue; } if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) { if (copyIsArray) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src: []; // 若是src存在且是数组的话就让clone副本等于src不然等于空数组。 } else { clone = src && jQuery.isPlainObject(src) ? src: {}; // 若是src存在且是对象的话就让clone副本等于src不然等于空数组。 } // 递归拷贝 target[name] = jQuery.extend(deep, clone, copy); } else if (copy !== undefined) { target[name] = copy; // 若原对象存在name属性,则直接覆盖掉;若不存在,则建立新的属性。 } } } } // 返回修改的对象 return target; }; |
在咱们想办法魔改深拷贝时,先看下以上这么多深拷贝的基本原理:
利用for-in循环遍历对象属性,若是属性值是对象则深拷贝,不是则直接赋值
因而俺眉头一皱发现事情并不简单,俺上一篇博客已经说明:for-in遍历的是对象以及其原型链上可枚举属性,所以想在遍历时对源对象的__proto__
作手脚是根本不存在的,__proto__
以及它的不可枚举属性根本不会被遍历到。能够经过下面的例子看出:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var deepClone = function() {...} // 随便从上面拿一个 var A = function() { this.val = 1; } A.prototype.log = function() { console.log(this.val); } var obj1 = new A(); var obj2 = deepClone(obj1); console.log(obj1); // A {val: 1} console.log(obj2); // {val: 1, log: function(){...}} |
所以,一个解决方法很单纯,就是像上面的jQuery.extend方法同样,本身传入拷贝的目标对象,extend方法本质上只是拓展目标对象的属性,使其得到源对象上的数据,这样一来只要咱们先建立好符合需求的目标对象便可。
另外一种方法则是不采用深拷贝,直接取出须要进行拷贝的对象的数据,而后再利用这份数据来实例化和设置一个新的对象出来:
1 2 3 4 5 6 7 8 9 10 11 |
var Foo = function( obj ){ this.name = obj.name; this.sex = obj.sex }; Foo.prototype.toJSON = funciton(){ return { name: this.name, sex: this.sex }; }; var foo = new Foo({ name: "bandit", sex: "male" }); var fooCopy = new Foo( foo.toJSON() ); |
问题一样获得解决【鼓掌】
回顾一下,没有哪一种方法是万用的魔法 —— 在咱们想要得到一个克隆对象以前,或许最好先搞清楚咱们究竟是在克隆什么,再采用最适合的方法。而非是拘泥于“深拷贝浅拷贝”的说法,去复制一段代码祈祷他能生效。我相信以上的示例代码尚未考虑到克隆对象的全部问题,但它们在合适的场景下可以处理合适的问题。嗯,其实不少事情都是这样蛤【带!】