js 的基本数据类型的赋值,就是值传递。引用类型对象的赋值是将对象地址的引用赋值。这时候修改对象中的属性或者值,会导致所有引用这个对象的值改变。如果想要真的复制一个新的对象,而不是复制对象的引用,就要用到对象的深拷贝。
数据类型(基本数据类型和引用数据类型)
基本数据类型(栈内存,引用值,深拷贝)
var a = 3;
let b = a;
b = 4;
console.log(a, b); //a=3,b=4
引用数据类型(堆内存,引用址,指针指向该地址。浅拷贝)
console.log("引用数据类型");
let arr1 = [1, 2];
let arr2 = arr1;
arr2.push(3);
console.log(arr1, arr2); //[1,2,3] [1,2,3]
浅拷贝
1.‘=’赋值。只是将对象的引用赋值
const a = { name: "xiaoMing", age: 20 };
const b = a;
console.log(b); //{name:'xiaoMing',age:20};
a.name = "xiaohong";
console.log(b); //{name:'xiaohong',age:20}
###
2.Object.assign()
Object.assign 是 ES6 的新函数。Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。Object.assgin 只能深拷贝第一层, 深层的还是浅拷贝, 记住这个就行了。
Object.assign(target, ...sources);
参数:
target:目标对象。
sources:任意多个源对象。
返回值:目标对象会被返回。
let target = {};
let source = { a: "koala", b: { name: "程序员成长指北" } };
Object.assign(target, source);
console.log(target); // { a: 'koala', b: { name: '程序员成长指北' } }
source.a = "smallKoala";
source.b.name = "程序员成长指北哦";
console.log(source); // { a: 'smallKoala', b: { name: '程序员成长指北哦' } }
console.log(target); // { a: 'koala', b: { name: '程序员成长指北哦' } }
//Object.assign 是浅拷贝,拷贝的是对象的引用值,如果为引用类型对象时,一级属性为深拷贝,对象中有二级属性的话,则二级属性以后都是浅拷贝。
3.扩展运算符(…)
let obj = { a: 1, b: { c: 1 } };
let obj2 = { ...obj };
obj.a = 2;
console.log(obj); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj.b.c = 2;
console.log(obj); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}
//扩展运算符是浅拷贝,拷贝的是对象的引用值,如果为引用类型对象时,一级属性为深拷贝,如果对象中有二级属性的话,则二级属性以后都是浅拷贝。
如果对象或者数组中包含子数组和子对象,那子数组或者对象为浅拷贝
原因是…遍历时那部分为对象/数组类型指向原来的地址
###
对象
var obj = { a: 1, b: 2, c: { a: 3 }, d: [4, 5] };
var obj1 = obj;
var obj2 = JSON.parse(JSON.stringify(obj)); //深拷贝常用方法
var obj3 = { ...obj };
var obj4 = Object.assign({}, obj);
obj.a = 999;
obj.c.a = -999;
obj.d[0] = 123;
console.log(obj1); //{a: 999, b: 2, c: { a: -999 },d: [123, 5]}
console.log(obj2); //{a: 1, b: 2, c: { a: 3 },d: [4, 5]}
console.log(obj3); //{a: 1, b: 2, c: { a: -999 },d: [123, 5]}
console.log(obj4); //{a: 1, b: 2, c: { a: -999 },d: [123, 5]}
数组
var arr = [1, 2, 3, [4, 5], { a: 6, b: 7 }];
var arr1 = JSON.parse(JSON.stringify(arr)); //深拷贝常用方法
var arr2 = arr;
var arr3 = [...arr];
var arr4 = Object.assign([], arr);
console.log(arr === arr1); //false
console.log(arr === arr2); //true
console.log(arr === arr3); //false
console.log(arr === arr4); //false
arr[0] = 999;
arr[3][0] = -999;
arr[4].a = 123;
console.log(arr1); //[1, 2, 3, [4, 5], {a: 6, b: 7}]
console.log(arr2); //[999, 2, 3, [-999, 5], {a: 123, b: 7}]
console.log(arr3); //[1, 2, 3, [-999, 5], {a: 123, b: 7}]
console.log(arr4); //[1, 2, 3, [-999, 5], {a: 123, b: 7}]
深拷贝
1.手动复制
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }
2.JSON 做字符串转换
用 JSON.stringify 把对象转成字符串,再用 JSON.parse 把字符串转成新的对象。
var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1);
// { body: { a: 10 } } <-- 沒被改到
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// false
这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则。只能序列化对象的可枚举的自有属性。
###
弊端
1.如果 obj 里面有时间对象,则 JSON.stringify 后再 JSON.parse 的结果,时间将只是字符串的形式,而不是对象的形式
eg:
var test = {
name: 'a',
date: [new Date(1536627600000), new Date(1540047600000)],
};
let b;
b = JSON.parse(JSON.stringify(test));
console.log(b);
解决方法,将 new Date()变为字符串,new Date().toString()
2.如果 obj 里有 RegExp(正则表达式的缩写)、Error 对象,则序列化的结果将只得到空对象;
const test = {
name: "a",
date: new RegExp("\\w+"),
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = "test";
//console.error('ddd', test, copyed);
console.log(copyed);
3.如果 obj 里有函数,undefined,则序列化的结果会把函数或 undefined 丢失;
const test = {
name: "a",
date: function hehe() {
console.log("fff");
},
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = "test";
console.error("ddd", test, copyed);
4.如果 obj 里有 NaN、Infinity 和-Infinity,则序列化的结果会变成 null
5.JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果 obj 中的对象是有构造函数生成的, 则使用 JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的 constructor;
function Person(name) {
this.name = name;
console.log(name);
}
const liai = new Person("liai");
const test = {
name: "a",
date: liai,
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = "test";
console.error("ddd", test, copyed);
6.如果对象中存在循环引用的情况也无法正确实现深拷贝;
总结:
用法简单,然而使用这种方法会有一些隐藏的坑:因为在序列化 JavaScript 对象时,所有函数和原型成员会被有意忽略。
通俗点说,JSON.parse(JSON.stringfy(X)),其中 X 只能是 Number, String, Boolean, Array, 扁平对象,即那些能够被 JSON 直接表示的数据结构。
3.递归拷贝
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if (prop === obj) {
continue;
}
if (typeof prop === "object") {
obj[i] = prop.constructor === Array ? [] : {};
arguments.callee(prop, obj[i]);
} else {
obj[i] = prop;
}
}
return obj;
}
var str = {};
var obj = { a: { a: "hello", b: 21 } };
deepClone(obj, str);
console.log(str.a);
4.使用 Object.create()方法
直接使用 var newObj = Object.create(oldObj),可以达到深拷贝的效果。
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if (prop === obj) {
continue;
}
if (typeof prop === "object") {
obj[i] = prop.constructor === Array ? [] : Object.create(prop);
} else {
obj[i] = prop;
}
}
return obj;
}
5.jquery
jquery 有提供一个$.extend 可以用来做 Deep Copy。
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
6.第三方函数
还有一些其它的第三方函数库有深拷贝 function,如 lodash。
文章引用:
https://blog.csdn.net/ljw1412/article/details/79651725
https://www.jianshu.com/p/52db1d0c1780
https://segmentfault.com/a/1190000016440069