在js中对象克隆就是把对象值重新赋给一个新对象。克隆也存在深克隆于浅克隆的区别。引用类型的值都会被保存在堆内存中,在栈内存中会存在一个指针指向堆内存中的值。这时,如果只复制了指针,则可以说这个克隆为浅克隆,如果时根据指针找到具体的值,复制值,就可以称之为深克隆。
1、JSON (深克隆)
JSON多用于前后台的数据交互,后台传递字符串类型的JSON数据,前端通过JSON.parse() ,可以转化为一个json对象。
现有一个对象,可以通过JSON.stringify() 转为json字符串。字符串类型的值保存在栈内存当中,所以可以直接赋值达到深度克隆的结果。最后通过JSON.parse(),就可以实现深度克隆了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var obj = { x: 1, y: { a: 1, b: 0, c: [1, 2, 3] } }; // 直接等于号赋值,如果是基本类型的数据,这样足够达到深度克隆 var obj2 = obj; console.log(obj2 == obj); //true 直接复制只是复制对象的指针,还指向同一个对象 // 转化为了字符串 var obj3 = JSON.parse(JSON.stringify(obj)); console.log(obj3 == obj) //false 通过json方法复制后的地址不一样 console.log(obj3); // 达到深度克隆 |
不过这种方法不是万能的,也是有缺陷的。
对象里存在函数,无法克隆。
1 2 3 4 5 6 7 8 9 10 11 12 13 | var obj = { x: 1, y: { a: 1, b: 0, c: [1, 2, 3] }, z: () => { console.log('c'); } }; var obj3 = JSON.parse(JSON.stringify(obj)); console.log(obj3); // {x: 1, y: {…}} 不存在z这个函数 |
2、Object.create()
Object.create()用于把一个对象用于另一个对象的原型,这种以原型的方式来设置一个对象在某个方面也算的上是一种克隆,也可以间接的达到目的。但是不建议,某些方面来说算不上克隆。
1 2 3 4 5 6 7 8 9 10 11 12 13 | var obj = { x: 1, y: { a: 1, b: 0, c: [1, 2, 3] } }; var obj2 = Object.create(obj); console.log(obj2 == obj); //false console.log(obj === obj2.__proto__); // true console.log(obj2); // 输出{} |
因为只是把对象设置为新对象的原型,所以新对象不会和原来的对象相等,但是原对象会与新对象的原型相等。一般来说对克隆出来的对象作修改,被克隆的对象没有被修改,就可以判定是一个深克隆出来的对象。但是要是去修改克隆出来的对象的原型中的属性,肯定会影响被克隆的对象,这样来看,又不像深克隆。所以是不是深克隆,界限不是很明确。不通过 _ _ proto_ _ 去访问原型上的对象进行修改也不会改变被克隆对象。
3、…运算符
都知道…运算符可以用于收集对象的所有属性和数组的所有值。
1 2 3 4 | let arr = [1,2,3,4,5]; let arr2 = [...arr]; console.log(arr === arr2); // false console.log(arr2); // [1, 2, 3, 4, 5] |
克隆出来的他俩不相等,那么这就可以判定这样是深克隆了吗。没错,这就可以这样判定,但是对象就不一定了。
如果对象的属性还是对象,那么就会出现意外,对象的属性都是基本类型的值的话,还是深度克隆。
1 2 3 4 5 6 7 8 9 10 11 12 13 | let obj1 = { a : 1, b : '2', c : true, d : { x : 'd' } } let obj2 = {...obj1}; console.log(obj1 === obj2); // false obj2.d.x = null; // 修改克隆出来的对象的属性d的属性x console.log(obj1, obj2); |
结果可以看到,obj1也被修改了。所以这不是深克隆。
4、assign
assign为ES7中的方法。方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
这个方法就是一个纯浅克隆。
1 2 3 4 5 6 7 8 9 10 | let obj1 = { a : 1, b : '2', c : true, d : { x : 'd' } } let obj2 = Object.assign(obj1); console.log(obj1 === obj2); // true |
6、通用
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 | // 待克隆对象 var obj = { original : 2, arr : [1,2,3], fun : function(){ return 'fun'; }, obj_son : { original_son : 3, obj_son_son : {} } } function clone(obj, obj_clone = {}){ // 判断被克隆的对象是不是一个空对象,如果是空对象就直接return,不在继续往下执行 if(Object.keys(obj).length == 0) return obj_clone; // 遍历对象 for(var key in obj){ // 判断当前遍历到的属性是不是对象的,(不是原型上的,原型上的不克隆) if(obj.hasOwnProperty(key)){ // 判断obj当前属性是原始类型还是引用类型 // 使用typeof 可以简单区分原始类型和引用类型 if(typeof obj[key] == 'function' || typeof obj[key] == 'object'){ // 调用toString方法 toString = Object.prototype.toString; if(toString.call(obj[key]) == '[object Object]'){ obj_clone[key] = {}; } else if(toString.call(obj[key]) == '[object Array]') { obj_clone[key] = []; } else if(toString.call(obj[key]) == '[object Function]'){ obj_clone[key] = new Function(); } // 递归,当有数组或者对象的时候继续往里面深入,重复调用当前的函数 // 第一种方法 // callee是arguments对象的一个属性,指向arguments对象的函数这个函数就是clone(clone=arguments.callee) // arguments.callee(obj[key], obj_clone[key]); // 第二种方法 clone(obj[key], obj_clone[key]); } else { // 原始数据类型直接赋值 obj_clone[key] = obj[key]; } } } return obj_clone; } let newobj = clone(obj); console.log(newobj === obj); // false |