「pass by value、pass by reference、深拷貝」相關筆記
2021-04-18 16:43 JavaScript
總結
對 Primitive types 與 Structural types 的資料使用等號進行「賦值」的動作時,賦予的內容分別是值本身(value)或記憶體中的位置(reference):
- Primitive types(
undefined、Boolean、Number、BigInt、String、Symbol):pass by value,可直接透過「賦值」拷貝(淺拷貝,shallow copy) - Structural types(
Object、Function):pass by reference,要完全拷貝其內容需執行深拷貝(deep copy)
Pass by value
- 將基本類別的資料從一個變數「賦值」到另外一個變數的時候,傳遞的是「值」本身(pass by value)
- 參考以下程式碼,在將
bag的值apples賦予pocket時,傳遞給pocket的是bag的值(apples)let bag = 'apples' let pocket = bag bag = 'oranges' console.log(pocket) // output: applespocket的apples與bag的apples分別位在記憶體中不同的空間,實際上是兩筆值為apples的資料- 所以,即使對
bag賦予一組新的值oranges,也不會影響到pocket的值
Pass by reference
- 將
Object與function類型的資料從一個變數「賦值」到另外一個變數的時候,傳遞的是「記憶體中的位置」(pass by reference) - 參考以下程式碼,
container1與container2兩個變數「指向」同一筆資料{apple: 6};修改container1「指向」的內容後,呼叫「指向同一筆資料」的container2就會看到被修改後的內容,即是{apple: 3}const container1 = {apple: 6} const container2 = container1 container1.apple = 3 console.log(container2) // output: {apple: 3}
深拷貝(deep copy)
概念如下:透過對container1進行深拷貝來取得container2的話 ⋯⋯
container2就會是一個完全獨立的新物件,對container2進行任何修改都不會影響到container1- 反之亦然,對
container1進行任何修改都不會影響到container2的內容 container1與container2分別指向記憶體中不同的位置
JSON.stringify搭配JSON.parse
把需要進行深拷貝的目標傳入JSON.stringify,再將JSON.stringify回傳的 JSON string 傳入JSON.parse()重建為 JavaScript 物件(或值)
const container1 = {apple: 6}
const jsonString = JSON.stringify(container1) // 取得JSON string化的container1
const container2 = JSON.parse(jsonString) // 將JSON string化的container1重建為JavaScript物件,得到與container1無關的container2
container1.apple = 3
console.log(container2) // output: {apple: 6}
JSON.stringify的特性:
- 無法處理
circular reference(循環參考)與BigInt(JSON.stringify會丟出TypeError) - 無法處理
undefined、Function或Symbol- 若是在處理
array時發現以上三種資料,這三種類型的資料會被轉換為null - 若是在處理
object時發現以上三種資料,這三種類型的資料會被略過 Symbol-keyed 會被略過undefined與Function會讓JSON.stringify回傳undefined- MDN 原文:
undefined,Functions, andSymbols are not valid JSON values. If any such values are encountered during conversion they are either omitted (when found in an object) or changed tonull(when found in an array).JSON.stringify()can returnundefinedwhen passing in “pure” values likeJSON.stringify(function(){})orJSON.stringify(undefined).
- 若是在處理
- 傳入
JSON.stringify的Infinity、NaN與null都會被視為null- MDN 原文:The numbers
InfinityandNaN, as well as the valuenull, are all considerednull.
- MDN 原文:The numbers
- 只會處理
Object中enumerable的部分- MDN 原文:All the other
Objectinstances (includingMap,Set,WeakMap, andWeakSet) will have only their enumerable properties serialized.
- MDN 原文:All the other
JSON.parse的使用注意事項:
- 無法處理 trailing commas
JSON.parse('[1, 2, 3, 4, ]') JSON.parse('{"foo" : 1, }') // both will throw a SyntaxError - 僅允許單引號
'包覆雙引號",反過來使用雙引號包覆單引號會丟出SyntaxErrorJSON.parse('[1, 5, "false"]'); // output: [1, 5, "false"] JSON.parse("[1, 5, 'false']"); // will throw a SyntaxError
使用Lodash的_.cloneDeep
文件:https://docs-lodash.com/v4/clone-deep/
const container1 = [{ apple: 6 }, { orange: 2 }]
const container2 = _.cloneDeep(container1)
container1[0].apple = 3
console.log(container1) // [{ apple: 3 }, { orange: 2 }]
console.log(container2) // [{ apple: 6 }, { orange: 2 }]
使用jQuery的$.extend
文件:https://api.jquery.com/jquery.extend/
const container1 = [{ apple: 6 }, { orange: 2 }]
const container2 = $.extend(true, [], container1);
container1[0].apple = 3
console.log(container1) // [{ apple: 3 }, { orange: 2 }]
console.log(container2) // [{ apple: 6 }, { orange: 2 }]
Node.js: Serialization API
文件:https://nodejs.org/api/all.html#v8_serialization_api
const v8 = require('v8')
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
}
The structured clone algorithm
參考:https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm 尚未於瀏覽器中實現。
bonus track: CPython id() in JavaScript?
提問:CPython 的id()會回傳物件在記憶體中的位置,JavaScript 有類似的功能嗎?
簡答:沒有。
詳答:
- If it would be possible at all, it would be very dependent on the javascript engine. The more modern javascript engine compile their code using a just in time compiler and messing with their internal variables would be either bad for performance, or bad for stability.
- Here’s an easier reason for why you can’t get a variable’s memory address: JS is a safe language for the web, which abstracts away not only the hardware, but also the OS and browser. There are only high-level concepts by design, because lower-level stuff does not have to matter at all.
參考文件
- MDN: Data and Structure types
- MDN: JSON.stringify()
- MDN: JSON.parse()
- 前端三十 - 成為更好的前端工程師系列:14. [JS] 深拷貝是什麼?如何實現?
- Roya’s Blog: JavaScript 淺拷貝 (Shallow Copy) 與深拷貝 (Deep Copy)
- Kanboo Notes: JS-淺拷貝(Shallow Copy) VS 深拷貝(Deep Copy)
- [筆記] 談談 JavaScript 中 by reference 和 by value 的重要觀念
- What is the most efficient way to deep clone an object in JavaScript?
- How can I get the memory address of a JavaScript variable?
- How to get the variable’ memory address in node.js?