说在前面的话
在日常开发项目中,我们会经常遇到这样的问题,例如: 需要复制一份对象或数组,但是又不能改变原始对象,咋办呢?这时js的深浅拷贝就派上用场了。在JS中,除了基本数据类型,其他一切皆对象。
想要了解 “深浅拷贝” 我们首先得要了解js的“数据类型” 及 “栈区和堆区”。
数据类型
- 基本数据类型 (String, Number, Boolean, null, undefined, Symbol, BigInt)
- 引用数据类型 (Object, Array, Data, Math, RegExp)
内存中的栈区(stack)和堆区(heap)
咱们知道,“基本数据类型”在内存中占有固定大小的空间,通过按值访问。“引用数据类型”在内存中大小不固定,所以其在栈中只存放堆内存地址(内存地址是大小固定的),指向的却是堆中的对象,所以它是按引用访问的。假如需要获取“引用数据类型”中的变量,需要先访问栈内存中的地址,然后根据该地址才能获取到相对应的值。
基本数据类型(栈区)
let a = 100;
let b = a;
b = 200;
console.log(a); // a = 100
console.log(b); // b = 200
注释:基本数据类型是按值存放的,故可以直接 “按值访问”,上述a赋值给b,对b数据做修改后,是不会影响原始数据的值。
引用数据类型(堆区)
let a = {
name: 'voledy',
age: 18
};
let b = a;
b.name = '哇哈哈';
console.log(a); // a = {name: '哇哈哈', age: 18}
console.log(b); // b = {name: '哇哈哈', age: 18}
注释:因为a是引用类型,a赋值给b,其实就是将a的内存地址指向b,它们指向的是同一内存地址,所以b的属性改了即a的属性也会跟着变。
浅拷贝和深拷贝
用最简单的一句话概括:当你在目标对象中修改一个值的时候,看对原对象是否有影响。如果有影响那就是浅拷贝,如果没有那就深拷贝。
上面说了一大堆,估计你都看的有点烦了,那现在我们就开始进入今天的正题吧!!!
一 浅拷贝
1. Object.assign('目标对象', obj1, obj2...)
//当定义的对象只有基本类型时,该方法就是深拷贝。
let a = {
name: 'voledy',
age: 18
};
let b = Object.assign({}, a);
b.name = "哇哈哈";
console.log(a); // a = {name: 'voledy', age: 18};
console.log(b); // b = {name: '哇哈哈', age: 18};
//当定义的对象中有引用类型的时,该方法就是浅拷贝。
let a = {
name: 'voledy',
age: 18,
eat:{
type: '苹果',
price: 18,
}
};
let b = Object.assign({}, a);
b.name = "哇哈哈";
b.eat.type = "西瓜";
b.eat.price = 30;
console.log(a); // a = {name: 'voledy', age: 18, eat:{ type: '西瓜', price: '30'}};
console.log(b); // b = {name: '哇哈哈', age: 18, eat:{ type: '西瓜', price: '30'}};
2. ES6中的扩展运算符
//我们通过使用扩展运算符(...)也能实现深拷贝。该方法和上述方法一样只能用于深拷贝第一层的值,当拷贝第二层的值时
仍是引用同一个内存地址。
let a = {
name: 'voledy',
age: 18
};
let b = {...a};
b.name = "哇哈哈";
console.log(a); // a = {name: 'voledy', age: 18};
console.log(b); // b = {name: '哇哈哈', age: 18};
----------------------------- 分割线 -----------------------------
let a = {
name: 'voledy',
age: 18,
eat:{
type: '苹果',
price: 18,
}
};
let b = {...a};
b.name = "哇哈哈";
b.eat.type = "西瓜";
b.eat.price = 30;
console.log(a); // a = {name: 'voledy', age: 18, eat:{ type: '西瓜', price: '30'}};
console.log(b); // b = {name: '哇哈哈', age: 18, eat:{ type: '西瓜', price: '30'}};
3. 数组中的方法:Array.prototype.slice() 和 Array.prototype.concat()
//该方法只能用于深拷贝第一层的值,当拷贝第二层的值时仍是引用同一个内存地址。
------------------------ 代码就不写了,有兴趣的可以自己去试下... ------------------------
总结
浅拷贝会在栈中开辟一个新的内存空间,将原对象一级中的“基本数据类型”复制一份到新的内存空间,所以相互不影响。当对象中有“引用类型”时,它只能拷贝“引用类型”在堆内存中的地址,所以赋值后会影响原对象的值。
二 深拷贝
1. JSON.parse(JSON.stringify(obj))
//该方法可以实现深拷贝。 但是需要注意
1:会忽略undefined,Symbol,函数。
2:在处理new Date() 会出错
3:循环引用会出错
4:不能处理正则,拷贝的是一个空对象
5:继承的属性会丢失
一句话概括:可以转成 JSON 格式的对象才能使用这种方法。
let a = {
name: 'voledy',
age: 18,
fn: function(){},
from: undefined,
to: Symbol('深圳'),
nums: /'g'/,
eat:{
type: '苹果',
price: 18,
}
};
let b = JSON.parse(JSON.stringify(a));
b.name = "哇哈哈";
b.eat.type = "西瓜";
b.eat.price = 30;
console.log(a); // a = {name: 'voledy', age: 18, nums: /'g'/, fn: function(){}, eat:{ type: '苹果', price: '18'}};
console.log(b); // b = {name: '哇哈哈', age: 18, nums: {}, eat:{ type: '西瓜', price: '30'}};
2. 使用递归函数
//写一个递归函数
deepCopy = (source) =>{
const targetObj = source.constructor === Array ? [] : {}; // 先判断是数组还是对象
for(let keys in source){ // 遍历
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,直接递归
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = this.deepCopy(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
};
let a = {
name: 'voledy',
age: 18,
eat:{
type: '苹果',
price: 18,
tt:{
aaa: 1000
}
}
};
let b = this.deepCopy(a);
b.eat.type = '西瓜';
b.eat.tt.aaa = '666666';
console.log(a) // a = {name: 'voledy', age: 18, eat:{ type: '苹果', price: '18', tt:{aaa: 1000}}};
console.log(b) // b = {name: 'voledy', age: 18, eat:{ type: '西瓜', price: '18', tt:{aaa: 666}}};
3. 使用第三方函数库 lodash
总结
深拷贝会在堆中开辟一个新的内存空间,将原对象或数组完整的拷贝一份在新的空间中,两者互不影响,老死而不相往来。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!