js里浅拷贝与深拷贝
0. 问题背景:
a是数组,b也是数组,赋值写成了 this.a = this.b,本意是一次性的赋值b的值给a, 结果潜在性导致后续代码中a变了b也跟着变了->ahhh,BUG!!!
1. 问题搜索:
深拷贝和浅拷贝的最根本区别是是否真正获取了一个对象的复制实体,而不是引用地址
深拷贝和浅拷贝只针对Arrray和Object这样的类型的(引用类型)
浅拷贝只是复制了引用类型数据的内存地址,深拷贝则是新开辟了一个新的内存用于存放复制的对象
浅拷贝互相关联,深拷贝断绝关系
2. 问题扩展
2.1 浅拷贝的实现
2.1.1 简单的引用复制
function shallowClone(copyObj) { var obj = {}; for ( var i in copyObj) { obj[i] = copyObj[i]; } return obj; } var x = { a: 1, b: { f: { g: 1 } }, c: [ 1, 2, 3 ] }; var y = shallowClone(x); console.log(y.b.f === x.b.f); // true
3 灰色地带:看起来像深拷贝的浅拷贝?
3.1.1 扩展运算符深拷贝数组
let arr = [1, 2, 3, 4, 5, 6];
let arr1 = [...arr];
arr1.push(7);
console.log(arr); //[1, 2, 3, 4, 5, 6]
console.log(arr1); //[1, 2, 3, 4, 5, 6, 7]
当数组是一维数组时,扩展运算符可以进行完全深拷贝,改变拷贝后数组的值并不会影响拷贝源的值。
但是,当数组为多维时:
let arr = [1, 2, 3, 4, 5, 6, [1, 2, 3]];
let arr1 = [...arr];
arr1.push(7);
arr1[arr1.length - 2][0] = 100;
console.log(arr); //[1, 2, 3, 4, 5, 6,[100, 2, 3]]
console.log(arr1); //[1, 2, 3, 4, 5, 6, [100, 2, 3],7]
由上可见,我们不难发现当改变拷贝后数组中第二层数组的值时,arr1arr1.length - 2 = 100;拷贝前数组第二层数组的值也跟着改变了。
对象同理,扩展运算符来实现深拷贝只适用单层的对象。
3.1.2 Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
var x = { a: 1, b: { f: { g: 1 } }, c: [ 1, 2, 3 ] }; var y = Object.assign({}, x); console.log(y.b.f === x.b.f); // true 第二层就是浅拷贝 y.a = 2; console.log(x.a) // 1 只改第一层的话 是深拷贝 同上面的扩展运算符一个意思 只是第一层的深拷贝
3.1.3 Array的slice和concat方法
对比下面的代码段
var array = [1,2,3]; var array_shallow = array; var array_concat = array.concat(); var array_slice = array.slice(0); console.log(array === array_shallow); //true console.log(array === array_slice); //false,“看起来”像深拷贝 console.log(array === array_concat); //false,“看起来”像深拷贝 var array = [1, [1,2,3], {name:"array"}]; var array_concat = array.concat(); var array_slice = array.slice(0); array_concat[1][0] = 5; //改变array_concat中数组元素的值 console.log(array[1]); //[5,2,3] console.log(array_slice[1]); //[5,2,3] array_slice[2].name = "array_slice"; //改变array_slice中对象元素的值 console.log(array[2].name); //array_slice console.log(array_concat[2].name); //array_slice
4. 深拷贝的实现
4.1.1 JSON
//例1 var source = { name:"source", child:{ name:"child" } } var target = JSON.parse(JSON.stringify(source)); target.name = "target"; //改变target的name属性 console.log(source.name); //source console.log(target.name); //target target.child.name = "target child"; //改变target的child console.log(source.child.name); //child console.log(target.child.name); //target child
//例2 var source = { name:function(){console.log(1);}, child:{ name:"child" } } var target = JSON.parse(JSON.stringify(source)); console.log(target.name); //undefined
//例3 var source = { name:function(){console.log(1);}, child:new RegExp("e") } var target = JSON.parse(JSON.stringify(source)); console.log(target.name); //undefined console.log(target.child); //Object {}
这种方法使用较为简单,可以满足基本的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型
但是对于正则表达式类型、函数类型等无法进行深拷贝(而且会直接丢失相应的值)。
还有一点不好的地方是它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。同时如果对象中存在循环引用的情况也无法正确处理。
4.1.2 递归写法
// 简略版本
function deepClone(obj) { var newObj = obj instanceof Array ? []:{}; if(typeof obj !== 'object') { return obj; }else{ for(var i in obj) { newObj[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]; } } return newObj; } var a = [1, 2, 4, 6, "a", "12", [1, 2]]; var b = deepClone(a); a[3] = 7; console.log(a); console.log(b);
// 详细版本 var $ = (function () { 'use strict'; var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' '); function type () { return Object.prototype.toString.call(this).slice(8, -1); } for (var i = types.length; i--;) { $['is' + types[i]] = (function (self) { return function (elem) { return type.call(elem) === self; }; })(types[i]); } return $; })();//类型判断 function copy (obj,deep) { if (obj === null || (typeof obj !== "object" && !$.isFunction(obj))) { return obj; } if ($.isFunction(obj)) { return new Function("return " + obj.toString())(); } else { var name, target = $.isArray(obj) ? [] : {}, value; for (name in obj) { value = obj[name]; if (value === obj) { continue; } if (deep && ($.isArray(value) || $.isObject(value))) { target[name] = copy(value,deep); } else { target[name] = value; } } return target; } }
参考链接:
[1] http://caibaojian.com/javascript-object-clone.html
[2] https://www.jqhtml.com/16976.html
[3]https://www.cnblogs.com/lalalagq/p/9903771.html