写在前面
如果要拷贝一个数据,既要看是什么数据类型,也要看用的是什么方法。
VN图表示引用数据类型的浅拷贝与深拷贝的关系:
我们都知道数据的类型,分为基本数据类型和引用数据类型
基本数据类型
基本类型包括:字符串、布尔值、数字、undefined、null、symbol。
基本类型的存储方式:基本类型以键值对(名-值)的方式,直接存储在 栈 中。
基本类型的拷贝都是
深拷贝:
let a = 1;
b = a;
b = 2;
b; // 2
a; // 1
当 b=a 时,栈内存会新开辟一个内存:
当你此时修改 a=2,对 b 并不会造成影响。
引用数据类型
引用数据类型包括:数组、对象、Date、RegExp、函数、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)。
引用数据类型的存储方式:引用类型以 " 名**-引用地址-**值 " 的方式,名存在“栈”内存中,值存在“堆”内存中,但是 “栈” 内存会提供一个 “引用地址” 指向 “堆” 内存中的值。
引用类型:将该对象引用地址存储在栈中,然后对象里面的数据存放在堆中。(数组、对象、Date、RegExp、函数、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math))。
引用类型的拷贝:引用类型的拷贝有
浅拷贝和深拷贝之分。
引用类型的浅拷贝和深拷贝分析
(1)、引用类型的浅拷贝分析
对象的浅拷贝,拷贝的仅仅是“引用地址”,不是值。
let a = [0,1,2,3,4],
b = a;
console.log(a === b); // true
a[0]=1;
console.log(a, b); // a: [1,1,2,3,4] b:[1,1,2,3,4]
我们以上面的例子画个图,初始:
当 b=a 进行拷贝时,其实复制的是 a 的引用地址,而并非堆里面的值。
而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。
【结论】引用类型的浅拷贝,拷贝的仅仅是“引用地址”,两个对象的引用地址对应的还是同一个值,所以,无论改变哪个对象的值,另一个对象对应的值也会改变。
(2)、引用类型的深拷贝分析
对象的深拷贝,把引用地址和值一起拷贝过来。
function deepClone(obj){
let objClone = Array.isArray(obj)?[]:{};
if(obj && typeof obj === "object"){
for(key in obj){
if(obj.hasOwnProperty(key)){ //判断ojb子元素是否为对象,如果是,递归复制
if(obj[key] && typeof obj[key] === "object"){
objClone[key] = deepClone(obj[key]);
}else{ //如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let obj = {
a: "hello",
b: {
a: "hello",
b: 21
}
};
// 拷贝
let cloneObj = deepClone(obj);
console.log('-----原a', cloneObj.a); // hello
console.log('-----原b', cloneObj.b); // {a: "hello", b: 21}
更改原对象,看看拷贝过来的对象是否变化 :
obj.a = "changed";
obj.b = {a: "world", b: 25}
console.log('-----新a', cloneObj.a); // hello
console.log('-----新b', cloneObj.b); // {a: "hello", b: 21}
console.log(cloneObj.a === obj.a); // false
【结论】引用类型的深拷贝,把引用地址和值一起拷贝过来,一个对象的值改变,另一个对象的值不受影响。
(3)、引用类型的浅拷贝和深拷贝的方法
只有引用数据类型才有深拷贝与浅拷贝之说。
- 引用类型的浅拷贝:拷贝的仅仅是“引用地址”,两个对象的引用地址对应的还是同一个值,所以,无论改变哪个对象的值,另一个对象对应的值也会改变。
- 引用类型的深拷贝:把引用地址和值一起拷贝过来,一个对象的值改变,另一个对象的值不受影响。
1、浅拷贝的方法
(1)、直接赋值法
var obj = {
a: 1,
b: {
c: 2
}
}
var cloneObj = obj;// 直接赋值是浅拷贝
cloneObj.a = 3;
cloneObj.b.c = 4;
cloneObj;
// {
// a: 3,
// b: {
// c: 4
// }
// }
obj;
// {
// a: 3,
// b: {
// c: 4
// }
// }
(2)、局部作用域直接使用全局作用域变量
局部作用域内直接使用全局作用域变量(使用前不做处理:比如使用 ES6 的拓展运算符等,关于 ES6 新语法对引用类型拷贝的影响下面会讲到)。
var obj = {
a: 1,
b: { c: 2 }
};
function test (x) {
x.a = 3;
x.b.c = 4
console.log('---函数作用域 x', x);
}
test(obj);
console.log('---全局作用域 obj', obj);
// ---函数作用域 x
// {
// a: 3,
// b: {
// c: 4
// }
// }
// ---全局作用域 obj
// {
// a: 3,
// b: {
// c: 4
// }
// }
2、深拷贝的方法
(1)、JSON.parse(JSON.stringify(obj))
let obj = {
a: "hello",
b: {
c: 21
},
d: ["Bob", "Tom", "Jenny"],
e: function() {
alert("hello world");
}
};
let cloneObj = JSON.parse(JSON.stringify(obj));
obj.a = "changed";
obj.b.c = 25;
obj.d = [1, 2, 3];
obj.e = () => { alert("changed") };
// {
// a: "hello",
// b: {
// c: 21,
// },
// d: ["Bob", "Tom", "Jenny"],
// e: undefined
// }
【拓展】在 JavaScript 中,当对象中的属性值为 undefined 时,在执行 JSON.stringify(obj) 时,这些属性会被忽略,因此最终的 JSON 字符串中不会包含这些属性。对象中的属性值为 null 没问题。例如:
const obj = { a: undefined, b: null };
console.log(obj); // 输出 { a: undefined, b: null }
console.log(JSON.stringify(obj)); // 输出 '{ b: null }'
(2)、递归
// 封装一个深拷贝的函数
const deepClone = (obj) => {
let cloneObj = Array.isArray(obj) ? [] : {};
for (let k in obj) {
if (obj.hasOwnProperty(k)) { // 判定 obj 里是否有 k 这个属性。
if(typeof obj[k] === "object"){ // 判定 k 是不是对象(广义)
cloneObj[k] = deepClone(obj[k]);
} else {
cloneObj[k] = obj[k];
}
}
}
return cloneObj;
}
// 测试
let obj = {
a: "hello",
b: {
c: 21
},
d: ["Bob", "Tom", "Jenny"],
e: function() {
alert("hello world");
}
};
const clone = deepClone(obj);
console.log(clone);
obj.a = "changed";
obj.b.c = 25;
obj.d = [1, 2, 3];
obj.e = () => { alert("changed") };
console.log(clone);
// 可见,改变原对象并不影响深拷贝的对象:
// {
// a: "hello",
// b: {
// c: 21,
// },
// d: ["Bob", "Tom", "Jenny"],
// e: function(){
// alert("hello world");
// }
// }
3、ES6 之深拷贝与浅拷贝的实现
(1)、ES6 的 Object.assign() 方法
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
Object.assign(target, ...sources)
参数:
- target:目标对象。
- sources:任意多个源对象。
返回值:
- 目标对象会被返回。
一层为深拷贝,多层为浅拷贝。
一层为深拷贝:
let obj = {
a: "hello",
b: 21
};
let cloneObj= Object.assign({}, obj);
cloneObj.a = "changed";
cloneObj.b = 25;
cloneObj;
// {
// a: "changed",
// b: 25
// }
obj;
// {
// a: "hello",
// b: 21
// }
多层为浅拷贝:
let obj = {
a: "hello",
b:{
c: 21
}
};
let cloneObj= Object.assign({}, obj);
cloneObj.a = "changed";
cloneObj.b.c = 25;
cloneObj;
// {
// a: "changed",
// b: {
// c: 25
// }
// }
obj;
// {
// a: "hello",
// b: {
// c: 25
// }
// }
(2)、ES6 的扩展运算符(...)
一层为深拷贝,多层为浅拷贝。
一层为深拷贝:
let obj = {
a: 'hello',
b: 21
}
let cloneObj = {...obj};
cloneObj.a = 'boy';
cloneObj.b = 25;
cloneObj;
// {
// a: "boy",
// b: 25
// }
obj;
// {
// a: "hello",
// b: 21
// }
多层为浅拷贝:
let obj = {
a: 'hello',
b: {
c: 21
}
}
let cloneObj = {...obj};
cloneObj.a = 'boy';
cloneObj.b.c = 25;
cloneObj;
// {
// a: "boy",
// b: {
// c: 25
// }
// }
obj;
// {
// a: "hello",
// b: {
// c: 25
// }
// }