iT邦幫忙

2021 iThome 鐵人賽

DAY 1
1
自我挑戰組

每日一杯 JavaScript 特調系列 第 1

JavaScript 的資料型別 (data type) 及存取值

  • 分享至 

  • xImage
  •  

前言:最近算是自學到一個階段~已經開始面試。這次參加鐵人賽的主題以 JS 基礎知識為主,並會盡量將面試碰到的問題稍做整理結合進內容。作者仍努力學習當中,若有錯誤還請指正~感謝大家!(•ө•)♡

開市第一篇來個 by value、by reference 小boss,自己對於這個概念看了不下十次了...沒慧根就只能勤能補拙啦!

截至目前的 JS 版本,JS 的資料型別依然分成兩大類:「原始型別、物件型別」,且不支援開發者自定義其他型別。

這篇主要內容是針對兩大類的介紹以及他們如何存值、取值。

https://ithelp.ithome.com.tw/upload/images/20210913/20141763JbELrkSNW4.png

Primitive type 原始型別

原始型別包含以下七種資料類型

當我們要從這些型別引用值時,這些值都是屬於原始值 (primitive values)。

原始值是什麼呢?原始值代表不可被改變的值,聽起來有點抽象對吧~

舉個例子

var a = 'hey'
a.toUpperCase()
console.log(a) // 依然是 hey
a = a.toUpperCase()
console.log(a) // 得到 HEY

這邊我們先把 a 變量賦值為字串 hey,並且用了轉為大寫的 method,但可以發現沒有作用。因為字串 hey 是原始值,我們日常規範的英文 h 就是 h,h 不會是 y,也不是 H。又或者 1 就是 1,1 不會是 0,這就是原始值的意思。我覺得如果把原始值翻作原語也許會更好理解些~

而在 JS 中,原始值是無法改變的,導致第二行 a.toUpperCase() 沒有效果。

那麼為什麼 a = a.toUpperCase() 卻可以操作?要注意避免把賦值這件事跟原始值本身搞混了。

我們可以給變數重新分配一個「新值」,但不能去修改「原本的值」。

