共计 2572 个字符,预计需要花费 7 分钟才能阅读完成。
想要讲明确 JS 中对象的浅拷贝和深拷贝,须要从它的数据类型说起。
JavaScript 中的数据类型
个别咱们说到 JS 的数据类型指的是它的原始 (Primitive types) 数据类型(共有 6 种):
- String
- Number
- Boolean
- Symbol(ES6 新增)
- Null
- Undefined
这些类型能够间接操作保留在变量中的理论值。
此外,JS 中还有援用数据类型:对象(Object)、数组(Array)、函数(Function)。在 JS 中除了根本数据类型以外的都是对象,数组是对象,函数是对象,正则表达式是对象,日期也是对象 …
栈 (stack) 和堆(heap)
stack 为主动调配的内存空间,它由零碎主动开释;而 heap 则是动态分配的内存,大小不确定。
根本数据类型是指寄存在栈中的简略数据段,数据大小确定,内存空间大小能够调配,它们是间接按值寄存的,所以能够间接按值拜访:
var a = 10;
var b = a;
b = 20;
console.log(a); // 10
console.log(b); // 20
被从新赋值的 b 并没有影响 a 的值。
援用类型是寄存在堆内存中的对象,变量其实是保留的在栈内存中的一个指针(保留的是堆内存中的援用地址),这个指针指向堆内存。
var obj1 = new Object();
var obj2 = obj1;
obj2.description = "Hello World";
console.log(obj1.name); // Hello World
阐明这两个援用数据类型指向了同一个堆内存对象。
援用传递和值传递
在变量复制的过程中,对象的复制是援用传递, 根底类型是值传递。
- 在将一个保留着原始值的变量复制给另一个变量时,会将原始值的正本赋值给新变量,尔后这两个变量是齐全独立的,他们只是领有雷同的 value 而已。
- 援用值:在将一个保留着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的扭转都会反映在另一个身上。
浅拷贝
当咱们想要拷贝一个对象时,如果它的属性是对象或数组时,这时候咱们传递的也只是一个地址。因而子对象在拜访该属性时,会依据地址回溯到父对象指向的堆内存中,即父子对象产生了关联,两者的属性值会指向同一内存空间。
const obj = {name: "bird", abilities: ["fly","sing"]}
function copy(target){var objNew ={};
for (var i in target){objNew[i] = target[i]
}
return objNew;
}
const objCopy = copy(obj)
console.log(objCopy) // {name: "bird", abilities: ["fly","sing"]}
objCopy.name = "bird01" // 批改原始类型的属性
console.log(objCopy) // {name: 'bird01', abilities: ["fly","sing"]}
objCopy.abilities.push("lay eggs") // 批改援用类型的属性
console.log(objCopy) // {name: 'bird01', abilities: ["fly","sing", 'lay eggs']}
console.log(obj) // {name: 'bird', abilities: ["fly","sing", 'lay eggs']}
能够看到 obj 的 abilities 属性随着 objCopy 的扭转而扭转,两个对象之间产生了关联。须要留神的是 Object.assign()或者是 Spread Operator 都是浅拷贝:
const obj = {name: "bird", abilities: ["fly","sing"]}
const objCopy = Object.assign({}, obj) // {name: "bird", abilities: ["fly","sing"]}
objCopy.abilities.push("lay egg")
console.log(obj) // {name: "bird", abilities: ["fly","sing","lay egg"]}
const objCopy2 = {...obj} // {name: "bird", abilities: ["fly","sing","lay egg"]}
objCopy2.abilities.pop()
console.log(obj) // {name: "bird", abilities: ["fly","sing"]}
这样的关联往往不是咱们心愿看到的,那么就有了深拷贝。
深拷贝
应用 JSON.parse/stringify 能够复制属性中含有数组的状况:
const a = {arr: ["Google","Baidu"]}
const clone = JSON.parse(JSON.stringify(a))
clone.arr.pop()
console.log(a) // {arr: ["Google","Baidu"]}
可见 clone 的属性扭转曾经不再和 a 产生关联。不过对于其余类型的属性,在这个过程中可能会放生失落:
const a = {
string: 'string',
number: 123,
bool: false,
nul: null,
date: new Date(), // stringified
undef: undefined, // lost
inf: Infinity, // forced to 'null'
re: /.*/, // lost
}
所以借助一些第三方的库是个不错的抉择,比方:
- lodash – cloneDeep; can be imported separately via the lodash.clonedeep module and is probably your best choice if you’re not already using a library that provides a deep cloning function
在不久以后兴许你能够应用 structuredClone
2021 update: The structuredClone global function is coming to browsers, Node.js, and Deno soon.
链接