JavaScript 由於弱型別的語言特性,對於剛開始學習的新手來說,型別就跟暗戀的對象一樣,總是讓人捉摸不透,即使你用了 typeof
運算子來問他,也會得到意外的答案;今天就讓我們一起來深入認識 JavaScript 中的基礎 - 型別。
本系列文已經重新編校彙整編輯成冊,並正式出版囉!
《前端三十:從 HTML 到瀏覽器渲染的前端開發者必備心法》好評販售中!
喜歡我文章內容的讀者們,歡迎您 前往購買 支持!
在 JavaScript 中,定義了以下七種型別:
我們先來看相對單純的基礎型別們吧:
不像一些傳統的語言把數字分成整數/浮點數,在 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
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`,` // 省了兩個字元!
布林值,也就是真假值、true
& false
,在許多的邏輯判斷句中一定會出現,相信大家也都不太陌生。
let flag = true
if (flag) { ... }
另外,在 JavaScript 中有 Truthy(真值)及 Falsy(假值)的概念,在簡化判斷句時經常用到;基本上開發者只要記得假值為:false
、0
、-0
、""
、null
、undefined
和 NaN
,其餘的數值全部都為真值,這樣就不會錯囉。
let arr = ['apple', 'banana', 'candy']
if ( ~arr.indexOf('dock')) {
console.log('有')
} else {
console.log('沒有')
}
// 沒有
// indexOf 回傳 -1,取補數為 0,falsy
空值,當開發者需要把數值明確定義為「空」的時候就可以將變數指定為 null
;雖然是一個基本型別,不過成員只有它一個,且在 JavaScript 底層中其實是一個特製的物件:
typeof null // object
未定義,出現在變數宣告了但沒有值,或是物件內沒有這個屬性。
新手開發者很容易把它和 null
搞混,甚至交錯使用;其實可以把 undefined 簡單理解成變數/屬性的 placeholder,並不要把任何變數指定為 undefined,把兩種值的用途明確切割,就不會一團亂啦~
從 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)的物件,同樣也擁有自己的屬性 & 方法。例如 前幾天 提到過的 name
、length
、call
、bind
等等,我們也可以對函式物件的屬性設值,並不會影響函式的執行:
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
半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。相信一切安排都是最好的路。
看完這篇
總覺得Typescript的語法糖有點奇怪呢
比如說像下面這個
p_list?: Point[];
明明Point[]在javascript算是"object"
可是用了?語法糖的p_list,它的型別卻是 Point[] | undefined
但undefined在javascript不是"object"
null在javascript才是"object"阿
我猜當初發明typescript的人
一定也不知道undefined在javascript算是基本型別吧
然後就Just do It了
let gen = (function* idMaker(){
let i = 0
while(i++ < i)
yield Symbol(i)
})()
let key1 = gen.next().value // Symbol(1)
這段其實看不懂