這個點是我後來才理解的,沒注意到就會忽略掉 ,主要是對原始值這個詞花了點時間才能意會。(´・_・`)

Object type 物件型別

除了上述提到的原始型別以外,其他的值都是物件型別,而物件包括了 Function、Array、Object、Date、Map、Set 等等,總之若不是原始型別,就會歸到物件型別。

物件型別要詳細說的話,還得開更多篇幅才能好好解釋其中的奧妙( 菜雞如我也還在宇宙中探索 )。但以剛開始認識 JS 來說,我認為先記得物件型別跟原始型別的差異在於,物件型別是可變的、以及把這些類型分清楚就好,因為會關係到下半部分提到的另一個新手大魔王 by value、 by reference。

請記住,所有值最終都會是這八種資料類型其中之一


理解值的儲存及引用 「 pass by value、pass by reference」

值屬於原始型別或是物件型別,影響到值被儲存、取用的方式。

先記得結論:

原始型別的值是 pass by value

物件型別的值是 pass by reference

https://ithelp.ithome.com.tw/upload/images/20210913/20141763pHLP2fJRbC.png

在講兩者的區別前,大家必須先知道 JS 背後對於變數的值是怎麼儲存的。

瀏覽器運作時,會佔用電腦的內存來執行網站所需要的程式碼。它將內存分配給 HTML CSS JS 等檔案,而在 JS 中,做法是會把用來存變數值的地方分成 stack(棧) 跟 heap(堆)。

https://ithelp.ithome.com.tw/upload/images/20210913/2014176378MYX9zcq3.png

stack 佔用的空間比較小、系統分配效率高,因此所存的是 value 的值以及 reference 的指針,可以看做一種簡單儲存,每個值的大小都差不多。

heap 佔用的空間大、自己存完還得分配指針到 stack → 效率相對低,用來存 reference 的值。

( stack 跟 heap 其實底層原理還得探討到資料結構,但這就偏題了,所以本篇不論述太多 )

好的~了解上面之後就先來看 pass by value

Pass by value

原始型別的值是以 pass by value 的形式操作,儲存以及複製的都是「值本身」

當我們的變數是原始型別時,複製這個變數,會發生什麼事?

var idol1 = 'Taeyeon'; // 字串=原始值
var idol2 = idol1;

第一行 宣告 idol1 為一個變數, idol1 在 stack 會指向 'Taeyeon' 這個值

接著宣告 idol2 = idol1 ,此時 pass by value 的做法會在 stack 複製一個新的 'Taeyeon' 並指向 idol2

https://ithelp.ithome.com.tw/upload/images/20210913/20141763poPStnDfxE.png

這種做法的坑在哪?我們現在把本來排定要第一場表演的 Taeyeon 改為 Joy

var idol1 = 'Taeyeon'; 
var idol2 = idol1;

idol1 = 'Joy'; // 更改 idol1 的值
console.log(idol2);

改變 idol1 的值後,你可能會想說 idol2 是從 idol1 複製來的,那 idol2 應該也是 Joy 對吧?

No~~~ 正如剛所說的,idol2 在 stack 其實是一個獨立的新值,所以 idol2 仍然是 Taeyeon !並不會被 idol1 影響。

https://ithelp.ithome.com.tw/upload/images/20210913/20141763pQeXDXDgF5.png

這就是 pass by value 的特性,當你的值為原始型別時,彼此形同陌路(誤。

Pass by reference

物件型別則是 pass by reference 引用數據類型

當我們創建一個變數並賦予值為物件型別時,他會在 stack 中存著一個指針,指針指向 heap 的值。此後,當 JS 解析你要取得該物件的時候,就先在 stack 檢索該地址,然後從 heap 取出實值。這麼解釋可能有點懞,看例子!

var obj1 = { name:'Agnes' }
var obj2 = ['apple','grape','lemon']

我們宣告了兩個物件,實際上是如此存儲的

https://ithelp.ithome.com.tw/upload/images/20210913/20141763UNsvomTkfm.png

obj1 在 stack 裡存的是一個檢索地址 0x001 ,這個 0x001 可以想成指針指向 heap 裡的值

那複製引用類型的值會發生什麼事?

var obj1 = { name:'Agnes' }
var obj2 = ['apple','grape','lemon']

var obj3 = obj1; // 把 obj3 賦值

這個動作會把 obj3 在 stack 的指針,指向跟 obj1 一樣的地址

https://ithelp.ithome.com.tw/upload/images/20210913/201417631M8jLtITAc.png

特性是在我們改變值時,會連帶影響到其他指向同地址的變數

var obj1 = ['apple','grape','lemon']
var obj2 = { name:'Agnes' }

var obj3 = obj1
obj3[0]='banana'

console.log(obj1)

去實際看會發現 obj1 也被改成 banana 了,這就是取用同個地址的值的結果

https://ithelp.ithome.com.tw/upload/images/20210913/20141763m5KRNZrsBx.png

所以要記得,如果是引用類型的數據~會影響彼此喔!

當了解這個觀念後,也許你會突然豁然開朗最初寫 code 的時候,為什麼有時候資料被改變,有時候卻沒有變?

再補充一個 pass by reference 的相關知識

我們剛所複製的方式是賦予另個變數,但如果是賦予完全同樣的值,那是會創建一個新地址喔!JS 並不會去比對「他們的值一樣,所以他們是同個物件。」

https://ithelp.ithome.com.tw/upload/images/20210913/20141763FXAopeFw7c.png

因此我們若更改 obj2 的值也與 obj4 無關

var obj1 = ['apple','grape','lemon']
var obj2 = { name:'Agnes' }

var obj3 = obj1
obj3[0]='banana'

var obj4 = { name:'Agnes' }
obj2.name = 'Bill' // 更改 obj2
console.log(obj4) // 依然是 Agnes

這時候頭腦動很快的小朋友就會開始想說,如果我想要擁有一樣的物件類型的值,但不要變動到原本的值該怎麼做?請 Google「深拷貝、淺拷貝」來處理!因為這觀念是個不小議題,這邊就不詳細說明,看之後有沒有篇幅再來筆記~

參考資料
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Data_structures
https://www.javascripttutorial.net/javascript-primitive-vs-reference-values/
https://roy-code.medium.com/普通類型和對象的區別-棧內存-stack-堆內存-heap-44295724848c
https://www.cnblogs.com/heioray/p/9487093.html


下一篇
執行環境 Execution Context、宣告提升 Hoisting
系列文
每日一杯 JavaScript 特調7
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言