第 9 章。運算子
目錄
購買書籍
(廣告,請勿封鎖。)

第 9 章。運算子

本章 提供運算子的概觀。

運算子和物件

所有運算子 強制轉換 (如 強制轉換 中所述) 其運算元為適當的類型。大多數運算子僅適用於基本值 (例如,算術運算子與比較運算子)。這表示在對物件執行任何操作之前,會將其轉換為基本值。一個不幸的範例是加號運算子,許多語言將其用於陣列串接。然而,JavaScript 並非如此,此運算子會將陣列轉換為字串並附加它們:

> [1, 2] + [3]
'1,23'
> String([1, 2])
'1,2'
> String([3])
'3'

注意

無法在 JavaScript 中覆載或自訂運算子,甚至等於運算子也不行。

賦值運算子

有數種方式可以使用 純粹賦值運算子:

x = 值
賦值給先前已宣告的變數 x
var x = 值
將變數宣告與賦值結合
obj.propKey = 值
設定屬性
obj['propKey'] = 值
設定屬性
arr[index] = 值
設定陣列元素[10]

賦值是一種會評估為已賦值值的表達式。這允許您串接賦值。例如,下列陳述句會將 0 賦值給 yx

x = y = 0;

複合賦值運算子

一個 複合賦值運算子 寫成 op=,其中 op幾個二元運算子之一,而 = 是賦值運算子。下列兩個表達式是等效的:

myvar op= value
myvar = myvar op value

換句話說,複合賦值運算子 op= 會將 op 套用於兩個運算元,並將結果賦值給第一個運算元。讓我們來看一個使用加號運算子 (+) 透過複合賦值的範例

> var x = 2;
> x += 3
5
> x
5

以下是所有 複合賦值運算子:

等於運算子:=== 與 ==

JavaScript 有兩種方法可判斷兩個值是否相等:

  • 嚴格相等(===)和嚴格不相等(!==)僅將類型相同的兩個值視為相等。
  • 一般(或「寬鬆」)相等(==)和不相等(!=)會嘗試在比較不同類型值之前,先將其轉換為與嚴格(不)相等相同的類型。

寬鬆相等在兩個方面有問題。首先,它執行轉換的方式令人困惑。其次,由於運算子過於寬鬆,類型錯誤可能會隱藏更久。

務必使用嚴格相等,並避免使用寬鬆相等。只有在您想知道為什麼應該避免使用寬鬆相等時,才需要了解它。

相等無法自訂。運算子無法在 JavaScript 中重載,您也無法自訂相等運作的方式。在某些運算中,您通常需要影響比較,例如 Array.prototype.sort()(請參閱排序和反轉元素(具破壞性))。該方法選擇性地接受一個會執行陣列元素之間所有比較的回呼函式。

嚴格相等(===、!==)

類型不同的值絕不會嚴格相等。如果兩個值類型相同,則會成立下列斷言:

陷阱:NaN

特殊數字值 NaN(請參閱NaN)不等於它自己:

> NaN === NaN
false

因此,您需要使用其他方法來檢查它,這些方法說明於陷阱:檢查值是否為 NaN

嚴格不等式 (!==)

嚴格不等式比較:

x !== y

等於嚴格等式比較的否定

!(x === y)

正常(寬鬆)等式 (==, !=)

透過正常等式比較演算法運作方式如下。如果兩個運算元有相同的類型(六種規格類型之一—未定義、Null、布林、數字、字串和物件),則透過嚴格等式比較它們。

否則,如果運算元是

  1. undefinednull,則它們被視為寬鬆相等

    > undefined == null
    true
  2. 字串和數字,則將字串轉換為數字,並透過嚴格等式比較兩個運算元。
  3. 布林和非布林,則將布林轉換為數字,並寬鬆比較(再次)。
  4. 物件和數字或字串,則嘗試將物件轉換為基元(透過演算法:ToPrimitive()—將值轉換為基元中說明的演算法),並寬鬆比較(再次)。

否則—如果上述情況都不適用—寬鬆比較的結果為 false

寬鬆不等式 (!=)

不等式比較:

x != y

等於等式比較的否定

!(x == y)

陷阱:寬鬆等式不同於轉換為布林

步驟 3 表示等式和轉換為布林(請參閱轉換為布林)運作方式不同。如果轉換為布林,大於 1 的數字會變成 true(例如,在 if 陳述式中)。但那些數字並不會寬鬆等於 true。註解說明如何計算結果

> 2 == true  // 2 === 1
false
> 2 == false  // 2 === 0
false

> 1 == true  // 1 === 1
true
> 0 == false  // 0 === 0
true

類似地,雖然空字串等於 false,但並非所有非空字串都等於 true

> '' == false   // 0 === 0
true
> '1' == true  // 1 === 1
true
> '2' == true  // 2 === 1
false
> 'abc' == true  // NaN === 1
false

陷阱:寬鬆相等和字串

某些寬鬆性可能很有用,視乎你的需要而定:

> 'abc' == new String('abc')  // 'abc' == 'abc'
true
> '123' == 123  // 123 === 123
true

