iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 12
0

JavaScript 由於弱型別的語言特性,對於剛開始學習的新手來說,型別就跟暗戀的對象一樣,總是讓人捉摸不透,即使你用了 typeof 運算子來問他,也會得到意外的答案;今天就讓我們一起來深入認識 JavaScript 中的基礎 - 型別。

型別

在 JavaScript 中,定義了以下七種型別:

  • number:數字
  • string:字串
  • boolean:布林值
  • null:空值
  • undefined:未定義
  • object:物件
  • symbol:符號

我們先來看相對單純的基礎型別們吧:

number

不像一些傳統的語言把數字分成整數/浮點數,在 JavaScript 中數字全部都是 number 類型。例如:整數 123、浮點數 0.1、科學記號表示 24e6。可表達的最大整數為 9007199254740991,可以透過 Number.MAX_SAFE_INTEGER 取到這個值;同理,也有 Number.MIN_SAFE_INTEGER-9007199254740991,超過這個值的計算,可能會有玄妙的結果:

9007199254740991 + 1 === 9007199254740991 + 2 // true

另外,JavaScript 也有許多語言都有的 浮點數精度問題

0.1 + 0.2 // 0.30000000000000004

用來做數學運算時需要特別注意。

number 型別還有個很弔詭的東西叫做 NaN,是 Not a Number 的縮寫,會在開發者把不是數字的東西做運算時出現;雖然是「Not a Number」,但在 JavaScript 的規範中,它是屬於 number 型別的一部分:

typeof NaN // 'number'

還有,規範中也定義,NaN 必須不等於它自己:

NaN === NaN // false

最後在提一下另外一個奇怪的地方,JavaScript 中有 0-0 兩個 0,不過 -0 只會出現在對 0 加上減號(-)的情況,不影響一般的使用:

let a = 0
console.log(a)  // 0
console.log(-a) // -0

string

JavaScript 中的字串,從 ES6 之後便有三種字面宣告方式:

let a = "foo"
let b = 'bar'
let c = `fizz`

單雙引號皆為一般的字串,可以在內文中有另一者的時候交替使用:

let desc = "You're Awesome!"

或著也可以用 \ 作為跳脫字元

let desc = 'You\'re Awesome!'

反引號則比較不同;透過反引號建立的字串叫做 模板字符串(Template Literals),可以藉由 ${...} 關鍵字在其中插入其他變數:

let name = 'Gary'
console.log(`Hello, ${name}`) // Hello, Gary

同時,它也可以接在函式名稱後面,直接變成傳入函式的參數;這在 Code Golf 中是個很常用的技巧:

let tmp = 'apple,banana,candy,dock,element'
let arr1 = tmp.split(',')
let arr2 = tmp.split`,`  // 省了兩個字元!

boolean

布林值,也就是真假值、true & false,在許多的邏輯判斷句中一定會出現,相信大家也都不太陌生。

let flag = true

if (flag) { ... }

另外,在 JavaScript 中有 Truthy(真值)及 Falsy(假值)的概念,在簡化判斷句時經常用到;基本上開發者只要記得假值為:false0-0""nullundefinedNaN,其餘的數值全部都為真值,這樣就不會錯囉。

let arr = ['apple', 'banana', 'candy']

if ( ~arr.indexOf('dock')) {
  console.log('有')
} else {
  console.log('沒有')
}
// 沒有
// indexOf 回傳 -1,取補數為 0,falsy

null

空值,當開發者需要把數值明確定義為「空」的時候就可以將變數指定為 null;雖然是一個基本型別,不過成員只有它一個,且在 JavaScript 底層中其實是一個特製的物件:

typeof null // object

undefined

未定義,出現在變數宣告了但沒有值,或是物件內沒有這個屬性。

新手開發者很容易把它和 null 搞混,甚至交錯使用;其實可以把 undefined 簡單理解成變數/屬性的 placeholder,並不要把任何變數指定為 undefined,把兩種值的用途明確切割,就不會一團亂啦~

symbol

從 ES6 之後新增了 symbol,用來表達一個獨一無二的數值;在某些時候我們不太在乎值的名稱,只是需要一個唯一值,這時候就很適合用 symbol。

舉個例子,在使用前端框架如 Vue 或 React 時,有時候會需要使用操縱陣列資料並生成畫面,這時候需要指定 key 值,讓框架引擎計算變動的 Component 是誰,以避免每次都全部銷毀再重新創造;如果原本的資料沒有合用的唯一值,這時可以透過一個簡單的 Generator Function 搭配 Symbol,做出一個 key 值生成器。

let gen = (function* idMaker(){
  let i = 0
  while(i++ < i)
    yield Symbol(i)
})()

let key1 = gen.next().value // Symbol(1)

以上就是全部的基礎型別了,剩下沒提到的所有東西,在 JavaScript 中全都是物件。

特別的物件

先說說一般的物件吧,大家可能馬上想到的是類似這樣的格式:

let obj = {
  num: 123,
  key: 'value',
  func() {
    // do something
  }
}

物件中可以透過一組一組的鍵值對定義物件的屬性,以及類似宣告函式的方式定義物件方法;不過,在擁有 ES6 的物件縮寫特性之前,其實只能這樣定義物件的方法:

let obj = {
  // ...
  func: function () {
    // do something
  }
}

也是透過鍵值對,只是右邊放入的是函式。

函式

沒錯,在 JavaScript 中,函式是可被呼叫(Callable)的物件,同樣也擁有自己的屬性 & 方法。例如 前幾天 提到過的 namelengthcallbind 等等,我們也可以對函式物件的屬性設值,並不會影響函式的執行:

var a = 'foo'

function func() {
  console.log(this.a)
} 

func.a = 'bar'

func() // foo
func.call(func) //bar,call 的第一參數為指定 this

陣列

終於講到今天標題的題目了,為什麼 typeof new Array() === 'object' 呢?當然是因為陣列其實也是物件囉~

由於它只是物件,其實我們可以直接對陣列的屬性賦值,例如長度:

let arr = [1, 2, 3]
arr.length = 5
console.log(arr) // [1, 2, 3, empty × 2]
arr.length = 0
console.log(arr) // []

將陣列的長度設定為 0 可以快速清空陣列,是很實用的小技巧喔!

陣列物件中操縱陣列的方法非常多,對初學者來說這邊蠻容易在方法間混淆的;不過重點是要記得哪些方法會直接修改陣列,哪些方法是回傳新陣列,只要清楚這兩點就能掌握各種方法啦~

可能有讀者會想到物件實作的陣列會不會有效能問題。不過不用太擔心,JavaScript 引擎例如 V8,大都有在底層對陣列的運算做效能優化﹐就放心正常的使用吧!

原生物件

除了函式及陣列,還有幾種是原生 JavaScript 就定義好的特製物件,並有提供程度不一的語法支援。例如 RegExp 物件用來製作正規表達式的判斷物件,Date 物件負責處理時間等等,這邊就不一一詳述了。

結語

今天討論了 JavaScript 中全部的型別,內容較著重在可能會被開發者忽略的小功能,或是初學者容易產生困惑的地方,希望有幫助讀者您進一步的認識這基礎中的基礎。

本文就到這邊啦,明天我們再來聊聊這些型別可以擦出什麼火花,敬請大家期待!

參考資料

筆者

Gary

半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。

相信一切安排都是最好的路。


上一篇
11. [JS] 如何處理非同步事件?
下一篇
13. [JS] 為什麼判斷相等時不能用雙等號?
系列文
前端三十 - 成為更好的前端工程師31

尚未有邦友留言

立即登入留言