JavaScript 是按引用傳遞還是按值傳遞型別

Sahil Bhosale 2024年2月15日
  1. JavaScript 中的原始資料型別是按值傳遞的
  2. JavaScript 中的非原始值是通過引用傳遞的
  3. JavaScript 既是按值傳遞又是按引用傳遞
JavaScript 是按引用傳遞還是按值傳遞型別

在深入探討 JavaScript 程式語言是否是按引用型別傳遞之前,讓我們先看看按值傳遞和按引用傳遞之間的區別。

當我們進行賦值操作或處理函式本身時,這些東西通常會出現。因為大多數時候,我們執行賦值操作,建立各種函式,並將多個變數傳遞給這些函式,瞭解這些東西的內部工作原理將使我們對程式語言有更深入的瞭解。

每當我們將一個變數的值分配或傳遞給另一個變數或函式時,它被稱為按值傳遞。當我們將一個變數的地址分配或傳遞給另一個變數或函式時,它被稱為通過引用傳遞。

現在我們已經看到了值傳遞和引用傳遞型別的基本定義,讓我們進一步瞭解它們在下面的示例中的使用方式。

JavaScript 中的原始資料型別是按值傳遞的

請注意,JavaScript 中有兩種資料型別:原始資料型別和非原始資料型別。原始資料型別包括數字、布林值、字串、未定義和空值。

這些資料型別沒有像我們在物件或陣列中那樣的任何預定義方法。原始資料型別是不可變的;這意味著無論何時你將一個原始變數的值儲存或分配給另一個變數並對第二個變數的值進行一些修改時,第一個變數的值都不會改變。它將保持原樣,直到並且除非你更改該變數本身的值。只有這樣,第二個變數才會改變。

你可以在 MDN 上找到與原始資料型別相關的更多資訊。

// Example 1
let a = 10;
let b = a;
b = b + 1;

console.log(a);
console.log(b);


// Example 2
let name = 'Google';

let greet =
    (val) => {
      val = 'Hi ' + name + '!';
      return val;
    }

             console.log(name);
console.log(greet(name));

輸出:

10
11
Google
Hi Google!

藉助下圖可以更好地理解上面的程式碼。請注意,它只解釋了第一個程式碼示例;第二個示例的工作方式與示例一大致相同。

原始型別示例視覺化

在我們的第一個示例中,我們有兩個變數:ab。我們為變數 a 分配了數值 10;這將在記憶體中為變數 a 分配一個空間,由具有地址 0x01 的黃色表示(這只是本示例中採用的任意地址)。

然後,我們將變數 a 分配給變數 b。這個過程將為變數 b 分配新的記憶體空間。在這裡,它將儲存變數 a 的值,即 10

從上面的例子中,我們可以清楚地看到變數 ab 的地址是不同的,這意味著它們在記憶體中分配了不同的空間。這個結果意味著,無論何時你將變數 b 的值更改或修改為 b = b + 1,變數 a 內的值都不會改變。只有變數 b 的值會改變,它會變成 11,因為我們將它加一。

在我們的第二個例子中,我們有一個變數 name,裡面有一個值 Google。我們還有一個名為 greet() 的函式,它接受一個字串值作為引數並返回一個新字串。作為此函式的引數,我們傳遞變數 a(請注意,傳遞變數意味著傳遞其值而不是其地址)。Google 值將儲存在名為 val 的函式區域性變數中。

目前,變數 val 包含值 Google。在函式中,我們將此值更改為 Hi Google!然後返回這個值。現在,如果你輸出 name 變數值和函式,以及返回值,它們都會不同。name 變數保持不變,因為 nameval 變數儲存在不同的位置,我們只是將值 Google 傳遞給函式和地址。

之前,我們已經看到了按值傳遞的定義,它表示我們只在賦值期間或將變數傳遞給函式時傳遞變數的值。上面的兩個例子在實踐中證明了這一點。

JavaScript 中的非原始值是通過引用傳遞的

非原始資料型別由陣列、物件或類組成。這些資料型別確實有預定義的內建函式,允許我們運算元組或物件內的資料。

原始資料型別是可變的;這意味著以後可以修改的資料型別被稱為可變的。此外,非原始資料型別是通過引用呼叫的。讓我們通過下面的例子來理解這一點。

let a = [1, 2];
let b = a;
b.push(3);

console.log(a)
console.log(b)

輸出:

[ 1, 2, 3 ]
[ 1, 2, 3 ]

上例的影象如下所示。

非前置式示例

在此示例中,我們建立了一個陣列 a,其中包含兩個元素:[1,2]。由於陣列是非原始資料型別,變數 a 不會儲存所有陣列元素。相反,值 [1,2] 將儲存在記憶體中的另一個位置。在這種情況下,該陣列的起始地址,在這種情況下,0x01,將儲存在變數 a 中,如上圖所示。由於變數 a 具有儲存實際陣列元素的地址,因此它將指向該位置。

當我們將變數 a 分配給變數 b 時,我們將存在於 a 中的地址儲存到變數 b 中。現在,變數 b 也將指向與變數 a 相同的記憶體位置 0x011,因為它們具有相同的記憶體地址。因此,如果你更改由 b 指向的陣列的元素,那麼 a 也會受到影響,因為兩者都指向相同的位置。

在這裡,我們使用變數 b(例如 b.push(3))將一個新元素推送到陣列中。現在,變數 b 將從第一個元素 1 開始遍歷整個陣列,直到最後一個元素 2。在最後一個元素之後,它將新元素 3 插入到陣列中。如果你同時列印變數 ab,你將看到由於引用,它們都將顯示與 [1,2,3] 相同的值。

這被稱為通過引用傳遞,因為我們傳遞的是記憶體地址本身而不是值。

JavaScript 既是按值傳遞又是按引用傳遞

JavaScript 程式語言支援按值傳遞和按引用傳遞。數字、布林值、字串、undefined 和 null 等原始值都是通過值傳遞的。非原始型別(例如陣列、物件和類)通過引用傳遞。

作者: Sahil Bhosale
Sahil Bhosale avatar Sahil Bhosale avatar

Sahil is a full-stack developer who loves to build software. He likes to share his knowledge by writing technical articles and helping clients by working with them as freelance software engineer and technical writer on Upwork.

LinkedIn