由於 JavaScript 將字串轉換為數字的方式(請參閱轉換為數字),其他情況會出現問題

> '\n\t123\r ' == 123  // usually not OK
true
> '' == 0  // 0 === 0
true

陷阱:寬鬆相等和物件

如果你比較一個物件與一個非物件,它會轉換為一個原始值,這會導致奇怪的結果:

> {} == '[object Object]'
true
> ['123'] == 123
true
> [] == 0
true

然而,只有兩個物件是同一個物件時,它們才相等。這表示你不能真正比較兩個包裝器物件

> new Boolean(true) === new Boolean(true)
false
> new Number(123) === new Number(123)
false
> new String('abc') == new String('abc')
false

沒有有效的 == 使用案例

你有時會讀到寬鬆相等(==)的有效使用案例。此部分會列出它們並指出更好的替代方案。

使用案例:檢查未定義或 null

下列比較確保 x 既不是 undefined 也不是 null

if (x != null) ...

雖然這是撰寫此檢查的簡潔方式,但它會讓初學者感到困惑,而專家無法確定它是否為錯字。因此,如果你想檢查 x 是否有值,請使用真值標準檢查(在真值和假值中涵蓋)

if (x) ...

如果你想更精確,你應該對兩個值執行明確檢查

if (x !== undefined && x !== null) ...

使用案例:使用字串中的數字

如果你不確定值 x 是數字還是數字字串,你可以使用下列檢查:

if (x == 123) ...

前述檢查 x 是否為 123'123'。同樣地,這非常簡潔,但最好明確說明

if (Number(x) === 123) ...

使用案例:比較包裝器實例與原始值

寬鬆相等讓你比較原始值與包裝原始值:

> 'abc' == new String('abc')
true

有三個理由反對這種方法。首先,寬鬆相等不適用於包裝原始值之間:

> new String('abc') == new String('abc')
false

其次,你應該避免使用包裝器。第三,如果你確實使用它們,最好明確說明

if (wrapped.valueOf() === 'abc') ...

排序運算子

JavaScript 知道下列排序運算子:

  • 小於 (<)
  • 小於或等於 (<=)
  • 大於 (>)
  • 大於或等於 (>=)

這些運算子適用於 數字和字串:

> 7 >= 5
true
> 'apple' < 'orange'
true

對於字串,它們不太有用,因為它們區分大小寫,而且無法妥善處理重音符號等功能(有關詳細資訊,請參閱 比較字串)。

演算法

您可以評估比較

x < y

透過執行 下列步驟:

  1. 確保兩個運算元都是 基本型別。物件 obj 會透過內部運算 ToPrimitive(obj, Number) 轉換為基本型別(請參閱 演算法: ToPrimitive()—將值轉換為基本型別),它會呼叫 obj.valueOf()obj.toString()(如果需要)來執行此動作。
  2. 如果兩個運算元都是字串,請透過比較表示字串的 JavaScript 字元的 16 位元組碼單元(請參閱 第 24 章)按字典順序來比較它們。
  3. 否則,將兩個運算元都轉換為數字,並以數值方式比較它們。

其他排序運算子以類似的方式處理。

加號運算子 (+)

粗略來說,加號運算子 會檢查其運算元。如果其中一個運算元是字串,另一個運算元也會轉換為字串,且兩個運算元會串接:

> 'foo' + 3
'foo3'
> 3 + 'foo'
'3foo'

> 'Colors: ' + [ 'red', 'green', 'blue' ]
'Colors: red,green,blue'

否則,兩個運算元都會轉換為數字(請參閱 轉換為數字),並相加

> 3 + 1
4
> 3 + true
4

這表示您評估的順序很重要

> 'foo' + (1 + 2)
'foo3'
> ('foo' + 1) + 2
'foo12'

演算法

您可以評估加法

value1 + value2

透過執行下列步驟

  1. 確保兩個運算元都是基本型別。物件 obj 會透過內部運算 ToPrimitive(obj) 轉換為基本型別(請參閱 演算法: ToPrimitive()—將值轉換為基本型別),它會呼叫 obj.valueOf()obj.toString()(如果需要)來執行此動作。對於日期,會先呼叫 obj.toString()
  2. 如果任一運算元是字串,請將兩個運算元都轉換為字串,並傳回結果的串接。
  3. 否則,將兩個運算元都轉換為數字,並傳回結果的總和。

布林值和數字的運算子

下列 運算子只有單一型別的運算元,而且也會產生該型別的結果。它們在其他地方有說明。

布林值運算子

數字運算子

特殊運算子

在此,我們將回顧特殊運算子,即條件運算子、逗號運算子,以及 void 運算子。

條件運算子 ( ? : )

條件 運算子是一個表達式:

«condition» ? «if_true» : «if_false»

如果條件為 true,則結果為 if_true;否則,結果為 if_false。例如

var x = (obj ? obj.prop : null);

運算子周圍的括號不是必需的,但它們使閱讀更輕鬆。

逗號運算子

«left» , «right»

逗號運算子評估兩個 運算元,並傳回 right 的結果。大致上,它對表達式所做的,與分號對陳述式所做的相同。

