「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: apples
pocket
的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
,Function
s, andSymbol
s 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 returnundefined
when passing in “pure” values likeJSON.stringify(function(){})
orJSON.stringify(undefined)
.
- 若是在處理
- 傳入
JSON.stringify
的Infinity
、NaN
與null
都會被視為null
- MDN 原文:The numbers
Infinity
andNaN
, as well as the valuenull
, are all considerednull
.
- MDN 原文:The numbers
- 只會處理
Object
中enumerable
的部分- MDN 原文:All the other
Object
instances (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
- 僅允許單引號
'
包覆雙引號"
,反過來使用雙引號包覆單引號會丟出SyntaxError
JSON.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?