什么是深浅拷贝
产生深浅拷贝的原因是JavaScript变量存在两种数据类型的值基本数据类型
和引用数据类型
。
而这两种数据类型在内存中的存储方式不同。
- 基本数据类型:基本数据类型的值会存放在栈中
- 引用数据类型:引用数据类型的值会存放在堆中,而指向堆位置的地址值会存放在栈中
基本数据类型的赋值
基本数据类型是将值复制一份给新的变量。
let a = 10
let b = a
console.log(a); //10
console.log(b); //10
a = 20
console.log(a); //20
console.log(b); //10
引用数据类型的赋值
引用数据类型是将地址值复制一份给新的变量,因此地址值相同,所以指向的堆空间也是相同的。
let obj1 = {
a:10,
arr:['red','yellow']
}
let obj2 = obj1
console.log(obj1);//{a: 10, arr: Array(2)}
console.log(obj2);//{a: 10, arr: Array(2)}
obj1.a = 20
console.log(obj1);//{a: 20, arr: Array(2)}
console.log(obj2);//{a: 20, arr: Array(2)}
赋值和深浅拷贝的区别
针对引用类型
而言:
- 赋的其实是该对象在栈中的地址值,而不是堆中的数据
- 浅拷贝:在堆中创建内存,拷贝前后对象中基本数据类型互不影响,但对象中的引用类型共享一块内存,相互影响。
- 深拷贝:在堆中创建内存,对象中引用类型也是互不影响的。
浅拷贝的实现方式
说明:当拷贝的对象只有一层基本类型的属性时,下面方法都是深拷贝
1、Object.assign()
Object.assign()
可以将多个对象拷贝到目标对象。
// Object.assign
let obj1 = {
name: 'hwm',
arr: [1, 2, 3]
}
// 浅拷贝
let obj2 = Object.assign({}, obj1)
obj1.name = 'hwm1'
obj1.arr.push('4')
console.log(obj2);//arr: (4) [1, 2, 3, '4'] name: "hwm"
2、Array.prototype.slice()
slice()
方法用于截取数组
// slice
let arr1 = [1,2,{name:'hwm'}]
let arr2 = arr1.slice(0)
arr1[1] = 0
arr1[2].name = 'abc'
console.log(arr2); // [1, 2, {name: 'abc'}]
3、Array.prototype.concat()
concat()
方法用于合并数组
// concat
let arr1 = [1,2,{name:'hwm'}]
let arr2 = arr1.concat()
arr1[0] = 0
arr1[2].name = 'abc'
console.log(arr2);// [1, 2, {name: 'abc'}]
4、函数库lodash的_.clone()
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.clone(obj1);
console.log(obj1.b.f === obj2.b.f);// true
5、展开运算符 …
let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }
深拷贝的实现方式
1、JSON.parse()和JSON.stringify()
let obj1 = {
name:'hwm',
arr:[1,2,3]
}
let obj2 = JSON.parse(JSON.stringify(obj1))
obj1.name = 'abc'
obj1.arr.push(4)
console.log(obj2); //{name: 'hwm', arr: [1, 2, 3]
缺点
- 如果对象里面有
Date
对象,则拷贝后是字符串 - 如果对象里面有
RegExp、Error
对象,则拷贝后是空对象 - 如果对象里面有
function、undefined
对象,则拷贝后会丢失
2、函数库lodash的_.cloneDeep()
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
3、Jquery.extend()
var $ = require('jquery');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
4、递归实现深拷贝
思路:
- 判断数据类型
- 基本类型------直接复制
- 引用类型------创建对应的类型,逐一复制内容,如果复制的内容依然是引用类型,再此调用自己。
- 循环引用问题------利用缓存将复制过的属性保存起来
// 递归实现深拷贝
function deepClone(src, cache = []) {
// cache用于缓存遍历过的属性
// 用来解决循环引用
for (let i = 0; i < cache.length; i++) {
if (src === cache[i].src) {
return cache[i].dist
}
}
// 获取数据类型
let type = Object.prototype.toString.call(src).slice(8, -1)
// 返回新的对象
let dist = null
// 判断是不是基本数据类型
if (['Number', 'String', 'Boolean', 'Null', 'Undefined'].includes(type)) {
return src
}
// 判断是否是数组
else if (type === 'Array') {
// 创建一个数组
dist = []
}
// 判断是不是对象
else if (type === 'Object') {
dist = {}
}
// 判断是不是时间对象
else if (type === 'Date') {
dist = new Date(src)
}
// 保存缓存
cache.push({ src, dist })
// 遍历对象的属性/数组的项
for (let key in src) {
// 将属性绑定在新对象上,并递归判断当前key的值是什么类型
if (src.hasOwnProperty(key)) {
dist[key] = deepClone(src[key], cache) //传递上一个回调的缓存
}
}
return dist
}