目录
(1)、JSON.parse(JSON.stringify(obj))(🌟🌟🌟🌟🌟)
1、Object.assign 和 扩展运算符(...)深拷贝踩坑
前言
若想拷贝一个数据,既要看是什么数据类型,也要看用的是什么方法。
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、引用类型的浅拷贝和深拷贝分析
(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
【结论】
引用类型的深拷贝,把引用地址和值一起拷贝过来,一个对象的值改变,另一个对象的值不受影响。
三、引用类型的浅拷贝和深拷贝的方法
只有引用数据类型才有深拷贝与浅拷贝之说。
- 引用类型的浅拷贝:拷贝的仅仅是“引用地址”,两个对象的引用地址对应的还是同一个值,所以,无论改变哪个对象的值,另一个对象对应的值也会改变。
- 引用类型的深拷贝:把引用地址和值一起拷贝过来,一个对象的值改变,另一个对象的值不受影响。
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)、JQuery 的 extend() 方法
$.extend( [deep ], target, object1 [, objectN ] )
deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝
target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
object1 objectN可选。 Object类型 第一个以及第N个被合并的对象。
let a=[0,1,[2,3],4],
b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b);
可以看到,效果与上面方法一样,只是需要依赖JQ库。
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
// }
// }
四、考考你
阿里面试题:请给出最终输出的值?
var a = 1;
var obj = {
b: 2
};
var fn = function () {};
fn.c = 3;
function test (x, y, z) {
x = 4;
y.b = 5;
z.c = 6;
console.log('---函数作用域', [x, y.b, z.c]);
}
test(a, obj, fn);
console.log('---全局作用域', [a, obj.b, fn.c]);
复制上述代码,在浏览器执行一下发现:
当全局作用域和函数作用域同时拥有同名的变量,更改函数里的变量:
- 如果该变量是基本类型则不会改变全局作用域里的同名变量的值;
- 如果该变量是引用类型则会改变全局作用域里的同名变量的值,因为这两个变量的引用地址是相同的,同一个引用地址指向的是同一个值。
拓展:
var a = 1;
var obj = {
b: 2,
c: { d: 3 }
};
var fn = function () {};
fn.e = 4;
function test (x, y, z) {
x = 5;
y.b = 6;
y.c.d = 7
z.e = 8;
console.log('---函数作用域', [x, y.b, y.c.d, z.e]);
}
test(a, obj, fn);
console.log('---全局作用域', [a, obj.b, obj.c.d, fn.e]);
// ---函数作用域 (4) [5, 6, 7, 8]
// ---全局作用域 (4) [1, 6, 7, 8]
从拷贝的角度分析:
函数作用域里的形参 可以看作是从 全局作用域里的变量 拷贝而来,基本类型值是深拷贝,所以拷贝的变量改变后不影响原来的变量。引用类型,局部作用域直接使用全局作用域变量,是浅拷贝,所以拷贝的变量改变后会影响原来的变量。但是,如果局部作用域不直接使用全局作用域变量,而是用 ES6 语法(比如扩展运算符)处理一下,就是深拷贝了:
var a = 1;
var obj = {
b: 2,
c: { d: 3 }
};
var fn = function () {};
fn.e = 4;
function test (x, y, z) {
x = 5;
y.b = 6;
y.c.d = 7
z.e = 8;
console.log('---函数作用域', [x, y.b, y.c.d, z.e]);
}
test(a, { ...obj }, fn);
console.log('---全局作用域', [a, obj.b, obj.c.d, fn.e]);
// ---函数作用域 (4) [5, 6, 7, 8]
// ---全局作用域 (4) [1, 2, 7, 8]
五、应用
1、Object.assign 和 扩展运算符(...)深拷贝踩坑
const arr = [{
type: 1,
flag: false
}, {
type: 2,
flag: true
}, {
type: 3,
flag: true
}];
const obj = {
title: "",
imgUrl: "",
content: "",
}
const newList = arr.map(item => {
const { type, flag } = item;
Object.assign(obj, { type, flag });
return obj;
})
console.log(newList);
// [{
// "title": "",
// "imgUrl": "",
// "content": "",
// "type": 3,
// "flag": true
// },
// {
// "title": "",
// "imgUrl": "",
// "content": "",
// "type": 3,
// "flag": true
// },
// {
// "title": "",
// "imgUrl": "",
// "content": "",
// "type": 3,
// "flag": true
// }]
上述代码,结果显然是错的。因为你拷贝的是变的部分,而不是不变的部分。由于 Object.assign() 以及 扩展运算符(...)在只有一层时是深拷贝——数据改变就会影响最终的结果,所以尽量拷贝不变的一方。解决方案如下:
方案一(推荐):
const arr = [{
type: 1,
flag: false
}, {
type: 2,
flag: true
}, {
type: 3,
flag: true
}];
const obj = {
title: "",
imgUrl: "",
content: "",
}
const newList = arr.map(item => {
const { type, flag } = item;
return {...obj, type, flag};
})
console.log(newList);
// [{
// "title": "",
// "imgUrl": "",
// "content": "",
// "type": 1,
// "flag": false
// },
// {
// "title": "",
// "imgUrl": "",
// "content": "",
// "type": 2,
// "flag": true
// },
// {
// "title": "",
// "imgUrl": "",
// "content": "",
// "type": 3,
// "flag": true
// }]
还有一种办法供参考:
方案二:
const arr = [{
type: 1,
flag: false
}, {
type: 2,
flag: true
}, {
type: 3,
flag: true
}];
const newList = arr.map(item => {
const { type, flag } = item;
return {
title: "",
imgUrl: "",
content: "",
type,
flag
};
})
console.log(newList);
// [{
// "title": "",
// "imgUrl": "",
// "content": "",
// "type": 1,
// "flag": false
// },
// {
// "title": "",
// "imgUrl": "",
// "content": "",
// "type": 2,
// "flag": true
// },
// {
// "title": "",
// "imgUrl": "",
// "content": "",
// "type": 3,
// "flag": true
// }]