共计 2570 个字符,预计需要花费 7 分钟才能阅读完成。
JavaScript 中的数据类型次要分为 根本数据类型 和援用数据类型 。
常见根本数据类型次要有:undefined, null, 布尔值, 字符串和数值;
援用类型次要是对象 (Object)。
要探讨对象的深浅拷贝问题,首先要理解下 JS 中的堆内存和栈内存的。
栈内存 (stack) 和堆内存(heap)
栈内存中变量个别都是已知大小或者范畴有下限的,可看做简略存储,给人整齐划一的感觉。次要用于存储各种根本类型的变量,包含 Boolean、Number、String、Undefined、Null,以及对象变量的指针。
而堆内存贮存的数据大小,往往都是未知的。对象自身是贮存在堆内存中的。
对堆栈内存数据有点理解之后咱们持续想,当初有 obj1 这么一个对象,咱们心愿依据 obj1 拷贝出一个对象 obj2 来,当初咱们有三大类形式:
- 间接赋值
- 浅拷贝
- 深拷贝
赋值
间接将 obj1 赋值给 obj2,其实赋予的值是 obj1 的指针,此时的赋值,只是“赋址”。也就是 obj1 和 obj2 在栈内存中的值都是 obj1 的指针,都指向堆内存中的 obj1。所以当咱们扭转 obj1 或 obj2 的任何一个的数据时,另一个都会扭转(两者其实是同一对象)。
var obj1 = { | |
name: "greennn", | |
age: 25, | |
bestfriend: { | |
name: "xiaoming", | |
age: 23 | |
} | |
}; | |
var obj2 = obj1; | |
obj2.age = 20; | |
obj1.bestfriend.name = "xiaohong"; | |
console.log(obj1.age); // 20 | |
console.log(obj2.bestfriend.name); //"xiaohong" | |
console.log(obj1 === obj2); // true |
浅拷贝
浅拷贝会将对象的第一层属性值独立的拷贝给新对象。
浅拷贝能够通过创立一个新的空对象,for…in 遍历现有对象的属性增加给新对象;代码更简洁的形式是 Object.assign()办法。也能够应用第三方库,如【Underscore】_.clone()等。
咱们先看看第一种形式封装的写法。
function shallowCopy(obj) {let copy = Array.isArray(obj) ? [] : {}; | |
for (let i in obj) {copy[i] = obj[i]; | |
} | |
return copy; | |
} | |
var obj2 = shallowCopy(obj1); | |
... |
这里咱们用 Object.assign()办法实现浅拷贝,展现后果。
当对象只有一层属性时:
var obj1 = { | |
name: "greennn", | |
age: 25 | |
}; | |
var obj2 = Object.assign({}, obj1 ); | |
obj2.age = 20; | |
console.log(obj1.age) // 25 | |
console.log(obj1 === obj2) // false |
当初 obj1 和 obj2 看上去就是两个独立的对象了,的确起到了“拷贝”的成果。但浅拷贝的“浅”字通知咱们这里必定有陷阱,接下来咱们看看对象的某个属性是对象时的状况。
var obj1 = { | |
name: "greennn", | |
age: 25, | |
bestfriend: { | |
name: "xiaoming", | |
age: 23 | |
} | |
}; | |
var obj2 = Object.assign({}, obj1 ); | |
obj2.age = 20; | |
obj1.bestfriend.name = "xiaoding"; | |
console.log(obj1.age) // 25 | |
console.log(obj2.bestfriend.name); // "xiaoding" | |
console.log(obj1 === obj2) // false |
上例就能看进去,浅拷贝只是复制了一层属性,而且当原对象有多层属性时,第一层的某个属性 prop1 的值指向一个对象时,理论记录的是指针,复制的值也是该属性值对象的指针,即新旧两个对象的 prop1 属性记录都是指向同一对象的指针。看上面示意图会更分明一些:
深拷贝
深拷贝是指将每一层的属性都独立拷贝给新对象,实现两个对象的齐全隔离。
深拷贝的实现有以下几个形式:
- 通过 JSON 转化
这个形式代码简洁,但它存在的问题是只能正确处理 Number, String, Boolean, Array, 等扁平对象,即那些可能被 json 间接示意的数据结构。无奈复制对象中的函数,会疏忽对象中的 undefined。
function jsonCopy(obj) {return JSON.parse(JSON.stringify(obj)); | |
} | |
var obj2 = jsonCopy({a:1}); |
- for···in 循环加递归
只是一段简略的示例,性能上其实和下面的 JSON 一样,在一些非凡状况下会有问题,为了防止踩坑倡议一看而过。
function deepCopy(obj) {let copy = Array.isArray(obj) ? [] : {}; | |
for (let i in obj) { | |
// 遍历时过滤掉原型链上的属性 | |
if (obj.hasOwnProperty(i)) { | |
// 属性是对象时递归解决 | |
copy[i] = typeof obj[i] === "object" ? deepCopy(obj[i]) : obj[i]; | |
} | |
} | |
return copy; | |
} |
- lodash _.cloneDeep()
本着不要反复造轮子的准则(除非你能造的比人家好),还是倡议间接应用第三方库 lodash 的办法。lodash 提供的复制办法有两个,_.clone()和_.cloneDeep()。其中_.clone(obj, true)等价于_.cloneDeep(obj)。
var _ = require("lodash"); | |
var obj1 = { | |
name: "greennn", | |
age: 25, | |
bestfriend: { | |
name: "xiaoming", | |
age: 23 | |
} | |
}; | |
var obj2 = _.cloneDeep(obj1); | |
obj2.age = 20; | |
obj1.bestfriend.name = "xiaoding"; | |
console.log(obj1.age) // 25 | |
console.log(obj2.bestfriend.name); // "xiaoming" | |
console.log(obj1 === obj2) // false |
能够看出,通过深拷贝之后,obj1 和 obj2 曾经齐全独立,互不干涉了。