目录
深浅拷贝
浅拷贝
拷贝的是对象的指针,修改内容互相影响
深拷贝
整个对象拷贝到另一块内存空间中,修改内容不互相影响
示例1
如下例子:对对象直接复制后,导致原对象值发生改变
let a = {
age: 1
}
let b = a
a.age = 2
console.log(b.age) // 2
解决办法一:
let a = {
age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
解决办法二:
let a = {
age: 1
}
let b = { ...a }
a.age = 2
console.log(b.age) // 1
解决办法三:
let a = ['ant', 'bison', 'camel', 'duck', 'elephant']
let b = a.slice(1, 5)
console.log(b) // ["bison", "camel", "duck", "elephant"]
浅拷贝只解决了第一层的问题,但是如果遇到嵌套对象,就不行了,就得用深拷贝。
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = { ...a }
a.jobs.first = 'native'
console.log(b.jobs.first) // native
解决办法一:
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
但是JSON.parse(JSON.stringify(object))方法也是有局限性的:
- 会忽略undefined和symbol
- 不能序列化函数
- 不能解决循环引用的对象
解决办法二:自己实现深拷贝函数(考虑了对象、数组、Symbol类型以及多层嵌套)
function deepClone(obj) {
function isObject(o) {
return (typeof o === 'object' || typeof o === 'function') && o !== null
}
if (!isObject(obj)) {
throw new Error('非对象')
}
let isArray = Array.isArray(obj)
let newObj = isArray ? [] : {}
Reflect.ownKeys(obj).forEach(key => {
newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
})
return newObj
}
const e = Symbol("e")
const f = Symbol.for("f")
let obj = {
a: [1, 2, 3],
b: {
c: 2,
d: 3
}
}
obj[e] = 'localSymbol'
obj[f] = 'globalSymbol'
let newObj = deepClone(obj)
newObj.b.c = 1
console.log(newObj) // { a: [ 1, 2, 3 ], b: { c: 2, d: 3 }, [Symbol(e)]: 'localSymbol', [Symbol(f)]: 'globalSymbol' }
console.log(newObj[e] === obj[e]) // true
console.log(obj.b.c) // 2
上述函数的问题是没有考虑循环引用
以及来自原型链上的属性
的拷贝。
let obj = {
a: [1, 2, 3],
b: {
c: 2,
d: 3
}
}
obj.e = obj
let newObj = deepClone(obj)
console.log(newObj.e) // 2
>输出
RangeError: Maximum call stack size exceeded
let childObj = Object.create(obj)
let newObj = deepClone(childObj)
console.log('原对象:')
for(let key in childObj){
console.log(childObj[key])
}
console.log('新对象:')
for(let key in newObj){
console.log(newObj[key])
}
>输出
原对象:
[ 1, 2, 3 ]
{ c: 2, d: 3 }
新对象:
解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系。
当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。
这里使用Reflect.ownKeys()
获取所有的键值,同时包括 Symbol
。
for…in
获取当前对象及其原型链上的所有可枚举属性Object.keys
获取当前对象上的所有可枚举属性Object.getOwnPropertyNames
获取当前对象上的所有可枚举和不可枚举属性Object.getOwnPropertySymbols
获取当前对象上所有Symbol属性Reflect.ownKeys
获取当前对象上所有可枚举、不可枚举属性以及Symbol属性Object.prototypeOf
获取对象原型链上一级的对象Reflect.prototypeOf
获取对象原型链上一级的对象
所有通过Object和Reflect方法获取对象的属性,都无法访问到对象原型链上的属性。
Object.keys
是获取到对象属性的所有方法中范围最小的一种方法Reflect.ownKeys
是获取到对象属性的所有方法中范围最大的一种方法- 此外
Reflect.ownKeys = Object.getOwnPropertyNames + Object.getOwnPropertySymbols
function deepClone(obj, wm = new WeakMap()) {
function isObject(o) {
return (typeof o === 'object' || typeof o === 'function') && o !== null
}
if (!isObject(obj)) {
throw new Error('非对象')
}
if (wm.has(obj)) return wm.get(obj); // 新增代码,查哈希表
let isArray = Array.isArray(obj)
let newObj = isArray ? [] : {}
wm.set(obj, newObj); // 新增代码,哈希表设值
Object.getOwnPropertySymbols(obj).forEach(symKey => {
newObj[symKey] = isObject(obj[symKey]) ? deepClone(obj[symKey], wm) : obj[symKey]
})
//使用for in替换Reflect.ownKeys
for( let key in obj){
newObj[key] = isObject(obj[key]) ? deepClone(obj[key],wm) : obj[key]
}
return newObj
}
测试一下:
const e = Symbol('e')
const f = Symbol.for('f')
const g = Symbol.for('g')
let obj = {
a: [1, 2, 3],
b: {
c: 2,
d: 3,
},
}
obj[e] = 'localSymbol'
obj[f] = 'globalSymbol'
let childObj = Object.create(obj)
childObj[g] = 'globalSymbol_'
let newObj = deepClone(childObj)
console.log('原对象:')
for (let key in childObj) {
console.log(childObj[key])
}
while (childObj) { // 循环
Object.getOwnPropertySymbols(childObj).forEach(symKey => {
console.log(childObj[symKey])
})
childObj = Object.getPrototypeOf(Object(childObj))
}
console.log('新对象:')
for (let key in newObj) {
console.log(newObj[key])
}
while (newObj) { // 循环
Object.getOwnPropertySymbols(newObj).forEach(symKey => {
console.log(newObj[symKey])
})
newObj = Object.getPrototypeOf(Object(newObj))
}
>输出
原对象:
[ 1, 2, 3 ]
{ c: 2, d: 3 }
globalSymbol_
localSymbol
globalSymbol
新对象:
[ 1, 2, 3 ]
{ c: 2, d: 3 }
globalSymbol_
此时,上述函数可以深拷贝当前对象或数组的所有可枚举属性、Symbol类型键,以及该对象原型链上的所有可枚举属性。但仍然有一个问题,就是不能拷贝原型链上的Symbol类型键。
需要使用Object.getPrototypeOf
来循环获取上一级对象的Symbol类型键属性。
function deepClone(obj, wm = new WeakMap()) {
function isObject(o) {
return (typeof o === 'object' || typeof o === 'function') && o !== null
}
if (!isObject(obj)) {
throw new Error('非对象')
}
if (wm.has(obj)) return wm.get(obj) // 新增代码,查哈希表
let isArray = Array.isArray(obj)
let newObj = isArray ? [] : {}
wm.set(obj, newObj) // 新增代码,哈希表设值
while (obj) {
Reflect.ownKeys(obj).forEach(key => {
if(obj.propertyIsEnumerable(key)){
newObj[key] = isObject(obj[key]) ? deepClone(obj[key],wm) : obj[key]
}
})
obj = Object.getPrototypeOf(Object(obj))
}
return newObj
}
至此,该函数可以深拷贝当前对象和它原型链上的所有可枚举属性及Symbol属性,结果大家可以去验证。
总结:
浅拷贝
- Object.assign()
- 扩展运算符 …
- Array.prototype.slice()/Array.prototype.concat()
深拷贝
- JSON.parse(JSON.stringify())
- lodash的深拷贝函数