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#

布林值,也就是真假值 truefalse,在許多的邏輯判斷句中一定會出現。

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

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)的物件,同樣也擁有自己的屬性與方法。例如先前提到過的 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 的全部型別,著重在容易被忽略的小功能與初學者容易困惑的地方,希望幫助讀者進一步認識這基礎中的基礎。

原文出處#

  • 原書/iThome:https://ithelp.ithome.com.tw/articles/10222207