1. 区别
对于原始值,两者都是直接拷贝一份,且修改拷贝后的值不会影响原来的;对于引用值,浅拷贝只复制指针,当两个指针指向同一个地址时,共享一块内存,修改值会互相影响,而深拷贝会开辟新的内存空间,即修改其中一个的值,不会影响到另外一个值。
2. 如何实现浅拷贝?
-
遍历赋值
function clone(obj) { // 只拷贝对象类型 if (typeof obj !== 'object' || obj == null) return obj; // 根据obj的类型判断是新建一个数组还是对象 var newObj = Array.isArray(obj) ? [] : {}; // 遍历obj for (var key in obj) { //判断是obj的属性才拷贝,因为for in会遍历对象原型链上的属性 if (obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } } return newObj; } -
ES6扩展运算符
var obj = { a:1, arr: [2,3] }; var obj1 = {...obj} -
数组的方法(拷贝数组)
Array.from()Array.prototype.concat()Array.prototype.slice()
-
ES6 Obect.assign()
var obj = { a:1, arr: [2,3] }; var obj1 = Object.assign({}, obj); obj.arr[1] = 5; console.log(obj1.arr[1]);//输出结果:5。
3. 如何实现深拷贝
-
借助JSON
var obj = { a:1, arr: [2,3] }; var obj1 = JSON.parse(JSON.stringify(obj)); obj.arr[1] = 5; console.log(obj1.arr[1]); //输出结果:3。注意:这个方法有局限性,会忽略undefined、symbol类型的数据,只能处理能够被json直接表示的数据结构;也不能解决循环引用的对象。
-
JQuery的extend
jQuery.extend( [deep], target, object1 [, objectN ] )函数的第一个参数可以传一个布尔值, 如果为 true,我们就会进行深拷贝, false 依然当做浅拷贝,这个时候,target 就往后移动到第二个参数。
var obj1 = { a: 1, b: { b1: 1, b2: 2 } }; var obj2 = { b: { b1: 3, b3: 4 }, c: 3 }; var obj3 = { d: 4 } console.log($.extend(obj1, obj2, obj3)); // { // a: 1, // b: { b1: 3, b3: 4 }, // c: 3, // d: 4 // } 以上为浅拷贝。 -
loadash
lodash —— _.clone() / _.cloneDeep() 在lodash中关于复制的方法有两个,分别是_.clone()和_.cloneDeep()。 其中_.clone(obj, true)等价于_.cloneDeep(obj)。 -
浅拷贝+递归
当遇到子成员是引用对象时,使用递归层层复制。
function deepClone(obj) { let type = typeof obj //如果是基本数据类型直接返回 if (type !== 'object' || obj === null) return obj var newObj = Array.isArray(obj) ? [] : {} if (obj && typeof obj === 'object') { for (var k in obj) { if (obj.hasOwnProperty(k)) { newObj[k] = obj[k] && typeof obj[k] === 'object' ? deepClone(obj[k]) : obj[k] } } } return newObj }
但是这种深拷贝并不能解决循环引用的问题。
4. 解决循环引用
假如拷贝的对象中存在循环引用关系,这样在递归中会导致死循环
-
利用数组去记录每个引用对象,看它是否已经拷贝过
//判断是否是一个对象 function isObject(obj) { return typeof obj === 'object' && obj != null } function find(arr, item) { for (var i = 0; i < arr.length; i++) { //判断item是否有被拷贝过 if (arr[i].source === item) { return arr[i] } } return null } function deepClone(source, list) { if (!isObject(source)) return source //用一个数组来记录被拷贝过的引用对象 if (!list) list = []; var target = Array.isArray(source) ? [] : {} var data = find(list, source) if (data) { //如果有被拷贝过,直接返回拷贝的结果,不需要再递归(会造成死循环) return data.target } list.push({ source, target }) for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { if (isObject(source[key])) { //递归调用,注意需要将list传进去,记录所有被拷贝过的引用对象 target[key] = deepClone(source[key], list) } else { target[key] = source[key] } } } return target } -
也可以用一个WeakMap结构去记录对象的引用关系
function deepCopy(obj) { // 记录所有的对象的引用关系 let map = new WeakMap(); function dp(obj) { let result = null; let keys = Object.keys(obj); let key = null, temp = null, existobj = null; existobj = map.get(obj); //如果这个对象已经被记录则直接返回原有的拷贝的结果 if (existobj) { return existobj; } result = Array.isArray(obj) ? [] : {} map.set(obj, result); for (let i = 0, len = keys.length; i < len; i++) { key = keys[i]; temp = obj[key]; if (temp && typeof temp === 'object') { result[key] = dp(temp); } else { result[key] = temp; } } return result; } return dp(obj); }