深浅拷贝
一、数据类型
最新的 ECMAScript 标准定义了 8 种数据类型:
- 7种原始类型(String、Number、Boolean、Null、Undefined、BigInt、Symbol):栈内存,使用 typeof 运算符检查。但
typeof null === "object"
- 引用类型(Object):引用地址-栈内存,指向内容-堆内存,instanceof
二、浅拷贝
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。浅拷贝,引用类型拷贝的就是内存地址。下面简单实现一个浅拷贝:
function shallowClone(obj) {
const newObj = {};
for(let prop in obj) {
if(obj.hasOwnProperty(prop)){
newObj[prop] = obj[prop];
}
}
return newObj;
}
在JavaScript
中,浅拷贝的实现方式有:
Object.assign
Array.prototype.slice()
,Array.prototype.concat()
- 使用拓展运算符实现的复制
1. Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
var obj = { a: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); //wade
注意:当object只有一层的时候,是深拷贝
let obj = {
username: 'kobe'
};
let obj2 = Object.assign({},obj);
obj2.username = 'wade';
console.log(obj);//{username: "kobe"}
2. slice()、concat()
补充说明:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。
const fxArr = ["One", "Two", "Three", { name: 'Jimmy'}]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
fxArrs[3].name = 'Bob';
console.log(fxArr) // ["One", "Two", "Three", { name: 'Bob'}] // 基础类型未改变,引用类型的值改变了
console.log(fxArrs) // ["One", "love", "Three", { name: 'Bob'}]
3. 拓展运算符
const fxArr = ["One", "Two", "Three", { name: 'Jimmy'}]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
fxArrs[3].name = 'Bob';
console.log(fxArr) // ["One", "Two", "Three", { name: 'Bob'}]
console.log(fxArrs) // ["One", "love", "Three", { name: 'Bob'}]
三、深拷贝
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
目前实现深拷贝的方法不多,主要是两种:
1. JSON.parse(JSON.stringify())
原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
但 undefined
、function
、symbol
、正则、 new Date()
、数字对象 会在转换过程中被忽略或不好使。
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj); // false
2. 手写递归
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝
// 实现深度克隆---对象/数组
function deepClone(target) {
// 初始化变量 result 成为最终克隆的数据;判断数据类型
let result, targetType = checkedType(target);
if (targetType === 'Object') {
result = {};
} else if (targetType === 'Array') {
result = [];
} else {
return target;
}
// 遍历目标数据
for (let i in target) {
let value = target[i];
if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
result[i] = deepClone(value); // 递归遍历获取到value值
} else {
result[i] = value;
}
}
return result;
}
// 定义检测数据类型的功能函数
function checkedType(target) {
return Object.prototype.toString.call(target).slice(8, -1); // [object Object] | [object Array]
}
3. Lodash.cloneDeep()
该函数库也有提供_.cloneDeep用来做 Deep Copy
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
四、区别
赋值与浅拷贝
赋值:赋的其实是该对象在栈中的地址,而不是堆中的数据。
浅拷贝:是按位拷贝对象,创建一个新对象,对原始对象属性值的一份精确拷贝。
// 赋值
const fxArr = ["One", "Two", "Three", { name: 'Jimmy'}];
const fxArrs = fxArr;
fxArrs[1] = "love";
fxArrs[3].name = 'Bob';
console.log(fxArr) // ["One", "love", "Three", { name: 'Bob'}] // 原对象一同改变
console.log(fxArrs) // ["One", "love", "Three", { name: 'Bob'}]
// 浅拷贝
const fxArr = ["One", "Two", "Three", { name: 'Jimmy'}];
const fxArrs = [...fxArr];
fxArrs[1] = "love";
fxArrs[3].name = 'Bob';
console.log(fxArr) // ["One", "Two", "Three", { name: 'Bob'}] // 基础类型未改变,引用类型的值改变了
console.log(fxArrs) // ["One", "love", "Three", { name: 'Bob'}]
深拷贝与浅拷贝
对于基本数据类型的拷贝,并没有深浅拷贝的区别,我们所说的深浅拷贝都是对于引用数据类型而言的。
- 浅拷贝:就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。
- 深拷贝:就是对目标的完全拷贝,引用和值都复制了。只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。
类型 | 与原数据是否指向同一对象 | 值为基本数据类型时是否一同改变 | 值为对象时是否一同改变 |
---|---|---|---|
赋值 | 是 | 变 | 变 |
浅拷贝 | 否 | 不变 | 变 |
深拷贝 | 否 | 不变 | 不变 |