一 深、浅拷贝的区别
深、浅拷贝是针对 Object 而言,基本数据类型只有浅拷贝。\
1、层次
- 浅拷贝 只会将对象的各个属性进行依次复制,并不会进行递归复制,,也就是说只会复制目标对象的第一层属性。
- 深拷贝 递归拷贝目标对象的所有属性。
2、是否开辟新的栈
- 浅拷贝 对于目标对象第一层为基本数据类型的数据,直接传值,改变一个对象的值,另一个对象的值不会改变;对于目标对象第一层为引用数据类型的数据,传地址,并且不会开辟新的栈,也就是说,复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变。
- 深拷贝 会开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
二 实现浅拷贝
目标对象属性只有一层时,修改一个对象的值,不会影响另一个对象;目标对象属性有二层及以上时,修改一个对象的值,会影响另一个对象。
- 1、for...in 和 hasOwnProperty
const checkType = (target)=>{
//判断是 Object 还是 Array
return Object.prototype.toString.call(target).slice(8,-1)
}
const shallowClone = (target)=>{
let result
if(checkType(target) === 'Object'){
result = {}
}else if(checkType(target) === 'Array'){
result = []
}
for (let i in target) {
//for in 遍历属性,hasOwnProperty 排除不属于自身的属性
if (target.hasOwnProperty(i)) {
result[i] = target[i];
}
}
return result
}
- 2、Obeject.assign
const obj1 = {x:1,y:2}
const obj2 = Object.assign({},obj1)
obj2.x = 3
console.log(obj1) //{x:1,y:2}
console.log(obj2) //{x:3,y:2}
const obj3 = {
x:1,
y:{
m:2
}
}
const obj4 = Object.assign({},obj3)
obj4.x = 3
obj4.y.m = 4
console.log(obj3) //{x:1,y:{m:4}}
console.log(obj4) //{x:3,y:{m:4}}
- 3、Array.concat()
const arr = [1,2,3,4,[5,6]]
const copy = arr.concat()
copy[0] = 5
copy[4][0] = 11
console.log(arr, copy) // [1,2,3,4,[11,6]] 、[5,2,3,4,[11,6]]
- 4、... 运算符
const arr = [1,2,3,4,[5,6]]
const copy = [...arr]
copy[0] = 5
copy[4][0] = 11
console.log(arr, copy) // [1,2,3,4,[11,6]] 、[5,2,3,4,[11,6]]
三 实现深拷贝
1、用 JSON
const copy = JSON.parse(JSON.stringify(obj))
- 缺点:
-
不支持 Date、正则、undefined、函数、symbol等数据;
-
不支持循环引用(即环状结构);
-
会抛弃对象的 constructor ,也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成 Object。
-
2、用递归
const deepClone = (a)=>{
if(a instanceof Object){ //不考虑 iframe
let result
if(a instanceof Function){ //不能 100% 拷贝
if (a.prototype) { //有 prototype 的是普通函数,JS 的函数分为普通函数、箭头函数、异步函数、generator 函数,后两个比较复杂,这里不考虑
//生成一个新的函数
// result(...args) 和 a(...args) 的参数、返回值都一样,即 result 和 a 的功能相同,所以是 a 的深拷贝
result = function (...args) {return a.apply(this, ...args)}
}else{
result = (...args)=>{return a.apply(undefined,...args)}
}
}else if(a instanceof Array){
result = []
}else if(a instanceof Date){
// a 是日期,日期-0 得到一个数字,new Date(这个数字) 得到一个事件戳
result = new Date(a - 0)
}else if(a instanceof RegExp){
result = new RegExp(a.source,a.flags)
}else{
// 其它对象类型都认为是普通对象,在此不作考虑
result = {}
}
for (let key in a) {
if (a.hasOwnProperty(key)) {
// a = function(){}
// a.b = function(){} ,即 函数里面的属性也是函数
// 如果 result[key] = a[key],传的是地址,修改一个对象的属性会影响另一个对象,必须用递归创造一个新对象
result[key] = deepClone(a[key])
}
}
return result
}else{
//基本数据类型
return a
}
}
缺点:没有考虑循环引用
a.self = a,当拷贝 a 时,会拷贝 a.self ,当拷贝 a.self 时,又会拷贝 a ,一直循环,没有出口。
解决方法:每次深拷贝后,将拷贝结果记录下来,如果下次深拷贝时,发现该属性已经拷贝过,就直接 return 拷贝后的结果。
//const cache = new Map() //cache 如果是全局变量的话,不能清空,每次深拷贝都会有数据残留
const deepClone = (a, cache) => {
//const cache = new Map() 不能放在函数里面声明,否则每调用一次深拷贝都会创建一个 cache,,最后会创建很多个 cache 而不是一个 cache。
//既不能放在外面,又不能放在里面,只能放在参数里面
//第一次深拷贝的时候创建 cache ,之后的递归会将 cache 作为参数传下去,就不会再继续创建 cache 了。
if (!cache) {
// key 有可能是对象,所以不能用 new Object(),只能用 Map(),Object 的 key 只能是 string/symbol
cache = new Map()
}
// 拷贝之前先检查下缓存里面是否有 a 的拷贝
if (cache.get(a)) {
//如果已经拷贝过 a ,就返回拷贝后的结果
return cache.get(a)
}
//不考虑 iframe(浏览器的元素),如果考虑 iframe ,则这句话不能判断 a 是普通对象
if (a instanceof Object) {
let result
//不能 100% 拷贝函数
if (a instanceof Function) {
//有 prototype 的是普通函数,JS 的函数分为普通函数、箭头函数、异步函数、generator 函数,后两个比较复杂,这里不考虑
if (a.prototype) {
//生成一个新的函数
// result(...args) 和 a(...args) 的参数、返回值都一样,即 result 和 a 的功能相同,所以是 a 的深拷贝
result = function (...args) {return a.apply(this, ...args)}
} else {
result = (...args) => {return a.apply(undefined, ...args)}
}
} else if (a instanceof Array) {
result = []
} else if (a instanceof Date) {
// a 是日期,日期-0 得到一个数字,new Date(这个数字) 得到一个事件戳
result = new Date(a - 0)
} else if (a instanceof RegExp) {
result = new RegExp(a.source, a.flags)
} else {
// 其它对象类型都认为是普通对象,在此不作考虑
result = {}
}
//把拷贝后的结果放在缓存中
cache.set(a, result)
for (let key in a) {
//只拷贝对象自身的属性
if (a.hasOwnProperty(key)) {
// a = function(){}
// a.b = function(){} ,即 函数里面的属性也是函数
// 如果 result[key] = a[key],传的是地址,修改一个对象的属性会影响另一个对象,必须用递归创造一个新对象
result[key] = deepClone(a[key], cache)
}
}
return result
} else {
//基本数据类型
return a
}
}
const a = {
number: 1, bool: false, str: "hi", empty1: undefined, empty2: null,
array: [
{name: "frank", age: 18},
{name: "jacky", age: 19}
],
date: new Date(2000, 0, 1, 20, 30, 0),
regex: /.(j|t)sx/i,
obj: {name: "frank", age: 18},
f1: (a, b) => a + b,
f2: function (a, b) { return a + b }
}
//循环引用
a.self = a
const b = deepClone(a)
console.log(b.self === b) // true
console.log(b.self = "hi")
console.log(a.self !== "hi") //true