1、深拷貝 1.1、概念 對象的深拷貝是指其屬性與其拷貝的源對象的屬性不共用相同的引用(指向相同的底層值)的副本。 因此,當你更改源或副本時,可以確保不會導致其他對象也發生更改;也就是說,你不會無意中對源或副本造成意料之外的更改。 在深拷貝中,源和副本是完全獨立的。深拷貝與其源對象不共用引用,所以對 ...
1、深拷貝
1.1、概念
對象的深拷貝是指其屬性與其拷貝的源對象的屬性不共用相同的引用(指向相同的底層值)的副本。
因此,當你更改源或副本時,可以確保不會導致其他對象也發生更改;也就是說,你不會無意中對源或副本造成意料之外的更改。
在深拷貝中,源和副本是完全獨立的。深拷貝與其源對象不共用引用,所以對深拷貝所做的任何更改都不會影響源對象。
1.2、實現方式:
1.2.1、使用 JSON.stringify()
將該對象轉換為 JSON 字元串,然後使用 JSON.parse()
將該字元串轉換回(全新的)JavaScript 對象。
前提:JavaScript 對象可以被序列化
序列化異常報錯
- 存在迴圈引用時,會拋出異常 TypeError ("cyclic object value")(迴圈對象值)
- 存在 BigInt 類型的值時,如:
{ num: BigInt(1111111111) }
會拋出 TypeError ("BigInt value can't be serialized in JSON")(BigInt 值不能 JSON 序列化).
序列化需要註意的隱式轉換
- 非數組對象的屬性不能保證以特定的順序出現在序列化後的字元串中
undefined
、任意的函數以及symbol
值,在序列化過程中會被忽略(出現在非數組對象的屬性值中時)或者被轉換成null
(出現在數組中時)。函數、undefined
被單獨轉換時,會返回undefined
,如JSON.stringify(function(){})
orJSON.stringify(undefined)
.- 所有以
symbol
為屬性鍵的屬性都會被完全忽略掉,即便replacer
參數中強制指定包含了它們。 Date
日期,會調用toJSON()
將其轉換為 string 字元串(同Date.toISOString())
NaN
和Infinity
格式的數值及null
都會被當做null
Map/Set/WeakMap/WeakSet
,僅會序列化可枚舉的屬性- HTML 元素對象, 會得到
{}
示例
// 隱式轉換
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// '[1,"false",false]'
JSON.stringify({x: undefined, y: Object, z: Symbol("")});
// '{}'
JSON.stringify([undefined, Object, Symbol("")]);
// '[null,null,null]'
JSON.stringify({[Symbol("foo")]: "foo"});
// '{}'
JSON.stringify({[Symbol.for("foo")]: "foo"}, [Symbol.for("foo")]);
// '{}'
JSON.stringify(
{[Symbol.for("foo")]: "foo"},
function (k, v) {
if (typeof k === "symbol"){
return "a symbol";
}
}
);
// undefined
// 不可枚舉的屬性預設會被忽略:
JSON.stringify(
Object.create(
null,
{
x: { value: 'x', enumerable: false },
y: { value: 'y', enumerable: true }
}
)
);
// "{"y":"y"}"
JSON.stringify(document.body) // '{}'
JSON.stringify(new Set([1, 2, 3])) // '{}'
JSON.stringify(new Map([['num', 2]])) // '{}'
1.2.2、使用 window.structuredClone()
,使用結構化克隆演算法將給定的值進行深拷貝。
前提:可序列化的對象,且在瀏覽器環境和任何其他實現了 window 這樣全局對象的 JavaScript 運行時的環境(structuredClone() 不是 JavaScript 語言本身的特性)
優點
- 支持把原始值中的可轉移對象轉移到新對象,而不是把屬性引用拷貝過去。
- 可轉移對象與原始對象分離並附加到新對象; 它們不可以在原始對象中訪問被訪問到。
- 支持迴圈引用
補充:結構化克隆演算法
- 結構化克隆演算法是用於複製複雜 js 對象的演算法。
- 它通過遞歸輸入對象來構建克隆,同時保持先前訪問過的引用的映射,以避免無限遍歷迴圈。
- Worker 的
postMessage()
或IndexedDB
存儲對象時內部使用該演算法。所以,也可間接的通過這2個方法實現深拷貝。
補充:可轉移對象
- 可轉移的對象(Transferable object)是擁有屬於自己的資源的對象,這些資源可以從一個上下文轉移到另一個,確保資源一次僅在一個上下文可用。傳輸後,原始對象不再可用;它不再指向轉移後的資源,並且任何讀取或者寫入該對象的嘗試都將拋出異常。
- 可轉移對象通常用於共用資源,該資源一次僅能安全地暴露在一個 js 線程中。
- 如:
ArrayBuffer
是一個擁有記憶體塊的可轉移對象。當此類緩衝區(buffer)線上程之間傳輸時,相關聯的記憶體資源將從原始的緩衝區分離出來,並且附加到新線程創建的緩衝區對象中。原始線程中的緩衝區對象不再可用,因為它不再擁有屬於自己的記憶體資源了。
異常報錯
- Function 對象是不能被結構化克隆演算法複製的,拋出 DATA_CLONE_ERR 異常,如:
structuredClone(function fn() {})
- Symbol 不能被結構化克隆演算法複製的,拋出 DATA_CLONE_ERR 異常,如:
structuredClone({s: Symbol(1)})
- HTML 元素對象,拋出 DATA_CLONE_ERR 異常,如:
structuredClone(document.body)
隱式轉換
RegExp
對象的lastIndex
欄位不會被保留- 屬性描述符,
setters
以及getters
(以及其他類似元數據的功能)同樣不會被覆制。例如,如果一個對象用屬性描述符標記為read-only
,它將會被覆製為read-write
,因為這是預設的情況下 - 原形鏈上的屬性也不會被追蹤以及複製
支持的js類型
- Array、ArrayBuffer、Boolean、DataView、Date、Map、Set、String、TypedArray
- Error 類型(僅限部分 Error 類型)。
- Object 對象:僅限簡單對象(如使用對象字面量創建的)。
- 除 symbol 以外的基本類型。
- RegExp:lastIndex 欄位不會被保留。
支持的 Error類型
- Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError(或其他會被設置為 Error 的)。
示例
// 迴圈引用
const original = { name: "MDN" };
original.itself = original;
// clone
const clone = structuredClone(original);
// 驗證
console.log(clone !== original) // true
console.log(clone.name === "MDN") // true
console.log(clone.itself === clone) // true
const get = { get foo() { return 'bar' } }
console.log(get.foo) // 'bar'
class MyClass {
foo = 'bar'
myMethod() { /* ... */ }
}
const myClass = new MyClass()
const cloned = structuredClone(myClass)
// { foo: 'bar' }
cloned instanceof myClass // false
1.2.3、 使用js庫
比如 lodash 中的 cloneDeep()
方法
該方法會遞歸拷貝 value。
clone 方法參考自 結構化克隆演算法 以及支持 arrays
、array buffers
、 booleans
、 date objects
、maps
、 numbers
, Object
對象, regexes
, sets
, strings
, symbols
, 以及 typed arrays
。 arguments
對象的可枚舉屬性會拷貝為普通對象。 一些不可拷貝的對象,例如 error objects
、functions
, DOM nodes
, 以及 WeakMaps
會返回空對象。
參考:MDN
2、淺拷貝
2.1、概念
對象的淺拷貝是其屬性與拷貝源對象的屬性共用相同引用(指向相同的底層值)的副本。
因此,當你更改源或副本時,也可能導致其他對象也發生更改——也就是說,你可能會無意中對源或副本造成意料之外的更改。
在淺拷貝中,對源或副本的更改可能也會導致其他對象的更改(因為兩個對象共用相同的引用)。
2.2、實現方式
在 js 中,所有標準的內置對象進行操作:...
展開語法、Array.prototype.concat()
、Array.prototype.slice()
、Array.from()
、Object.assign()
和 Object.create()
,創建的都是淺拷貝而不是深拷貝。
參考:MDN 淺拷貝
最後,感謝您閱讀這篇博客!希望本文能夠為您提供有價值的信息和啟示。
如果您對本文的內容有任何疑問或建議,請隨時在評論區留言,我會儘快回覆您。