这是我参与「第四届青训营 」笔记创作活动的第4天
如果只想看完美深拷贝☞点击这里,快速导航
JS深拷贝与浅拷贝
引入案例
拷贝:数据复制
let num1 = 1;
let num2 = num1;
console.log(num1);
console.log(num2);
let arr = [1, 2, 3, 4];
let Arr = arr;//这里复制的是指针
Arr[0] = 66;
console.log("原来的数组" + arr);
console.log("拷贝的数组" + Arr);
原因
解决方法
数组:slice()和concat()方法,扩展运算符,遍历
let arr = [1, 2, 3, 4];
//let Arr=[];
//arr.forEach(function(element){
// Arr.push(element);
//})
//let Arr = arr.slice();
//let Arr = arr.concat();
//let Arr = [...arr];
Arr[0] = 66;
console.log("原来的数组" + arr);
console.log("拷贝的数组" + Arr);
对象:Object.assign(),遍历
Object.assign(target, ...sources)
参数:
target:目标对象。 sources:任意多个源对象。 返回值:目标对象会被返回
var obj1 = {
a: "hello",
b: {
a: "hello",
b: 21}
};
var cloneObj1= Object.assign({}, obj1);
cloneObj1.a = "changed";
//cloneObj1.b.a = "changed";
console.log(obj1.a); //hello
//console.log(obj1.b.a); // "changed"
调用函数遍历
function simpleClone(initalObj) {
var obj = {};
for ( var i in initalObj) {
obj[i] = initalObj[i];
}
return obj;
}
以上做法仅实现一级拷贝
let arr = [1, [1, 2, 3], {
a: 11,
b: 22,
c: {
c1: 11,
c2: 12
}
}];
let Arr=[];
arr.forEach(function(element){
Arr.push(element);
})
let Arr = arr.slice();
let Arr = arr.concat();
let Arr = [...arr];
Arr[0] = 66;
Arr[1][0] = 66;
Arr[2].a = 66;
Arr[2].c.c1 = 66;
console.log(arr);
console.log(Arr);
深拷贝
如果目标数组或对象只有一层,可以采用以上方法实现深拷贝。
我自己的理解:拷贝之后,如果变量里存的是一个引用值的地址,就是浅拷贝;反之,存的是一个原始值或者函数,就是深拷贝
实现方法
1.jQuery的extend方法
语法:$.extend( [deep ], target, object ) ①deep表示是否深拷贝,为true为深拷贝,为false为浅拷贝 ②target目标对象,其他对象的成员属性将被附加到该对象 ③object拷贝源对象
let arr = [1, [1, 2, 3], {
a: 11,
b: 22,
c: {
c1: 11,
c2: 12
}
}];
let Arr = $.extend(true,[],arr);
Arr[0] = 66;
Arr[1][0] = 66;
Arr[2].a = 66;
Arr[2].c.c1 = 66;
console.log(arr);
console.log(Arr);
2.JSON的序列化和反序列化
用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。
JSON.parse(JSON.stringify(XXXX))
let arr = [1, [1, 2, 3], {
a: 11,
b: 22,
c: {
c1: 11,
c2: 12
}
}];
let Arr = JSON.parse(JSON.stringify(arr));
Arr[0] = 66;
Arr[1][0] = 66;
Arr[2].a = 66;
Arr[2].c.c1 = 66;
console.log(arr);
console.log(Arr);
缺点:
- 它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。
- Date对象, RegExp对象, Error对象等是无法通过这种方式深拷贝。这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。
- 如果原对象中有值为undefined的情况, JSON.stringify 后会丢失
- 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
3.递归拷贝
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : {};
arguments.callee(prop, obj[i]);//调用自身
} else {
obj[i] = prop;
}
}
return obj;
}
var str = {};
var obj = { a: {a: "hello", b: 21} };
deepClone(obj, str);
console.log(str.a);
4.使用Object.create()方法
直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
} else {
obj[i] = prop;
}
}
return obj;
}
👆 2022.08.21
👇 2022.10.22
完美版深拷贝
评价一个深拷贝是否完善,请检查以下问题是否都实现了:
- 基本类型数据是否能拷贝?
- 键和值都是基本类型的普通对象是否能拷贝?
- Symbol作为对象的key是否能拷贝?
- Date和RegExp对象类型是否能拷贝?
- Map和Set对象类型是否能拷贝?
- Function对象类型是否能拷贝?(函数我们一般不用深拷贝)
- 对象的原型是否能拷贝?
- 不可枚举属性是否能拷贝?
- 循环引用是否能拷贝?
测试数据
const obj = {
// =========== 1.基础数据类型 ===========
num: 0, // number
str: '', // string
bool: true, // boolean
unf: undefined, // undefined
nul: null, // null
sym: Symbol('sym'), // symbol
bign: BigInt(1n), // bigint
// =========== 2.Object类型 ===========
// 普通对象
obj: {
name: '我是一个对象',
id: 1
},
// 数组
arr: [0, 1, 2],
// 函数
func: function () {
console.log('我是一个函数')
},
// 日期
date: new Date(0),
// 正则
reg: new RegExp('/我是一个正则/ig'),
// Map
map: new Map().set('mapKey', 1),
// Set
set: new Set().add('set'),
// =========== 3.其他 ===========
[Symbol('1')]: 1 // Symbol作为key
};
// 4.添加不可枚举属性
Object.defineProperty(obj, 'innumerable', {
enumerable: false,
value: '不可枚举属性'
});
// 5.设置原型对象
Object.setPrototypeOf(obj, {
proto: 'proto'
})
// 6.设置loop成循环引用的属性
obj.loop = obj
直接上代码
function deepClone(source) {
// 采用WeakMap 构建 Hash表处理循环引用,WeakMap 有利于垃圾回收
const cache = new WeakMap()
// 策略模式缓存每种对象数据类型的处理方式 返回一个函数
let targetResult = {
Function: function (source) {
return new Function('return ' + source.toString())()
},
// 日期或者正则对象则直接构造一个新的对象返回
Date: function (source) {
return new Date(source)
},
RegExp: function (source) {
return new RegExp(source)
},
// Map 和 Set new 一个新的实例 注意内部存的值也需要进行递归深拷贝
Map: function (source) {
let result = new Map()
cache.set(source, result) // 设置缓存要在递归之前,才能处理循环引用
source.forEach((val, key) => {
result.set(key, clone(val))
})
return result
},
Set: function (source) {
let result = new Set()
cache.set(source, result) // 设置缓存要在递归之前,才能处理循环引用
source.forEach((val, key) => {
result.add(clone(val))
})
return result
},
Array: function (source) {
let result = []
cache.set(source, result) // 设置缓存要在递归之前,才能处理循环引用
source.forEach(val => {
result.push(clone(val))
})
return result
},
Object: function (source) {
// Reflect.ownKeys能够遍历对象的不可枚举属性以及 Symbol 类型,
const keys = Reflect.ownKeys(source)
// Object.getOwnPropertyDescriptors()设置属性描述对象
const allDesc = Object.getOwnPropertyDescriptors(source)
// Object.create()方式继承原型链
const result = Object.create(Object.getPrototypeOf(source), allDesc)
cache.set(source, result) // 设置缓存要在递归之前,才能处理循环引用
// Object.create()是浅拷贝 递归执行实现深拷贝
keys.forEach(key => {
result[key] = clone(source[key])
})
return result
},
}
function clone(source) {
// 基础数据类型 直接返回 包括 null
if (source instanceof Object === false) return source
// 如果有缓存,就从缓存中取
if (cache.has(source)) return cache.get(source)
// [object Object] 截取第9个到倒数第1个字符 Object
let sourceType = Object.prototype.toString.call(source).slice(8, -1)
// 拿到拷贝完成的结果
let result = targetResult[sourceType](source)
return result
}
return clone(source)
}