深克隆要解决的问题:正确性,完整性,独立性
- 正确性: 值不能变
- 完整性: 不能缺少属性,比如不可枚举属性,symbol, 原型链
- 独立性:不能影响被克隆对象, 区分值的复制还是引用的复制
一共八种数据类型:
七种基本数据类型: String、Number、Boolean、Null、Undefined、Symbol、BigInt
还有一个万恶的Object: Array, Map,Set, WeakMap, WeakSet, Date,和几乎所有通过 new keyword 创建的东西。
浅克隆
展开运算符...
只能扩展和深拷贝第一层的值
const a = [[1], [2], [3]];
const b = [...a];
b.shift().shift();
a // [[], [2], [3]]
b // [[2], [3]]
Object.assign
拷贝源对象自身的并且可枚举的属性
const obj = {};
Object.defineProperty(obj, 'x', { enumerable: false, value: 15 });
obj.test = {
name: '啊'
}
const cloneObj = {};
Object.assign(cloneObj, obj);
cloneObj.test.name = '啊啊'
console.log('obj', obj) // {test: {name: '啊啊'}, x: 15}
console.log('cloneObj', cloneObj) // {test: {name: '啊啊'}}
还有数组的一些方法,都是浅克隆,Array.prototype.concat, Array.prototype.slice等
我们可以看出来浅克隆主要是独立性达不到,修改克隆后的对象会影响原始数据,
深克隆
1. JSON.parse和JSON.stringify
const person = Object.create(
null,
{
x: { value: 'x', enumerable: false },
y: { value: 'y', enumerable: true }
}
)
const symbolName = Symbol(2)
let user = {
name: "松",
age: 22,
firends: [
{
name: "小黄",
age: 22,
}
],
time: new Date(),
error: new Error('error'),
regExp: new RegExp(),
func: function () { },
defined: undefined,
symbol: Symbol(1),
[symbolName]: 1,
nan: NaN,
infinity: Infinity,
person: person
}
const copyUser = JSON.parse(JSON.stringify(user))
console.log('copyUser', copyUser)
// 输出
{
name: '松',
age: 22,
firends: [
{
name: "小黄",
age: 22,
}
],
time: '2021-12-22T06:18:44.171Z',
error: {},
regExp: {},
nan: null,
infinity: null,
person: { y: 'y'},
}
优点:
简单, 方便
缺点:
- 值为Function,Symbol,Undefined,key为Symbol,序列化后会丢失;
- 对象存在循环引用会报错
- Date对象, 序列化后会变为字符串;
- RegExp、Error对象,序列化后得到空对象;
- NaN、Infinity,-Infinity,序列化的结果会变成null
- 序列化只会处理对象的可枚举的自有属性
2. 递归循环
这是网上传播最多的版本,其实有很多问题
function deepClone(obj) {
let newObj = Array.isArray(obj) ? [] : {}
if (obj && typeof obj === "object") {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = (obj && typeof obj[key] === 'object') ? deepClone(obj[key]) : obj[key];
}
}
}
return newObj
}
优点
独立性: 通过递归,深度复制每个属性值,
正确性: 直接赋值,没有经过转化
缺点
- 完整性:其它对象类型都没有考虑,如Date, RegExp等
- 如果一个对象的隐式原型为undefined,比如上方通过Object.create可以创建的隐式原型为空的对象,不存在hasOwnProperty这个方法,会直接报错。
- 没有考虑递归爆栈
- 没有考虑循环引用
终极版
注意现在eval和Function不能直接在浏览器中运行,不然会报错unsafe-eval
function funcClone(func) {
let paramReg = /\((.*?)\)/
let bodyReg = /\{(.*)\}/g
let funcString = func.toString()
if (func.prototype) {
let param = paramReg.exec(funcString)
let body = bodyReg.exec(funcString)
if (body) {
if (param) {
let arrParam = param[1].split(',')
return new Function(...arrParam, body[1])
} else {
return new Function(body[1])
}
}
} else {
return eval(funcString)
}
}
const deepcloneTest = function (obj, hash = new WeakMap()) {
const root = {}
const loopList = [
{
parent: root,
key: undefined,
data: obj,
}
];
while (loopList.length) {
const { parent, data, key } = loopList.pop()
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
if (hash.has(data)) {
parent[key] = hash.get(data)
continue
}
const type = [Date, RegExp, Set, Map]
if (type.includes(data.constructor)) {
parent[key] = new data.constructor(data)
continue
}
const allDesc = Object.getOwnPropertyDescriptors(data)
Object.defineProperties(res, allDesc)
res.__proto__ = Object.getPrototypeOf(data)
hash.set(data, res)
for (let k of Reflect.ownKeys(data)) {
if (data[k] !== null && typeof data[k] === 'object') {
loopList.push({
parent: res,
key: k,
data: data[k],
})
} else if (typeof data[k] === 'function') {
res[k] = funcClone(data[k])
} else {
res[k] = data[k]
}
}
}
return root
}
function createData(deep, breadth = 0) {
var data = {};
var temp = data;
for (var i = 0; i < deep; i++) {
temp = temp['data'] = {};
for (var j = 0; j < breadth; j++) {
temp[j] = j;
}
}
return data;
}
function Person() {
console.log("person")
}
let myPerson = new Person()
const enumer = Object.create(
null,
{
x: { value: 'x', enumerable: false, writable: false },
y: { value: 'y', enumerable: true }
}
)
let obj = {
name: '啊啊',
sex: 1,
boolean: true,
array: [{
apple: 1,
}, 2, 3],
null: null,
undefined: undefined,
Symbol: Symbol(2),
bigint: BigInt(100),
func: function () { console.log("func") },
arrow: () => { console.log("arrow") },
date: new Date(),
regExp: new RegExp(),
person: myPerson,
enumer,
}
obj.loop = obj
deepcloneTest(createData(100000)) // 检查是否爆栈
let newObj = deepcloneTest(obj)
console.log('result', newObj)
console.log('检验方法复制', newObj.func === obj.func)
console.log('检验普通对象', newObj.array === obj.array)
console.log('检验原型', newObj.person.constructor)
console.log(newObj.enumer.y)
newObj.enumer.y = 2
console.log('检验描述对象属性', newObj.enumer.y) // 无法修改, 严格模式下会报错,
console.log('检验循环属性', newObj.loop === obj.loop)
- Object.defineProperties(res, allDesc) 可以复制属性的描述对象
- Object.getPrototypeOf(data) 可以获取原型链
- Reflect.ownKeys 可获取不可枚举和symbol属性
- 各类对象可以使用new keyword实现
- weakMap解决循环引用的问题
- 使用循环解决递归爆栈的问题
WeakSet, WeakMap主要用途是避免内存泄漏,复制没有意义,其次是无法枚举,不知道key,无法拿到value
总结要点:
- 要考虑各种数据类型,普通类型和对象类型,对象又分为普通对象,方法,和其它使用new keyword实现的对象
- 考虑属性的描述对象和原型链
- 方法复制比较特殊,
- 要考虑循环引用
- 考虑递归爆栈
参考文章:
segmentfault.com/a/119000001…
blog.csdn.net/lyt_angular…