深拷贝与浅拷贝的区别
我们常说的浅拷贝与深拷贝主要是针对Object,Array这样的引用类型, 为什么? 因为引用类型在JS里面存储的内存地址。
浅拷贝: 只会复制一层对象的属性 深拷贝: 递归复制对象所有层级属性
因此就会存在一个问题, 如果遇到引用类型, 浅拷贝它复制下来的是对象的指针地址, 导致复制后的对象中的引用类型属性是共享的。
下面分别从Array/Object来看看它们有哪些原生的拷贝方法。
Array的拷贝
原生数组支持的拷贝方法有: slice()/concat()/Array.from()/扩展运算符
// 只含基本数据类型的拷贝 const a = [1,2,3] const b = a.slice() const c = [...a] const d = [].concat(a) const e = Array.from(a) a[0] = 0 // [ 0, 2, 3 ] [ 1, 2, 3 ] [ 1, 2, 3 ] [ 1, 2, 3 ] [ 1, 2, 3 ] console.log(a, b, c, d, e) // 包含引用类型的拷贝 const a1 = [1,2,3,{name: 'kobe'}] const b1 = a1.slice() const c1 = [...a1] const d1 = [].concat(a1) const e1 = Array.from(a1) a1[3].name = 'change' // [ 1, 2, 3, { name: 'change' } ] [ 1, 2, 3, { name: 'change' } ]
// [ 1, 2, 3, { name: 'change' } ]
// [ 1, 2, 3, { name: 'change' } ] [ 1, 2, 3, { name: 'change' } ] console.log(a1, b1, c1, d1, e1)
可见这些方法都只是浅拷贝。
Object的拷贝
原生的对象支持拷贝的方法有: Object.assign()/扩展运算符/JSON.parse(JSON.stringify(obj))
// 基本数据类型 const a = { name: 'kobe', age: 10 } const b = {...a} const c = Object.assign({},a) const d = JSON.parse(JSON.stringify(a)) a.name = 'change' // { name: 'change', age: 10 } { name: 'kobe', age: 10 }
// { name: 'kobe', age: 10 } { name: 'kobe', age: 10 } console.log(a,b,c,d) // 引用数据类型 const a1 = { info: { name: 'kobe', age: 10 } } const b1 = {...a1} const c1 = Object.assign({}, a1) const d1 = JSON.parse(JSON.stringify(a1)) a1.info.name = 'change' // { info: { name: 'change', age: 10 } } { info: { name: 'change', age: 10 } }
// { info: { name: 'change', age: 10 } } { info: { name: 'kobe', age: 10 } } console.log(a1,b1,c1,d1) 发现Object.assign()/扩展运算符只是浅拷贝。 至于JSON.parse(JSON.stringify(obj))好像可以, 但是如果你去查询MDN文档会发现。
const test = {
a: function () {
console.log('a')
},
[Symbol('age')]: 10,
name: undefined,
address: null,
time: new Date()
}
const clone = JSON.parse(JSON.stringify(test))
console.log(clone) // { address: null, time: '2019-05-18T14:24:49.050Z' }
手写深拷贝
可见JS本身不能提供深拷贝机制, 需要自己实现一个。
const test = { info: { name: 'kobe', nums: [1,2,3] } } function isObject (obj) { return typeof(obj) === 'object' && obj !== null } function deepClone (source) { const obj = Array.isArray(source) ? [] : {} for (let key in source) { obj[key] = isObject(source[key]) ? deepClone(source[key]) : source[key] } return obj } const copy = deepClone(test) test.info.nums.push(4) // { info: { name: 'kobe', nums: [ 1, 2, 3, 4 ] } } // { info: { name: 'kobe', nums: [ 1, 2, 3 ] } } console.log(test, copy)
环
环就是我们常说的循环引用拷。
const test = { info: { name: 'kobe', nums: [1,2,3] } } test.loop = test; function isObject (obj) { return typeof(obj) === 'object' && obj !== null } function deepClone (source) { const obj = Array.isArray(source) ? [] : {} for (let key in source) { obj[key] = isObject(source[key]) ? deepClone(source[key]) : source[key] } return obj } const copy = deepClone(test) test.info.nums.push(4) console.log(test, copy) // Maximum call stack size exceeded
解决思路如下: 通过一个WeakMap来存储拷贝过的对象, 每一次拷贝之前先向WeakMap询问是否拷贝, 有直接返回没有就拷贝。
所以我们需要修改下代码:
const test = { info: { name: 'kobe', nums: [1,2,3] } } test.loop = test; function isObject (obj) { return typeof obj === 'object' && obj !== null } function deepClone (source, hash = new WeakMap()) { if (hash.has(source)) { return hash.get(source) } const obj = Array.isArray(source) ? [] : {} hash.set(source, obj) for (let key in source) { obj[key] = isObject(source[key]) ? deepClone(source[key], hash) : source[key] } return obj } const copy = deepClone(test) test.info.nums.push(4) console.log(test, copy)
特殊对象的拷贝
由于JS的对象类型太多, 这里我们只考虑比如Date, 正则, 其他自己扩展
const test = { info: { name: 'kobe', nums: [1, 2, 3] }, reg: /[0-9]{4}/gi, Date: new Date() } test.loop = test; function isObject(obj) { return typeof obj === 'object' && obj !== null } function deepClone(source, hash = new WeakMap()) { let obj const Constructor = source.constructor switch (Constructor) { case Date: obj = new Constructor(source.getTime()) break case RegExp: obj = new Constructor(source) break default: if (hash.has(source)) { return hash.get(source) } obj = new Constructor() hash.set(source, obj) break } for (let key in source) { obj[key] = isObject(source[key]) ? deepClone(source[key], hash) : source[key] } return obj } const copy = deepClone(test) test.info.nums.push(4) console.log(test, copy)

浙公网安备 33010602011771号