前言
日常开发经常会遇到复用一组数据的场景,这里会遇到对原数据是否有影响的问题,从而引出浅拷贝和深拷贝两个概念
浅拷贝
只对目标对象第一层基础类型赋值,引用类型赋址,所谓赋址的址就是在堆中地址
实现浅拷贝
这里使用Object.keys实现,和for in的区别是for in不仅会拿到对象自身的属性,也会拿到继承的属性
const type = (data) => Object.prototype.toString.call(data)
function qCopy(data) {
// 这里可以添加data非对象的判断。。。。。。
let newObj = type(data) == '[object Array]' ? [] : {};
Object.keys(data).forEach(item=>newObj[item] = data[item])
return newObj
}
var obj1 = { a: 1, b: { b1: 1 } }
var obj2 = qCopy(obj1)
obj2.a = 2
obj2.b.b1 = 2
console.log('obj2---', obj2) // { a: 2, b: { b1: 2 } }
console.log('obj1---', obj1) // { a: 1, b: { b1: 2 } }
var arr1 = [1, 2, { a: 1 }]
var arr2 = qCopy(arr1)
arr2[0] = 2
arr2[2].a = 2
console.log('arr2---', arr2) // [2, 2, { a: 2 }]
console.log('arr1---', arr1) // [1, 2, { a: 2 }]
常见浅拷贝
- Object.assign()
- [...arr]、{...obj}
- [].concat(arr)
- Array.prototype.slice()
// Object.assign()
var obj1 = { a: 1, b: { b1: 1 } }
var obj2 = Object.assign({}, obj1)
obj2.a = 2
obj2.b.b1 = 2
console.log('obj1', obj1) // { a: 1, b: { b1: 2 } }
console.log('obj2', obj2) // { a: 2, b: { b1: 2 } }
// { ...obj }
var obj2 = { ...obj1 }
console.log('obj1', obj1) // { a: 1, b: { b1: 2 } }
console.log('obj2', obj2) // { a: 2, b: { b1: 2 } }
// [].concat(arr)
var arr1 = [1, 2, {a: 1} ]
var arr2 = [].concat(arr1)
arr2[0] = 2
arr2[2].a = 2
console.log('arr1', arr1) // [1, 2, {a: 2} ]
console.log('arr2', arr2) // [2, 2, {a: 2} ]
// slice()
var arr2 = [].concat(arr1)
console.log('arr1', arr1) // [1, 2, {a: 2} ]
console.log('arr2', arr2) // [2, 2, {a: 2} ]
// [...arr]
var arr2 = [...arr1]
console.log('arr1', arr1) // [1, 2, {a: 2} ]
console.log('arr2', arr2) // [2, 2, {a: 2} ]
深拷贝
对目标对象中基础类型赋值,所有引用类型开辟新的地址
JSON.parse(JSON.stringify())
其实日常JSON.parse(JSON.stringify())可以满足绝大部分的需求,接下来我们就来看一下它的问题
function Test() {}
var obj1 = {
a: 1,
b: '2',
c: { c1: 1 },
d: function() {},
e: [1, 2],
f: undefined,
g: null,
h: true,
i: new Date(),
j: /1/,
k: NaN,
l: ()=>123,
m: new Test(),
n: Symbol("KK"),
o: new Error(),
p: Infinity,
x: {}
}
// obj1.n = obj1
var obj2 = JSON.parse(JSON.stringify(obj1))
console.log(obj2)
console.log(obj2.m.__proto__)
console.log(obj1.m.__proto__)
// 打印结果
// obj2属性如下:
// a: 1
// b: "2"
// c: {c1: 1}
// e: [1, 2]
// g: null
// h: true
// i: "2020-12-25T07:56:52.734Z"
// j: {}
// k: null
// m: {}
// o: {}
// p: null
// x: {}
// proto2 { constructor: f Object() }
// proto1 { constructor: f Test(), __proto__: Object }
根据以上打印结果可以看出JSON.parse(JSON.stringify())会造成如下问题:
- 普通函数、箭头函数、Symbol、undefined数据丢失
- Date类型转成了字符串
- 正则、Error类型变成了空对象
- NaN、Infinity变成了null
- 构造函数实例变为普通对象,constructor变成函数Object
继续看问题六:
obj1.x.x1 = obj1.c
var obj2 = JSON.parse(JSON.stringify(obj1))
console.log(obj1.x.x1 === obj1.c) // true
console.log(obj2.x.x1 === obj2.c) // false
// 同级引用创造了新对象
- 同级引用创造了新的对象
继续问题七:
obj1.q = obj1
var obj2 = JSON.parse(JSON.stringify(obj1))
// Uncaught TypeError: Converting circular structure to JSON
// 导致栈溢出
- 父级循环引用导致栈溢出
这是目前发现的问题欢迎补充
lodash cloneDeep
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
obj1.q = obj1
obj1.x.x1 = obj1.c
var obj3 = _.cloneDeep(obj1)
console.log(obj3) // 正常打印,且数据结构正常
console.log(obj1.x.x1 === obj1.c) // true
console.log(obj3.x.x1 === obj3.c) // true
Jquery extend
话说有3年没接触过Jquery
var obj4 = $.extend({}, obj1)
console.log(obj4) // 正常打印,且数据结构正常
console.log(obj1.x.x1 === obj1.c) // true
console.log(obj4.x.x1 === obj4.c) // true
实现深拷贝
const type = (data) => Object.prototype.toString.call(data)
let map = new WeakMap()
function dpCopy(data) {
// 区分[]和{}
let newData = type(data) == '[object Array]' ? [] : {};
// 通过WeakMap存储新老数据,解决同级和父级引用问题
map.set(data, newData)
Object.keys(data).map(item=>{
if(type(data[item]) == ('[object Object]' || '[object Array]')) {
if(map.get(data[item])) { // 存在直接赋值
newData[item] = map.get(data[item])
} else {
// 对象构造函数非Object时继承构造函数
if (data[item].__proto__.constructor === Object) {
newData[item] = dpCopy(data[item])
} else {
newData[item] = dpCopy(data[item])
newData[item].__proto__ = data[item].__proto__
}
}
} else {
newData[item] = data[item]
}
})
return newData
}
function Test() {}
var obj1 = {
a: 1,
b: '2',
c: { c1: 1 },
d: function() {},
e: [1, 2],
f: undefined,
g: null,
h: true,
i: new Date(),
j: /1/,
k: NaN,
l: ()=>123,
m: new Test(),
n: Symbol("KK"),
o: new Error(),
p: Infinity,
x: {}
}
obj1.q = obj1
obj1.x.x1 = obj1.c
var obj2 = dpCopy(obj1)
console.log(obj2) // 正常打印,且数据结构正常
console.log(obj1.x.x1 === obj1.c) // true
console.log(obj2.x.x1 === obj2.c) // true
console.log(obj2.m.__proto__ === Test.prototype) // true
网上看到了while比递归更快,有兴趣的试试
结语
没事走两步!!!