此範例說明第二個運算元變為運算子的結果

> 123, 'abc'
'abc'

此範例說明兩個運算元都已評估

> var x = 0;
> var y = (x++, 10);

> x
1
> y
10

逗號運算子會令人困惑。最好不要耍小聰明,只要有可能,就寫兩個獨立的陳述式。

void 運算子

void 運算子的語法 為:

void «expr»

它會評估 expr 並傳回 undefined。以下是一些範例

> void 0
undefined
> void (0)
undefined

> void 4+7  // same as (void 4)+7
NaN
> void (4+7)
undefined

> var x;
> x = 3
3
> void (x = 5)
undefined
> x
5

因此,如果您將 void 實作為一個函式,它看起來如下

function myVoid(expr) {
    return undefined;
}

void 運算子與其運算元緊密相關,因此請視需要使用括號。例如, void 4+7 會繫結為 (void 4)+7

為什麼 JavaScript 會有 void 運算子?

根據 JavaScript 創造者布蘭登·艾克,他將它加入語言中,以協助處理 javascript: 連結(上述使用案例之一):

我在 Netscape 2 發布之前,將 void 運算子加入 JS,以便在 javascript: URL 中輕鬆捨棄任何非 undefined 的值。[12]

透過 typeof 和 instanceof 分類值

如果您想要 分類一個值,很不幸地,您必須在 JavaScript 中區分基本型別和物件(請參閱 第 8 章

  • typeof 運算子區分基本型別和物件,並判斷基本型別的型別。
  • instanceof 運算子判斷一個物件是否為特定建構函式的執行個體。請參閱 第 17 章,以取得有關 JavaScript 中物件導向程式設計的更多資訊。

typeof:分類基本型別

運算子 typeof

typeof «value»

傳回描述 value 為何種值的字串。以下是一些範例

> typeof undefined
'undefined'
> typeof 'abc'
'string'
> typeof {}
'object'
> typeof []
'object'

typeof 用於區分基本型別和物件,以及分類基本型別(無法由 instanceof 處理)。很遺憾地,此運算子的結果並非完全合理,而且僅與 ECMAScript 規格的型別(在 JavaScript 的型別 中說明)大致對應

運算元 結果

undefined,未宣告的變數

'undefined'

null

'object'

布林值

'boolean'

數字值

'number'

字串值

'string'

函式

'function'

所有其他一般值

'object'

(引擎建立的值)

JavaScript 引擎可以建立 typeof 會傳回任意字串(不同於此表格中列出的所有結果)的值。

陷阱:typeof null

很遺憾地,typeof null'object'。這被視為錯誤(null 不是內部型別 Object 的成員),但無法修正,因為這樣做會破壞現有的程式碼。因此您必須小心 null。例如,下列函式會檢查 value 是否為物件:

function isObject(value) {
    return (value !== null
       && (typeof value === 'object'
           || typeof value === 'function'));
}

試用

> isObject(123)
false
> isObject(null)
false
> isObject({})
true

typeof null 的歷史

第一個 JavaScript 引擎將 JavaScript 值表示為 32 位元字。此類字的最低 3 位元用作型別標籤,以表示值是物件、整數、雙精度浮點數、字串或布林值(如您所見,即使是這個早期的引擎,也會盡可能將數字儲存為整數)。

物件的型別標籤為 000。為了表示值 null,引擎使用了機器語言 NULL 指標,一個所有位元都為零的字。typeof 檢查型別標籤以判斷值的型別,這就是它會回報 null 為物件的原因。[13]

檢查變數是否存在

檢查:

typeof x === 'undefined'

有兩個用例

  1. 它判斷 x 是否為 undefined
  2. 它決定變數 x 是否存在。

以下是這兩種使用案例的範例

> var foo;
> typeof foo === 'undefined'
true

> typeof undeclaredVariable === 'undefined'
true

對於第一個使用案例,通常比較好的選擇是直接與 undefined 進行比較。但是,它不適用於第二個使用案例

> var foo;
> foo === undefined
true

> undeclaredVariable === undefined
ReferenceError: undeclaredVariable is not defined

instanceof:檢查物件是否為給定建構函式的執行個體

instanceof 營運子:

«value» instanceof «Constr»

決定 value 是否已由建構函式 Constr 或子建構函式建立。以下是一些範例

> {} instanceof Object
true
> [] instanceof Array  // constructor of []
true
> [] instanceof Object  // super-constructor of []
true

正如預期的那樣,對於非值 undefinednullinstanceoffalse

> undefined instanceof Object
false
> null instanceof Object
false

但對於所有其他原始值,它也是 false

> 'abc' instanceof Object
false
> 123 instanceof Object
false

有關 instanceof 的詳細資訊,請參閱 instanceof 營運子



[10] 嚴格來說,設定陣列元素是設定屬性的特例。

[11] 感謝 Brandon Benvie (@benvie),他告訴我使用 void 來進行 IIFE。

[13] 感謝 Tom Schuster (@evilpies) 指出第一個 JavaScript 引擎的原始碼。

下一頁:10. 布林值