普通文組 2.5

「pass by value、pass by reference、深拷貝」相關筆記

總結

對 Primitive types 與 Structural types 的資料使用等號進行「賦值」的動作時,賦予的內容分別是值本身(value)或記憶體中的位置(reference):

  • Primitive types(undefinedBooleanNumberBigIntStringSymbol):pass by value,可直接透過「賦值」拷貝(淺拷貝,shallow copy)
  • Structural types(ObjectFunction):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
    • pocketapplesbagapples分別位在記憶體中不同的空間,實際上是兩筆值為apples的資料
    • 所以,即使對bag賦予一組新的值oranges,也不會影響到pocket的值

Pass by reference

  • Objectfunction類型的資料從一個變數「賦值」到另外一個變數的時候,傳遞的是「記憶體中的位置」(pass by reference)
  • 參考以下程式碼,container1container2兩個變數「指向」同一筆資料{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的內容
  • container1container2分別指向記憶體中不同的位置

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(循環參考)與BigIntJSON.stringify會丟出TypeError
  • 無法處理undefinedFunctionSymbol
    • 若是在處理array時發現以上三種資料,這三種類型的資料會被轉換為null
    • 若是在處理object時發現以上三種資料,這三種類型的資料會被略過
    • Symbol-keyed 會被略過
    • undefinedFunction會讓JSON.stringify回傳undefined
    • MDN 原文:undefined, Functions, and Symbols are not valid JSON values. If any such values are encountered during conversion they are either omitted (when found in an object) or changed to null (when found in an array). JSON.stringify() can return undefined when passing in “pure” values like JSON.stringify(function(){}) or JSON.stringify(undefined).
  • 傳入JSON.stringifyInfinityNaNnull都會被視為null
    • MDN 原文:The numbers Infinity and NaN, as well as the value null, are all considered null.
  • 只會處理Objectenumerable的部分
    • MDN 原文:All the other Object instances (including Map, Set, WeakMap, and WeakSet) will have only their enumerable properties serialized.

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.

參考文件