一、前言
我们知道对象相互赋值的一些关系,分别包括:
- 引入的赋值:指向同一个对象,相互之间会影响;
- 对象的浅拷贝:只是浅层的拷贝,内部引入对象时,依然会相互影响;
- 对象的深拷贝:两个对象不再有任何关系,不会相互影响;
1.引用赋值:
info引用obj对象,info和obj指向同一个对象地址,info改变name,obj也跟着改变
const obj = {
name: "why",
friend: {
name: "kobe"
},
foo: function() {
console.log("foo function")
},
[s1]: "abc",
s2: s2
}
const info = obj;
info.name = 'info'
console.log('info, obj>>>>>', info, obj);
打印结果如下:(name都变成了info)
info, obj>>>>> {
name: 'info',
friend: { name: 'kobe' },
foo: [Function: foo],
s2: Symbol(),
[Symbol()]: 'abc'
} {
name: 'info',
friend: { name: 'kobe' },
foo: [Function: foo],
s2: Symbol(),
[Symbol()]: 'abc'
}
#名字跟着变成了info
2.浅拷贝
info对obj进行了浅拷贝,现在info所指对象和obj所指对象有不一样的内存地址,但是他们所指对象中的复杂数据结构的内存地址还是同一个,所以info.friend.name改变也会影响到obj.friend.name
创建方法:三点拓展符号,object.assign()
const s1 = Symbol()
const s2 = Symbol()
const obj = {
name: "why",
friend: {
name: "kobe"
},
foo: function() {
console.log("foo function")
},
[s1]: "abc",
s2: s2
}
const info = { ...obj };
info.name = 'info'
info.friend.name='coder66y'
console.log('info, obj>>>>>', info, obj);
打印结果如下:
info, obj>>>>> {
name: 'info',
friend: { name: 'coder66y' },
foo: [Function: foo],
s2: Symbol(),
[Symbol()]: 'abc'
} {
name: 'why',
friend: { name: 'coder66y' },
foo: [Function: foo],
s2: Symbol(),
[Symbol()]: 'abc'
}
3.深拷贝
info对obj进行了深拷贝,现在info所指对象和obj所指对象有不一样的内存地址,他们所指对象中的复杂数据结构的内存地址也是不一样的,所以info.friend.name改变不会影响到obj.friend.name
常见的深拷贝方法:JSON转换,lodash深拷贝方法
const s1 = Symbol()
const s2 = Symbol()
const obj = {
name: "why",
friend: {
name: "kobe"
},
foo: function() {
console.log("foo function")
},
[s1]: "abc",
s2: s2
}
const info = JSON.parse(JSON.stringify(obj));
info.name = 'info'
info.friend.name='coder66y'
console.log('info, obj>>>>>', info, obj);
打印结果如下:
info, obj>>>>> { name: 'info', friend: { name: 'coder66y' } } {
name: 'why',
friend: { name: 'kobe' },
foo: [Function: foo],
s2: Symbol(),
[Symbol()]: 'abc'
}
虽然以上的方法都可以深拷贝,但是难免有不足,比如json不能拷贝symbal数据,不能拷贝函数,循环引用问题没有解决,所以废话不多说,我们来实现一下手写深拷贝函数
二、手写深拷贝函数
实现以下功能:
- 自定义深拷贝的基本功能;
- 对Symbol的key进行处理;
- 其他数据类型的值进程处理:数组、函数、Symbol、Set、Map;
- 对循环引用的处理;
1. 基本实现(对基本数据类型进行拷贝,其实是浅拷贝)
function deepClone(originValue) {
const newObject = {};
for (const key in originValue) {
newObject[key] = originValue[key];
}
return newObject;
};
// 测试代码
const obj = {
name: 'coder66y',
age: '18'
};
const newObj = deepClone(obj);
console.log('newObj === obj>>>>>', newObj === obj); // false 说明两者所指对象的内存不是同一个地址
console.log('newObj>>>>>', newObj); // { name: 'coder66y', age: '18' }
newObj.name = 'ly';
console.log('newObj, obj>>>>>', newObj, obj); // { name: 'ly', age: '18' } { name: 'coder66y', age: '18' }
这个时候,我们发现打印的确实现了拷贝,但是是深拷贝嘛?检查一下,并不是,现在只是一个浅拷贝
// 测试代码
const obj = {
name: 'coder66y',
age: '18',
friend: {
name: 'lss',
age: '21'
}
};
const newObj = deepClone(obj);
console.log('newObj === obj>>>>>', newObj === obj); // false 说明两者所指对象的内存不是同一个地址
console.log('newObj>>>>>', newObj); // { name: 'coder66y', age: '18' }
newObj.name = 'ly';
console.log('newObj, obj>>>>>', newObj, obj); // { name: 'ly', age: '18' } { name: 'coder66y', age: '18' }
newObj.friend.name = '熊大';
console.log('newObj, obj>>>>>', newObj, obj);
// { name: 'ly', age: '18', friend: { name: '熊大', age: '21' } } { name: 'coder66y', age: '18', friend: { name: '熊大', age: '21' } }
newObj.friend.name变化,obj.friend.name也跟着变化了,明显这是一个浅拷贝
2. 深拷贝(对其他类型进行深拷贝)
测试代码
// 测试代码
let s1 = Symbol();
let s2 = Symbol('s2');
const obj = {
name: 'coder66y',
age: '18',
friend: {
name: 'lss',
age: '21',
address: {
city: '杭州',
}
},
// Symbol作为key和value
s1: s1,
s2: s2,
[s1]: 's1',
[s2]: 's2',
// 数组类型
hobbies: ['唱歌', '跳舞', '画画'],
// 函数类型
hobby() {
console.log('写代码');
},
set: new Set(['aaa', 'bbb']),
map: new Map([['1', '111'], ['3', '333']]),
};
最常见的对对象深拷贝
function isObject(originValue) {
if(['object', 'function'].includes(typeof originValue)) return true;
return false;
}
function deepClone(originValue) {
if (!isObject(originValue)) return originValue; // 不是对象直接返回
// 判断传入的对象是数组, 还是对象
const newObj = Array.isArray(originValue) ? []: {}
for (const key in originValue) {
newObj[key] = deepClone(originValue[key])
}
return newObj;
}
const newObj = deepClone(obj);
console.log('newObj === obj>>>>>', newObj === obj); // false 说明两者所指对象的内存不是同一个地址
newObj.name = 'ly'; // 没有影响到原来的obj
newObj.friend.name = '熊大'; // 没有影响到原来的obj
newObj.hobbies = ['buxihuan'] // 没有影响到原来的obj
console.log('newObj>>>>>', newObj, obj);
测试结果如下:
newObj === obj>>>>> false
newObj>>>>> {
name: 'ly',
age: '18',
friend: { name: '熊大', age: '21', address: { city: '杭州' } },
s1: Symbol(s1),
hobbies: [ 'buxihuan' ],
hobby: {},
set: {},
map: {}
} {
name: 'coder66y',
age: '18',
friend: { name: 'lss', age: '21', address: { city: '杭州' } },
s1: Symbol(s1),
hobbies: [ '唱歌', '跳舞', '画画' ],
hobby: [Function: hobby],
set: Set(2) { 'aaa', 'bbb' },
map: Map(2) { '1' => '111', '3' => '333' },
[Symbol(s1)]: 's1',
[Symbol(s2)]: 's2'
}
发现此时的对象和数组都实现了深拷贝,newObj的更改,没有影响到原来的obj
但是我们发现函数并没有拷贝过来, 接着下一步:
实现函数的深拷贝
如果函数有返回值,由于函数本身内存性质的不同,其返回的值会重新占有内存,就不存在相互影响的问题;所以函数的深拷贝可以直接复用原来的函数,新的对象中的方法,指向该函数即可;
function isObject(originValue) {
if(['object', 'function'].includes(typeof originValue)) return true;
return false;
}
function deepClone(originValue) {
// 新增
if(typeof originValue === 'function') return originValue; // 是函数直接返回
if (!isObject(originValue)) return originValue; // 不是对象直接返回
// 判断传入的对象是数组, 还是对象
const newObj = Array.isArray(originValue) ? []: {}
for (const key in originValue) {
newObj[key] = deepClone(originValue[key])
}
return newObj;
}
const newObj = deepClone(obj);
console.log('newObj === obj>>>>>', newObj === obj); // false 说明两者所指对象的内存不是同一个地址
console.log('newObj>>>>>', newObj, obj);
打印结果:
newObj === obj>>>>> false
newObj>>>>> {
name: 'coder66y',
age: '18',
friend: { name: 'lss', age: '21', address: { city: '杭州' } },
s1: Symbol(s1),
hobbies: [ '唱歌', '跳舞', '画画' ],
hobby: [Function: hobby],
set: {},
map: {}
} {
name: 'coder66y',
age: '18',
friend: { name: 'lss', age: '21', address: { city: '杭州' } },
s1: Symbol(s1),
hobbies: [ '唱歌', '跳舞', '画画' ],
hobby: [Function: hobby],
set: Set(2) { 'aaa', 'bbb' },
map: Map(2) { '1' => '111', '3' => '333' },
[Symbol(s1)]: 's1',
[Symbol(s2)]: 's2'
}
此时,函数实现了深拷贝, newObj.hobby与obj.hooby一样, 实现复用
symbol类型
由上面的打印可以看到Symbol对象作为key的时候,是可以被“拷贝”过来的,但是打印newObj.s1 === obj.s1会发现改值为true,意思是他们两的s1指的是同一个,那么会出现相互影响的问题,现在来完善:
function isObject(originValue) {
if(['object', 'function'].includes(typeof originValue)) return true;
return false;
}
function deepClone(originValue) {
// 新增
if (typeof originValue === 'symbol') return Symbol(originValue.description); // 返回新建的Symbol类型
if (typeof originValue === 'function') return originValue; // 是函数直接返回
if (!isObject(originValue)) return originValue; // 不是对象直接返回
// 判断传入的对象是数组, 还是对象
const newObj = Array.isArray(originValue) ? []: {}
for (key in originValue) {
newObj[key] = deepClone(originValue[key])
}
return newObj;
}
const newObj = deepClone(obj);
console.log('newObj === obj>>>>>', newObj === obj); // false 说明两者所指对象的内存不是同一个地址
console.log('newObj.s1 === obj.s1>>>>>', newObj.s1 === obj.s1); // false
此时newObj.s1 === obj.s1为false;打印结果如下:
newObj === obj>>>>> false
newObj.s1 === obj.s1>>>>> false
newObj>>>>> {
name: 'coder66y',
age: '18',
friend: { name: 'lss', age: '21', address: { city: '杭州' } },
s1: Symbol(),
s2: Symbol(s2),
hobbies: [ '唱歌', '跳舞', '画画' ],
hobby: [Function: hobby],
set: {},
map: {}
} {
name: 'coder66y',
age: '18',
friend: { name: 'lss', age: '21', address: { city: '杭州' } },
s1: Symbol(),
s2: Symbol(s2),
hobbies: [ '唱歌', '跳舞', '画画' ],
hobby: [Function: hobby],
set: Set(2) { 'aaa', 'bbb' },
map: Map(2) { '1' => '111', '3' => '333' },
[Symbol()]: 's1',
[Symbol(s2)]: 's2'
}
发现symbol作为key的时候,并没有被复制,所以需要完善:
因为symbol类型使用for... of...的方法不能遍历,可以用Object.getOwnPropertySymbols()获取所有的symbol类型的key,然后遍历,给新的对象赋值;
function isObject(originValue) {
if(['object', 'function'].includes(typeof originValue)) return true;
return false;
}
function deepClone(originValue) {
// key是symbol类型(新增)
const symbalArr = Object.getOwnPropertySymbols(originValue);
symbalArr.forEach(item => {
newObj[item] = deepClone(originValue[item])
})
// value是Symbol类型
if (typeof originValue === 'symbol') return Symbol(originValue.description); // 返回新建的symbol类型
// value是函数类型
if(typeof originValue === 'function') return originValue; // 是函数直接返回
if (!isObject(originValue)) return originValue; // 不是对象直接返回
// 判断传入的对象是数组, 还是对象
const newObj = Array.isArray(originValue) ? []: {}
for (const key in originValue) {
newObj[key] = deepClone(originValue[key])
}
return newObj;
}
const newObj = deepClone(obj);
newObj[s1] = 's2'
console.log('obj[s1], newObj[s1]>>>>>', obj[s1], newObj[s1]); // s1, s2
console.log('newObj>>>>>', newObj, obj);
打印结果如下:
obj[s1], newObj[s1]>>>>> s1 s2
newObj>>>>> {
name: 'coder66y',
age: '18',
friend: { name: 'lss', age: '21', address: { city: '杭州' } },
s1: Symbol(),
s2: Symbol(s2),
hobbies: [ '唱歌', '跳舞', '画画' ],
hobby: [Function: hobby],
set: {},
map: {},
[Symbol()]: 's2',
[Symbol(s2)]: 's2'
} {
name: 'coder66y',
age: '18',
friend: { name: 'lss', age: '21', address: { city: '杭州' } },
s1: Symbol(),
s2: Symbol(s2),
hobbies: [ '唱歌', '跳舞', '画画' ],
hobby: [Function: hobby],
set: Set(2) { 'aaa', 'bbb' },
map: Map(2) { '1' => '111', '3' => '333' },
[Symbol()]: 's1',
[Symbol(s2)]: 's2'
}
现在不仅打印出了Symbol类型的key,而且也不会影响原有被拷贝的数据;
Set类型和Map类型:
set类型和map类型,此时不能用typeof 来区分set和map, 因为typeof new Set() = 'object', 变成instanceof 来进行类型判断
function isObject(originValue) {
if(['object', 'function'].includes(typeof originValue)) return true;
return false;
}
function deepClone(originValue) {
// set类型,此时不能用typeof 来区分set, 因为typeof new Set() = 'object', 变成instanceof 来进行类型判断
if ( originValue instanceof Set ) return new Set(originValue);
if ( originValue instanceof Map ) return new Map(originValue);
// value是Symbol类型
if (typeof originValue === 'symbol') return Symbol(originValue.description); // 返回新建的symbol类型
// value是函数类型
if(typeof originValue === 'function') return originValue; // 是函数直接返回
if (!isObject(originValue)) return originValue; // 不是对象直接返回
// 判断传入的对象是数组, 还是对象
const newObj = Array.isArray(originValue) ? []: {}
for (key in originValue) {
newObj[key] = deepClone(originValue[key])
}
// key是symbol类型
const symbalArr = Object.getOwnPropertySymbols(originValue);
symbalArr.forEach(item => {
newObj[item] = deepClone(originValue[item])
})
return newObj;
}
const newObj = deepClone(obj);
newObj.set.add(1);
newObj.map.set(0, 'o');
console.log('newObj>>>>>', newObj, obj);
打印结果如下:
newObj>>>>> {
name: 'coder66y',
age: '18',
friend: { name: 'lss', age: '21', address: { city: '杭州' } },
s1: Symbol(),
s2: Symbol(s2),
hobbies: [ '唱歌', '跳舞', '画画' ],
hobby: [Function: hobby],
set: Set(3) { 'aaa', 'bbb', 1 },
map: Map(3) { '1' => '111', '3' => '333', 0 => 'o' },
[Symbol()]: 's1',
[Symbol(s2)]: 's2'
} {
name: 'coder66y',
age: '18',
friend: { name: 'lss', age: '21', address: { city: '杭州' } },
s1: Symbol(),
s2: Symbol(s2),
hobbies: [ '唱歌', '跳舞', '画画' ],
hobby: [Function: hobby],
set: Set(2) { 'aaa', 'bbb' },
map: Map(2) { '1' => '111', '3' => '333' },
[Symbol()]: 's1',
[Symbol(s2)]: 's2'
}
新的对象map和set值的改变,并没有影响到原来的对象, 深拷贝成功
针对类型的完整的深拷贝函数:
function isObject(originValue) {
if(['object', 'function'].includes(typeof originValue)) return true;
return false;
}
function deepClone(originValue) {
// set类型,此时不能用typeof 来区分set, 因为typeof new Set() = 'object', 变成instanceof 来进行类型判断
if ( originValue instanceof Set ) return new Set(originValue);
if ( originValue instanceof Map ) return new Map(originValue);
// value是Symbol类型
if (typeof originValue === 'symbol') return Symbol(originValue.description); // 返回新建的symbol类型
// value是函数类型
if(typeof originValue === 'function') return originValue; // 是函数直接返回
if (!isObject(originValue)) return originValue; // 不是对象直接返回
// 判断传入的对象是数组, 还是对象
const newObj = Array.isArray(originValue) ? []: {}
for (key in originValue) {
newObj[key] = deepClone(originValue[key])
}
// key是symbol类型
const symbalArr = Object.getOwnPropertySymbols(originValue);
symbalArr.forEach(item => {
newObj[item] = deepClone(originValue[item])
})
return newObj;
}
3.实现循环引用的深拷贝
使用map数据结构,使用WeakMap()
为什么使用weakMap 而不是map?
因为弱引用有利于垃圾回收
function isObject(originValue) {
if(['object', 'function'].includes(typeof originValue)) return true;
return false;
}
function deepClone(originValue, map = new WeakMap()) {
// value是set类型,此时不能用typeof 来区分set, 因为typeof new Set() = 'object', 变成instanceof 来进行类型判断
if ( originValue instanceof Set ) return new Set(originValue);
if ( originValue instanceof Map ) return new Map(originValue);
// value是Symbol类型
if (typeof originValue === 'symbol') return Symbol(originValue.description); // 返回新建的symbol类型
// value是函数类型
if(typeof originValue === 'function') return originValue; // 是函数直接返回
if (!isObject(originValue)) return originValue; // 不是对象直接返回
if (map.has(originValue)) {
return map.get(originValue)
}
// 判断传入的对象是数组, 还是对象
const newObj = Array.isArray(originValue) ? []: {}
map.set(originValue, newObj)
for (const key in originValue) {
newObj[key] = deepClone(originValue[key], map)
}
// key是symbol类型
const symbalArr = Object.getOwnPropertySymbols(originValue);
symbalArr.forEach(item => {
newObj[item] = deepClone(originValue[item])
})
return newObj;
}
obj.info = obj;
const newObj = deepClone(obj);
newObj.info.info = {0: 'o'}
console.log(newObj.info, obj.info);
打印如下:已经解决循环引用问题,newObj的info改变没有影响到原来的obj
{ '0': 'o' } <ref *1> {
name: 'coder66y',
age: '18',
friend: { name: 'lss', age: '21', address: { city: '杭州' } },
s1: Symbol(),
s2: Symbol(s2),
hobbies: [ '唱歌', '跳舞', '画画' ],
hobby: [Function: hobby],
set: Set(2) { 'aaa', 'bbb' },
map: Map(3) { '1' => '111', '3' => '333', 'obj' => { o: 0 } },
info: [Circular *1],
[Symbol()]: 's1',
[Symbol(s2)]: 's2'
}