iT邦幫忙

2021 iThome 鐵人賽

DAY 19
0
Modern Web

我的JavaScript日常系列 第 19

JavaScript Day 19. by value ( 傳值 ) 與 by reference ( 傳址 )

上一篇說到 JavaScript 原始型別與物件型別,我想今天試著來討論「傳值」與「傳址」;在其他程式語言可能可以決定要「傳值」還是「傳址」,但在 JavaScript 中我們沒辦法選擇,原始型別與物件型別會有他們個別的傳遞行為。

在 JavaScript 中,值的傳遞分為兩種:

  • 傳值:Call by value 或 Pass by value / 原始型別傳值呼叫
  • 傳址:Call by reference 或 Pass by reference / 物件型別傳參考呼叫

原始型別傳值 ( Pass by value )

前一篇有提到,JavaScript 中除了物件以外的所有型別,都是原始型別,也有提到分別有哪幾種,這裡就不再贅述了。原始型別也稱為「純值」,並且會以「傳值 ( Pass by value )」的方式傳遞。

直接來看看範例:

let cat = '喵'; // 定義 cat 的內容為'喵'字串
let newCat;
newCat = cat; // 將 newCat 指定等於 cat 字串
console.log(newCat); // '喵'

newCat = '呼嚕';    // 再將 newCat 的內容更動為'呼嚕'
console.log(cat); // '喵'
console.log(newCat); // 打印 newCat 出現變更後的內容'呼嚕'

這個範例看起來與我自己認知的部分沒有什麼出入,假如以這個例子來說明什麼是「 Pass by value」,我這邊可以理解的是,不能受到改變的就是「原始型別 / Pass by value ( 傳值 )」,不過為了怕自己遺忘,這邊也還是稍微解釋一下。

前一篇我們也有提到過,原始型別也有「純值」的意思,他是不可變動的,以上面的範例來說,我們可以更改 cat = '喵' 這邊的 cat 變數,但沒有辦法更改 '喵' 這個值。在電腦的空間裡面有一個大型的空間稱做「記憶體」,每一個空間都有他的位置,為了能夠讓我們取用,因此會有變數的存在,於是我們可以使用變數指向這些記憶體位置,宣告變數並賦予值,這樣的行為就是向電腦要一個記憶體空間來存值。

let cat = '喵' 來說,變數 cat 指向電腦中某記憶體的位置,並在這個記憶體儲存 '喵' 這個值,我們另外又宣告了一個 newCat,雖然 catnewCat 的值是一樣的,但其實 newCat 另外在不同的記憶體位置,只是複製了 cat 的值,他們兩個仍然是各自存在於獨立的記憶體位置,因此後來我們又給 newCat 一個新的值的時候 cat 仍然不會被變動。

另外,這裡要注意的是,newCat 的值之所以變成 '呼嚕',是因為 newCat 改變了記憶體的指向,不是改變了 '呼嚕' 這個值,值是不能被變動的。像上面這樣的過程,我們可以稱做「傳值」。

再來一個簡易的範例,可以當作穩固概念用:

let a = 50;
let b = 50;

a === b ;  // true

50 為原始型別,ab 互相比較的是彼此的值,因此這裡回傳 true


物件型別傳參考傳址 ( Pass by reference )

物件型別是以「傳址 ( Pass by reference )」的方式傳遞。一個物件型別的變數,被存在某個有地址的「位置」,而這個「記憶體位置」則存在於這個變數中,因此以「記憶體位置為參考」,並在變數間傳遞存取的行為,就稱為「傳參考呼叫」。

範例:

let arr = [1,2,3];
let newArr = arr;
console.log(arr2); // [1,2,3]

arr[0] = 2; 
console.log(arr); // [2,2,3]
console.log(newArr); // [2,2,3]

來解析一下上面這一段範例,我們建立了一個陣列 arr,這個陣列指向了一個新的記憶體位置,這時候我們再建立一個陣列 newArr,並且讓 newArr 等於 arr,此時 newArr 會指向 arr 的記憶體位置,因此之後不論我們怎麼修改,console.log 都會呈現 newArr === arr 這樣的結果,因為兩個記憶體指向是在同一個位置。

不過這裡還是要提到有一個例外,就是當我們直接用等號賦予新的值,就等於是建立一個新的記憶體位置,此時 arrnewArr 就不會指向同一個位置,因此回傳 false

let arr = [1,2,3];
let newArr = arr;
console.log(newArr); // [1,2,3]

arr = [4,5,6]; // 這邊賦予了新的記憶體位置
console.log(newArr); // [1,2,3]
console.log(newArr === arr); // false

結果可以看得出來,物件型別是以「記憶體位置為參考」互相傳遞,而不是以值做傳遞的過程就稱為「傳址」。

上面我們給了一個簡易的原始型別比較的範例,這邊我們可以來看看如果是物件型別的時候,是否會得到相同的結果呢?

let obj1 = { a: 1 };
let obj2 = { a: 1 };

obj1 === obj2;  // false

可以看到這邊的結果跟原始物件的時候不同,這是因為每個物件都是獨立存在的實體,兩個物件的記憶體位置不同,而物件型別比較的是「記憶體位置」,不是值。

Pass by sharing

除了 Pass by value 和 Pass by reference,在查找資料的過程,還遇到了覺得其實是兩種綜合體的 Pass by sharing,剛剛我們談論到 Pass by reference 的例外,其實就是 Pass by sharing。

Pass by sharing 比較像是融合了 by value 和 by reference:

  • 碰到基本型別,呈現的行為是 Pass by value 的狀態。
  • 碰到物件型別,如果只是針對內容改變,呈現的行為是 Pass by reference,但是如果對物件是重興賦予值的情況,則呈現為 Pass by value。

這個部分我們可以透過範例來證實:

function test(obj) {
  obj = { number: 20 }; // 物件重新賦值
  console.log(obj); // { number: 20 }
}

let a = { number: 10 }; // obj
test(a);

console.log(a); // { number: 10 } =>  這邊沒有一起改變

來解析這個過程,宣告 function test(obj),接著宣告 a 變數,對 a 賦予物件 { number: 10 },把 a 丟進 test function 裡,等同於 obj 複製 a,大概是長這個樣子 obj = a,因為變數資料是物件型別,在 function 裡面又透過 obj = { number : 20 } 重新賦予值,這時候會有新的地址對應新的物件值,obj 會有新的地址,並且指向新的值。

因此 aobj 的地址不同,指向的值也不同所以最後印出的 a{ number: 10 },沒有因為 obj 重新賦值而被改變。這邊有一個很大的差異是,不是透過 obj.number = 20 去改變物件的值,因為以物件型別來說這樣子寫的確會形成 Pass by reference,但今天使用的重新賦值方法為 obj = { number : 20 },這會改變整個 obj 的值。

最後像這樣寫法,他會有點像是 Pass by value,obj 複製了 a,並呈現 aobj 兩個記憶體位置,互相不受到影響。

以上概念其實就是混合了 Pass by value 和 Pass by reference 兩種行為,稍微解釋一下如果他們分別為 by value 或是 by reference 的情況:

  • Pass by value 的情況:傳參數進 function 以後,透過 obj = { number: 20 } 重新賦予值,此時會創建新的地址與值,但由於外部變數與內部變數位置不同,指向也不同,因此不會互相受到影響。
  • Pass by reference 的情況:傳參數進 function 以後,透過 obj.number = 20 改變內容,由於記憶體位置與指向都是同一個,因此會互相改變與影響。

obj 重新賦值以後,以 Pass by reference 的概念來說,應該要互相影響,但實際上卻有點像是 Pass by value ,像這樣的情況其實較偏向 Pass by sharing。


寫完這篇其實自己也反覆看了幾遍,為了盡量理解每一篇看過的資料,也是反覆的測試與思考,因此這篇內容都是以自己的理解範圍下去整理,如果說明有誤還請各位看文的大大們,能不吝嗇告知,我會非常感激的!!

參考資料:
JS 原力覺醒 Day12- 傳值呼叫、傳址呼叫
重新認識 JavaScript: Day 05 JavaScript 是「傳值」或「傳址」?
[JavaScript] Javascript中的傳值 by value 與傳址 by reference
JS 變數傳遞探討:pass by value 、 pass by reference 還是 pass by sharing?


上一篇
JavaScript Day 18. 原始型別與物件型別
下一篇
JavaScript Day 20. BOM 與 DOM
系列文
我的JavaScript日常31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言