js 对象赋值之浅拷贝深拷贝在实际中应用
我们有时候有这样的需求,需要将一个值赋值给另外一个变量。例如:
var a = 10;
var b = a;
在这里我们首先需要知道基本类型和引用类型的区别。js 有 6 种基本数据类型:Undefined、Null、Boolean、Number、String、Symbol。引用类型有 Array、Date、Function、(Boolean、String…),他们都是复杂数据类型 Object 的实例对象。基本数据类型是直接存放于栈内存中,可直接访问。引用类型存放在堆内存中保存的是一个指针,当直接赋值时,会指向同一个内存空间。
首先我们看基本数据类型赋值前后操作:
var a = 10;
var b = a;
b = 5;
console.log(b); // 5
console.log(a); // 10
从这里可以看出虽然把 a 的值赋值给了 b,但是对 b 修改时并不会影响到 a。也就是说,他们保存在单独的空间里。
再看引用类型赋值前后操作:
var obj = {
a: "111",
b: "222"
};
var newObj = obj;
newObj.a = "aaa";
console.log(obj); // { a: 'aaa', b: '222' }
console.log(newObj); // { a: 'aaa', b: '222' }
从这里可以看出,修改赋值后的 newObj,原来的 obj 也发生了改变。可见他们指向的是同一片内存地址。
在实际应用中经常会有赋值的操作,如果不想影响到之前的数据就需要使用浅拷贝或者深拷贝。
1.浅拷贝
除了上面的直接赋值,还可以使用 Object.assign()进行浅拷贝。
例如:
var obj = {
a: "111",
b: "222"
};
var assObj = Object.assign({}, obj);
assObj.a = "aaa";
console.log(obj); // { a: '111', b: '222' }
console.log(assObj); // { a: 'aaa', b: '222' }
对 Object.assign()拷贝后的数据进行更改,不会改变原来对象里的值。那么为什么它是浅拷贝而不是深拷贝呢,因为 Object.assign()只能深拷贝第一层数据。
例如:
var obj = {
aaa: "123",
bbb: "456",
ccc: {
c: "666",
oo: {
sss: "000"
}
}
};
var assObj = Object.assign({}, obj);
assObj.bbb = "bbbb";
assObj.ccc.c = "cccc";
assObj.ccc.oo.sss = "sss";
console.log(obj); // { aaa: '123', bbb: '456', ccc: { c: 'cccc', oo: { sss: 'sss' } } }
console.log(assObj); // { aaa: '123',bbb: 'bbbb',ccc: { c: 'cccc', oo: { sss: 'sss' } } }
上例可以看出对拷贝后的数据进行修改,虽然第一层数据源对象没有被修改,但是一层以外的数据被修改了。所以 Object.assign()只能深度复制一层对象数据。
使用 JSON.parse(JSON.stringify(obj))进行深拷贝。
var obj = {
aaa: "123",
bbb: "456",
ccc: {
c: "666",
oo: {
sss: "000"
}
}
};
var jsonObj = JSON.parse(JSON.stringify(obj));
jsonObj.bbb = "bbbb";
jsonObj.ccc.c = "ccc";
jsonObj.ccc.oo.sss = "sss";
console.log(obj); //{ aaa: '123', bbb: '456', ccc: { c: '666', oo: { sss: '000' } } }
console.log(jsonObj); // { aaa: '123',bbb: 'bbbb',ccc: { c: 'ccc', oo: { sss: 'sss' } } }
上例可以看到使用 JSON.parse(JSON.stringify())对进行拷贝的对象修改,所有层级的对象都没有影响到源对象。所以可以说 JSON.parse(JSON.stringify())是深拷贝方法。
为什么我描述为“可以说”,因为对象里的值可以为任意引用类型。比如:Array、Date、Function、Boolean、String…
var obj = {
aaa: "123",
bbb: "456",
ccc: {
c: "666",
oo: {
sss: "000"
}
},
foo: function() {
console.log("foo");
},
date: new Date(),
boolean: false,
arr: [1, 2, 3, 4, 5, 6, 7],
exp: new RegExp("e")
};
var jsonObj = JSON.parse(JSON.stringify(obj));
jsonObj.bbb = "bbbb";
jsonObj.ccc.c = "ccc";
jsonObj.ccc.oo.sss = "sss";
console.log(obj);
/* { aaa: '123',
bbb: '456',
ccc: { c: '666', oo: { sss: '000' } },
foo: [Function: foo],
date: 2019-05-28T08:42:05.454Z,
boolean: false,
arr: [ 1, 2, 3, 4, 5, 6, 7 ],
exp: /e/
}
*/
console.log(jsonObj);
/* { aaa: '123',
bbb: 'bbbb',
ccc: { c: 'ccc', oo: { sss: 'sss' } },
date: '2019-05-28T08:42:05.454Z',
boolean: false,
arr: [ 1, 2, 3, 4, 5, 6, 7 ],
exp: {}
}
*/
从上例可以看出,虽然对拷贝后的值改变对象属性值源对象内值没变,但是源对象里的 Function 类型和正则表达式数据却没有拷贝过来。
要达到真正意义上的深拷贝,需要手写递归函数。
function deepClone(obj) {
if (!obj && typeof obj !== "object") {
throw new Error("error arguments");
}
var objClone = Array.isArray(obj)
? []
: Object.prototype.toString.call(obj) === "[object Object]"
? {}
: obj.valueOf();
if (obj && typeof obj === "object") {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = deepClone(obj[key]);
} else {
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
var deep = deepClone1(obj);
deep.bbb = "bbbb";
deep.ccc.c = "ccc";
deep.date = new Date("2018-10-09");
console.log(obj);
/*
{ aaa: '123',
bbb: '456',
ccc: { c: '666', oo: { sss: '000' } },
foo: [Function: foo],
date: 2019-05-29T02:43:12.332Z,
boolean: false,
arr: [ 1, 2, 3, 4, 5, 6, 7 ],
exp: /e/ }
*/
console.log(deep);
/*
{ aaa: '123',
bbb: 'bbbb',
ccc: { c: 'ccc', oo: { sss: '000' } },
foo: [Function: foo],
date: 2018-10-09T00:00:00.000Z,
boolean: false,
arr: [ 1, 2, 3, 4, 5, 6, 7 ],
exp: /e/ }
*/
总结:实际应用中会有很多地方需要对对象进行拷贝。这时候就要看情况使用浅拷贝还是深拷贝。在上文中,能够了解到的知识点有浅拷贝深拷贝以及手写深拷贝方法。