一、理解
深拷贝 与 浅拷贝
针对于 引用类型(Object,Array,Function) 来说的
浅拷贝:在栈中分配一块新内存,拷贝需要拷贝的值,
对简单数据类型,就是拷贝值;对复杂数据类型,就是拷贝了一份栈内存储的堆内存的地址
深拷贝:在堆中重新分配新内存,存储新的拷贝数据;
在栈中分配新内存,存储新数据分配的堆的地址。
标准浅拷贝:
// =
let obj1 = obj
obj.a = '88888'
console.log(obj)
console.log( obj1)
结果:
实现方式:
浅拷贝的实现方式:
1. 基于Object.assign()
2. 基于for in = 直接赋值
3. ... 原地展开
深拷贝的三种实现方式:
1. 递归实现(重点)
2. JSON.stringify + JSON.parse 方法
3. JQuery的$extend方法(了解)
其他奇怪的api方法?可能非真正的深拷贝
1. slice 一维数组深拷贝,多维拷贝不彻底(针对数组)
2. Object.assign
3. Object.create
二、一些奇怪的api的使用及测试
1.Object.assign()
Object.assign用法:
用于将源对象的所有可枚举属性到目标对象,返回目标对象
至少需要两个对象做参数:Object.assign(目标对象, 源对象1,...)
只要有一个参数不是对象,就会抛出TypeError错误
如果有同名属性,后覆盖前;只复制自身属性(也会复制Symbol值的属性),不复制不可枚举/继承属性
可用于: 为对象添加属性或方法;克隆对象;合并多个对象;为属性指定默认值
拷贝实现:
// Object.assign
let obj = {
a: 1,
b: 2,
c: [1, 2, 3],
d: {
g:777,
s: {},
f: function() { console.log('f')}
}
}
let obj1 = Object.assign({}, obj)
console.log(obj1)
拷贝结果:
拷贝测试:
对第一层的测试:
obj1.a = 666 // 对第一层的基本数据类型修改=>深拷贝
obj1.c = [] // 对第一层的引用数据类型 => 深拷贝
// obj1.c[0] = '000' // 对第一层的引用数据类型 内的某一项进行修改=>浅拷贝
//obj1.d.g = 8888 //
obj1.d.s.w = '9999999' // 对内层引用数据类型进行修改(添加)=>浅拷贝
console.log(obj)
console.log( obj1)
测试结果:
其他测试:
obj1.c[0] = '000' // 对第一层的引用数据类型 内的某一项进行修改=>浅拷贝
obj1.d.g = 8888 // 对内层基础数据类型修改 => 浅拷贝
obj1.d.s.w = '9999999' // 对内层引用数据类型某一项 进行修改(添加)=>浅拷贝
obj1.d.f = {} // 对内层引用数据类型 整体修改 => 浅拷贝
console.log(obj)
console.log( obj1)
测试结果:
小总结:
对拷贝的第一层的基础数据类型 实现了深拷贝(修改拷贝值,不影响新值);
对深层数据,依然是浅拷贝,修改数据值会影响原数据
注意:当整体修改第一层引用数据后,新对象的整个引用数据的指向应该是修改后的,这时再对引用数据中某一项修改时,不会影响原来的对象;但直接对于第一层引用数据类型的某一项进行修改时,还是会影响原来的对象。
2.Object.create
api用法:
方法用于 创建一个新对象,使用现有的对象来提供新创建的对象的proto
参数: Object.create(proto新建对象的原型对象,[添加到新创建对象的可枚举属性])
返回值:在指定原型对象上添加新属性后的对象
拷贝:
// 3. Object.create 浅拷贝
let obj1 = Object.create(obj)
console.log(obj1) // obj1 是一个空对象,其原型为obj;obj1.__proto__是obj的浅拷贝
console.log(obj1.__proto__ === obj) // true
运行结果:
浅拷贝,新对象的原型为旧对象
obj1 是一个空对象,其原型为obj;obj1.__proto__是obj的浅拷贝
3. ES6 ...扩展运算符
代码实现:
// ES6 扩展运算符
let obj1 = { ...obj }
console.log(obj1)
测试结果同Object.assign
4.slice+concat实现拷贝(针对数组)
slice:
实现及测试:
// 针对数组
let arr = ['a', 'b', 'c', {d:4}]
// slice
let arr1 = arr.slice(0)
// console.log(arr)
// console.log(arr1)
arr1[0] = '1'
arr1[3].d = '777'
console.log(arr)
console.log(arr1)
运行结果:
可见,slice 对于数组中的基础数据类型,能够实现深拷贝,对于引用数据类型,无法实现深拷贝。
concat:
// concat
let arr1 = arr.concat()
// console.log(arr)
// console.log(arr1)
测试结果与slice相同。
三、实现深拷贝的三种方式
1. JSON.stringify + JSON.parse
拷贝实现:
let obj = {
a: 1,
b: 2,
c: [1, 2, 3],
d: {
g:777,
s: {w:'www'},
f: function() { console.log('f')}
},
e: undefined,
p: NaN,
date:new Date()
}
// JSON.stringify + JSON.parse 方法
let obj1 = JSON.parse(JSON.stringify(obj))
console.log(obj)
console.log(obj1)
结果截图:
可见,拷贝对象无函数f、无undefined属性e,即该方法无法实现对函数、对undefined值的拷贝 ;
经测试,为深拷贝。
该方法存在的问题:
1.对象中有时间类型时,序列化后会变成字符串类型;
2.对象中有function、undefined类型的数据,会直接丢失;
3.对象中有NaN、Infinity、-Infinity时,序列化后会显示null;
4.对象循环引用时,会报错。
2.JQuery中的$.extend方法可以实现深拷贝(知道)
3.递归实现深拷贝(待升级完善版)
深拷贝的简单递归实现:
let obj = {
a: 1,
b: 2,
c: [1, 2, 3],
d: {
g:777,
s: {w:'www'},
f: function() { console.log('f')}
},
e: undefined,
p: NaN,
date:new Date()
}
// 递归实现深拷贝--简单版()
function deepClone(obj) {
let newObj = null
// if (obj === null) return obj//???
if (typeof obj == 'undefined') return undefined
if (typeof obj === 'function') return obj//浅拷贝函数?
if (obj.constructor == Date) return new Date(obj)
if(obj.constructor == RegExp) return new RegExp(obj)
if (typeof obj == 'object') {
newObj = obj instanceof Array ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key])
}
}
} else {
newObj = obj
}
return newObj
}
let obj1 = deepClone(obj)
console.log(obj)
console.log(obj1)
运行截图:
复杂版:
// 深拷贝 复杂版?
function checkType(target) {
console.log(Object.prototype.toString.call(target).slice(8,-1))
return Object.prototype.toString.call(target).slice(8,-1)
}
function deepClone2(data) {
const obj = checkType(data) === 'Array' ? [] : {}
let arr = ['Object','Array']
if (arr.includes(checkType(data))) {
for (let key in data) {
let value = data[key]
//value为简单类型,直接赋值
if (!arr.includes(checkType(value))) {
obj[key] = value
} else {
// 定义一个映射,初始化时,将data本身加入映射中
const map = new WeakMap()
// 如果拷贝的是复杂数据类型第一次拷贝后存入map
// 第二次再遇到该值时,直接赋值为null,结束递归
map.set(data, true)
if (map.has(value)) {
obj[key] = null
} else {
map.set(value, true);
obj[key] = deepClone2(value)
}
}
}
} else {
return data
}
return obj
}
let obj1 = deepClone2(obj)
console.log(obj)
console.log(obj1)
结果: