0%

以陣列 Array 的複製談型別(上)

Day 11

別懷疑,複製就是拷貝,深拷貝淺拷貝就是深複製淺複製,呼~

人家說,如果我們可以和某位朋友一起旅行 30 天,要嘛旅程結束變至親,不然就是變仇人,鐵人賽已經第十天,本人也開始有點了解 Array 這位木訥寡言,但一肚子學問的中年大叔。很難想像,光陣列就可以讓我寫十天寫不盡,即使寫完它,但是後頭還有多少 JavaScript 特性或其他奇怪的東西要了解?不過,只要確定走在進步的路上,那就繼續走吧!

複製到哪個程度?

要說到陣列的複製,得讓我們聊聊 Javascript 是怎麼將型別分類的,原因是這牽涉到記憶體的分配與讀取的方式。在這之前,我們再來複習一下,在前幾天有聊到的 Javascript 資料型別。

在「你說不知道的 JS 導讀,型別與文法」提面提到,JavaScript 定義了七個內建型別:

  • Null
  • Undefined
  • Boolean
  • Number
  • String
  • Object
  • Symbol (ES6 增加的,但目前還很少用)

而在這些型別中,又被分為兩大類 Primitive type(基本型別)和 Non-primitive type(非基本型別)。形容這兩大類的詞彙有許多版本,我們可以透過底下這張圖看得更清楚一點。

DataType圖

實作時常會聽到call by reference 還是 call by value,這兩個有什麼不同?最簡易的解釋方法是:

  • Call by value : 呼叫變數的值
  • Call by reference : 呼叫變數的記憶體位址

ObjetArray 在 Javascript 屬於複合型(composite)或參考型(reference)的原始資料類型,在呼叫、複製或傳參數的時候,是參考記憶體的位址而不是值,這點要特別注意。

在查找資料時,又多了新的疑問,因為許多人談到另一種名叫call by sharing 雖然讀了許多資料,但本人還是無法清楚的釐清它和Call by reference的相異之處,有興趣的朋友可以來看看這篇深入探討 JavaScript 中的參數傳遞:call by value 還是 reference? | TechBridge 技術共筆部落格 相信會有收穫的,在本人還沒完全吸收的狀況下,還是留給高人來解說比較保險。

比較不同型別的複製的方法

By value 你的是你的,我的是我的

這些抽象的概念,初學者並不是那麼容易懂,但透過實作會讓這些抽象部分更有感覺。

我們先創建一個變數a,再將a複製給b,接著重新給b 一個新值,然後以這兩個變數的結果來觀察。我們會發現ab都有各自的值。a的值不會因為b而更動到。

1
2
3
4
5
6
7
// by value
let a = 'something';
let b = a;
b = 'somebody';

a; // something
b; // somebody

By reference 你怎麼把我變得跟你一樣?

我們先以物件資料型態的物件來測試。

先建立一個物件person,並給予兩個屬性,在另創一個變數person2,並將person指向(或複製)到person2,這時我們試著修改person2的某個屬性,結果person的屬性也被更動了。

在修改person2這個object裡的name屬性時,也會同時改到原來參考的personname屬性,是不是很恐怖?

因為他們兩個是住在同一個記憶體裡啊!只不過,當我們認為從person複製一份到person2這件事,實際上是並沒有像影印一樣拷貝一份,而是拷貝時,我們跟person2說,要找person2就來person這個位址查找吧,所以來到同樣一個位址,改變person2也就理所當然的改到原本的person

1
2
3
4
5
6
7
8
9
10
11
// by reference(參考值)
let person = {
name: 'Tracy',
city: 'Tainan'
};

let person2 = person;
person2.name = 'Ayda';

person; // name: "Ayda"
person2; // name: "Ayda"

接下來我們來測試我們的主角陣列。
同樣是先創建一個陣列的變數arr1,並複製一份給arr2,接著我們試著在arr2index3位置加入一個陣列元素4(使用push()也可以),再把兩個陣列叫出來觀察。

嗯,arr1也被更動到了! 是不是很討厭!

1
2
3
4
5
6
7
// 陣列 by reference(參考值)
let arr1 = [1, 2, 3];
let arr2 = arr1;
arr2[3] = 4;

arr1; // [1, 2, 3, 4]
arr2; // [1, 2, 3, 4]

因為Array也是參考型(reference)的原始資料類型所以也會像Object一樣,在修改時也會更改到原始參考值by reference

住在同一個位址有問題嗎?

這樣的狀況會對寫程式和處理資料有什麼影響? 會出人命啊 當然會。

想像一下我們跟同學借了筆記來抄,抄完之後我們開始在我們抄過來的筆記上畫重點,甚至塗鴉,等到借我們筆記的同學翻開他的筆記時,發現 這什麼鬼啊 筆記上有著我們畫的密密麻麻的記號和塗鴉,友誼還要走下去嗎?

上面的複製方式是我們最常用的,也是最直覺的複製方法,但是卻對陣列與物件的資料行不通,有其他方法複製嗎?當然有 或….應該有~~

我們可以利用 JavaScript 許多內建的方法來做複製這件事,但是 JavaScript 可沒那麼容易讓我們複製物件型態的資料。大家也聽過很多人都在聊的「深拷貝(DeepCopy)」和「淺拷貝(ShallowCopy)」,這兩者的區別又在哪裡呢?

我們可以先想像一下,在 JavaScript 裡,要複製這些物件型別的「DNA」是有難度的,假如我們想徹徹底底地模仿一位巨星,髮型、服飾、化妝、配件都和這位巨星打扮的一模一樣,這樣或許看起來一樣,但當我們講話、走路時,就馬上會被識破,然後我們努力的把自己的音調也改了、姿態也修正了,以為一切都完美了,結果,遇到這會巨星的好朋友或男友,你想,我們會過關嗎?

物件型別的複製,正有如以上講述情境的尷尬,也就是說,即使是複製,也是分好幾個層級的。我們明天再來聊聊,如何改變我們的DNA,完完全全地成為那位巨星! 甘五摳零?

如有需要改進的地方,拜託懇求請告知,我會盡量快速度修改,感謝您~