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`;單雙引號皆為一般的字串,可以在內文中有另一者的時候交替使用,或著也可以用 \ 作為跳脫字元。
反引號則比較不同;透過反引號建立的字串叫做樣板字串(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,在許多的邏輯判斷句中一定會出現。
在 JavaScript 中有 Truthy(真值)及 Falsy(假值)的概念,在簡化判斷句時經常用到;基本上開發者只要記得假值為:
false、0、-0、""、null、undefined和NaN,其餘的數值全部都為真值。
null#
空值;當開發者需要把數值明確定義為「空」的時候,就可以將變數指定為 null;雖然是一個基本型別,不過成員只有它一個,且在 JavaScript 底層中其實是一個特製的物件:
typeof null; // object
undefined#
未定義,出現在變數宣告但沒有值,或物件內沒有此屬性。
新手很容易將它與 null 搞混甚至交錯使用。可以把 undefined 理解成變數或屬性的 placeholder,並避免主動將變數指定為 undefined。將兩者用途切割清楚就不會一團亂。
symbol#
從 ES6 之後新增了 symbol,用來表達一個獨一無二的數值;在某些時候我們不太在乎值的名稱,只是需要一個唯一值,這時候就很適合用 symbol。
舉個例子,在使用前端框架如 Vue 或 React 時,有時候會需要操縱陣列資料並生成畫面,這時候需要指定 key 值;如果原本的資料沒有合用的唯一值,可以透過一個簡單的 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 的全部型別,著重在容易被忽略的小功能與初學者容易困惑的地方,希望幫助讀者進一步認識這基礎中的基礎。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10222207