# 浅拷贝与深拷贝
# 浅拷贝
什么是浅拷贝?如果是对象类型,只拷贝第一层,如果对象的属性又是一个对象,那么此时拷贝的就是这个属性的应用。
function shadowCopy(obj) {
const newObj = {}
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
newObj[prop] = obj[prop]
}
}
return newObj
}
const obj1 = {
name: 'litterStar',
a: {
b: '1',
},
}
const obj2 = shadowCopy(obj1)
obj2.name = 'lucyStar'
obj2.a.b = '2'
console.log(obj1)
// { name: 'litterStar', a: { b: '2' } }
console.log(obj2)
// { name: 'lucyStar', a: { b: '2' } }
可以看到修改 obj2 的 name 属性不会影响 obj1,但是修改 obj2 的 a 属性(是个对象)的 b,就会影响 obj1.a.b
使用下面这些函数得到的都是浅拷贝:
Object.assign
Array.prototype.slice()
、Array.prototype.concat()
- 使用拓展运算符实现的复制
# 深拷贝
什么是深拷贝? 浅拷贝是只拷贝一层,深拷贝会拷贝所有的属性。深拷贝前后两个对象互不影响。
深拷贝的实现:
JSON.parse(JSON.stringify())
- 手写递归函数
- 函数库 lodash (或者其他库)
JSON.parse(JSON.stringify())
有存在以下问题:
- 无法解决循环引用问题
- 无法拷贝特殊的对象,比如:RegExp, Date, Set, Map 等在序列化的时候会丢失。
- 无法拷贝函数
let obj = {
fun: function(){},
syb: Symbol('foo'),
a: undefined,
b: NaN,
c: Infinity,
reg : /^abc$/,
date: new Date(),
set: new Set([1, 2, 3, 4, 4]),
map: new Map([
['name', '张三'],
['title', 'Author']
]),
text:'aaa',
}
let cloneObj = JSON.parse(JSON.stringify(obj));
console.log(cloneObj);
// 结果如下
{
reg: {},
b: null,
c: null,
date: '2020-05-05T09:32:52.533Z',
set: {},
map: {},
text: 'aaa'
}
console.log(typeof obj.date); // object
console.log(typeof cloneObj.date); // string
- 如果 obj 里有函数,undefined,则序列化的结果会把函数或 undefined 丢失;
- 如果 obj 里面有时间对象,则 JSON.stringify 后再 JSON.parse 的结果,时间将只是字符串的形式。而不是时间对象;
- 如果 obj 里有 RegExp、 Set, Map 等,则序列化的结果将只得到空对象;
- 如果 obj 里有 NaN、Infinity 和-Infinity,则序列化的结果会变成 null;
- 循环引用,则直接报错
TypeError: Converting circular structure to JSON
const obj = {
a: '111',
}
obj.b = obj
let cloneObj = JSON.parse(JSON.stringify(obj))
console.log(cloneObj) // TypeError: Converting circular structure to JSON
写一个深拷贝,需要考虑下面几种情况:
- 属性是基本类型
- 属性是对象
- 属性是数组
- 循环引用的情况,比如
obj.prop1 = obj
function deepCopy(originObj, map = new WeakMap()) {
if (typeof originObj === 'object') {
// 判断是都否为数组
const cloneObj = Array.isArray(originObj) ? [] : {}
// 判断是否为循环引用
if (map.get(originObj)) {
return map.get(originObj)
}
map.set(originObj, cloneObj)
for (const prop in originObj) {
cloneObj[prop] = deepCopy(originObj[prop], map)
}
return cloneObj
} else {
return originObj
}
}
const obj1 = {
a: '111',
}
obj1.obj2 = obj1
const aa = deepCopy(obj1)
console.log(aa)
// { a: '111', obj2: [Circular] }
上面的实现存在一些问题:
- 一些特殊类型的对象,比如 Date, 正则,Set,Map 等没有处理
- 使用 typeof 来判断是否是对象是有问题的,typeof null 的结果也是 'object'
完善版本
// 是否为引用类型
function isObject(obj) {
return typeof obj === 'object' || (typeof obj === 'function' && obj !== null)
}
function deepCopy(originObj, map = new WeakMap()) {
// 判断是否为基本数据类型
if (isObject(originObj)) {
// 判断是否为循环引用
if (map.get(originObj)) {
return map.get(originObj)
}
// 判断是否为几种特殊需要处理的类型
let type = [Date, RegExp, Set, Map, WeakMap, WeakSet]
if (type.includes(originObj.constructor)) {
return new originObj.constructor(originObj)
}
// 其他类型
let allDesc = Object.getOwnPropertyDescriptors(originObj)
let cloneObj = Object.create(Object.getPrototypeOf(originObj), allDesc)
// Reflect.ownKeys 可以获取到
for (const prop of Reflect.ownKeys(originObj)) {
cloneObj[prop] =
isObject(originObj[prop]) && typeof originObj[prop] !== 'function'
? deepCopy(originObj[prop], map)
: originObj[prop]
}
return cloneObj
} else {
return originObj
}
}
let obj = {
fun: function () {},
syb: Symbol('foo'),
a: undefined,
b: NaN,
c: Infinity,
reg: /^abc$/,
date: new Date(),
set: new Set([1, 2, 3, 4, 4]),
map: new Map([
['name', '张三'],
['title', 'Author'],
]),
text: 'aaa',
}
let cloneObj = deepCopy(obj)
console.log(cloneObj)
打印的结果如下:
{
fun: [Function: fun],
syb: Symbol(foo),
a: undefined,
b: NaN,
c: Infinity,
reg: /^abc$/,
date: 2020-05-05T13:57:14.904Z,
set: Set { 1, 2, 3, 4 },
map: Map { 'name' => '张三', 'title' => 'Author' },
text: 'aaa'
}
← 浅比较与深比较 实现 Promise →