日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

《JavaScript权威指南第7版》第4章 表达式和运算符

發(fā)布時(shí)間:2023/12/20 javascript 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《JavaScript权威指南第7版》第4章 表达式和运算符 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

  • 4.1 原始表達(dá)式
  • 4.2 對象和數(shù)組的初始化表達(dá)式
  • 4.3 函數(shù)定義表達(dá)式
  • 4.4 屬性訪問表達(dá)式
    • 4.4.1 條件屬性訪問
  • 4.5 調(diào)用表達(dá)式
    • 4.5.1 條件調(diào)用
  • 4.6 對象創(chuàng)建表達(dá)式
  • 4.7 運(yùn)算符概覽
    • 4.7.1 操作數(shù)的數(shù)量
    • 4.7.2 操作數(shù)和結(jié)果類型
    • 4.7.3 運(yùn)算符副作用
    • 4.7.4 運(yùn)算符優(yōu)先級
    • 4.7.5 運(yùn)算符結(jié)合性
    • 4.7.6 求值順序
  • 4.8 算術(shù)表達(dá)式
    • 4.8.1 + 運(yùn)算符
    • 4.8.2 一元算術(shù)運(yùn)算符
    • 4.8.3 位運(yùn)算符
  • 4.9 關(guān)系表達(dá)式
    • 4.9.1 相等與不等運(yùn)算符
      • 嚴(yán)格相等
      • 帶類型轉(zhuǎn)換的相等
    • 4.9.2 比較運(yùn)算符
    • 4.9.3 in運(yùn)算符
    • 4.9.4 instanceof 運(yùn)算符
  • 4.10 邏輯表達(dá)式
    • 4.10.1 邏輯與(&&)
    • 4.10.2 邏輯或(||)
    • 4.10.3 邏輯非(!)
  • 4.11 賦值表達(dá)式
    • 4.11.1 帶操作的賦值
  • 4.12 表達(dá)式計(jì)算
    • 4.12.1 eval()
    • 4.12.2 全局 eval()
    • 4.12.3 嚴(yán)格 eval()
  • 4.13 其他運(yùn)算符
    • 4.13.1 條件運(yùn)算符 ( ?: )
    • 4.13.2 第一定義選擇運(yùn)算符 (??)
    • 4.13.3 typeof 運(yùn)算符
    • 4.13.4 delete 運(yùn)算符
    • 4.13.5 await 運(yùn)算符
    • 4.13.6 void 運(yùn)算符
    • 4.13.7 逗號運(yùn)算符 (,)
  • 4.14 總結(jié)

本章介紹了JavaScript表達(dá)式和構(gòu)建這些表達(dá)式所使用的運(yùn)算符。表達(dá)式是JavaScript的一個(gè)短語,可以對其求值以生成值。在程序中嵌入的常量是一種非常簡單的表達(dá)式。變量名也是一個(gè)簡單的表達(dá)式,它的計(jì)算結(jié)果是分配給該變量的任何值。復(fù)雜表達(dá)式是從更簡單的表達(dá)式中生成的。例如,數(shù)組訪問表達(dá)式由一個(gè)表達(dá)式組成,該表達(dá)式的計(jì)算結(jié)果為一個(gè)數(shù)組,后跟一個(gè)左方括號、一個(gè)計(jì)算結(jié)果為整數(shù)的表達(dá)式和一個(gè)右方括號。這個(gè)新的更復(fù)雜的表達(dá)式的計(jì)算結(jié)果是存儲(chǔ)在指定數(shù)組的指定索引處的值。類似地,函數(shù)調(diào)用表達(dá)式由一個(gè)計(jì)算結(jié)果為函數(shù)對象的表達(dá)式和零個(gè)或多個(gè)用作函數(shù)參數(shù)的其他表達(dá)式組成。

從簡單表達(dá)式構(gòu)建復(fù)雜表達(dá)式的最常見方法是使用運(yùn)算符。運(yùn)算符以某種方式組合其操作數(shù)的值(通常是其中的兩個(gè))并計(jì)算為新值。乘法運(yùn)算符*就是一個(gè)簡單的例子。表達(dá)式x*y的計(jì)算結(jié)果是表達(dá)式x和y的值的乘積。為了簡單起見,我們有時(shí)說運(yùn)算符返回值而不是“計(jì)算”值。

本章記錄了JavaScript的所有操作符,還解釋了不使用操作符的表達(dá)式(如數(shù)組索引和函數(shù)調(diào)用)。如果您已經(jīng)知道另一種使用C風(fēng)格語法的編程語言,那么您會(huì)發(fā)現(xiàn)JavaScript的大多數(shù)表達(dá)式和運(yùn)算符的語法你已經(jīng)很熟悉了。

4.1 原始表達(dá)式

最簡單的表達(dá)式,稱為原始表達(dá)式,是那些獨(dú)立的表達(dá)式,它們不包含任何更簡單的表達(dá)式。JavaScript中的原始表達(dá)式是常量或字面量、某些語言關(guān)鍵字和變量引用。

字面量是直接嵌入程序中的常量值。它們看起來像這樣:

1.23 // 數(shù)字字面量 "hello" // 字符串字面量 /pattern/ // 正則表達(dá)式字面量

第3.2節(jié)介紹了數(shù)字字面量的JavaScript語法。字符串字面量記錄在§3.3中。正則表達(dá)式字面量語法在§3.3.5中介紹,并將在§11.3中詳細(xì)記錄。

JavaScript的一些保留字是原始表達(dá)式:

true // 計(jì)算為布爾值 true false // 計(jì)算為布爾值 false null // 計(jì)算為空值 this // 計(jì)算為“當(dāng)前”對象

我們在§3.4和§3.5中了解了true、false和null。與其他關(guān)鍵字不同,this不是一個(gè)常量,它在程序的不同位置計(jì)算出不同的值。this關(guān)鍵字用于面向?qū)ο缶幊獭T谝粋€(gè)方法體中,this的計(jì)算結(jié)果是調(diào)用該方法的對象。詳見§4.5,第8章(尤其是§8.2.2)和第9章。

最后,第三種類型的原始表達(dá)式是變量、常量或者全局對象的屬性:

i // 計(jì)算變量i的值。 sum // 計(jì)算變量sum的值。 undefined // 全局對象的“undefined”屬性的值

當(dāng)任何標(biāo)識符單獨(dú)出現(xiàn)在程序中時(shí),JavaScript假定它是全局對象的變量、常量或?qū)傩?#xff0c;并查找其值。如果不存在具有該名稱的變量,則嘗試對不存在的變量求值時(shí)會(huì)引發(fā)ReferenceError。

4.2 對象和數(shù)組的初始化表達(dá)式

對象和數(shù)組初始化表達(dá)式是值為新創(chuàng)建的對象或數(shù)組的表達(dá)式。這些初始化表達(dá)式有時(shí)稱為對象字面量和數(shù)組字面量。但是,與true字面量不同,它們不是原始表達(dá)式,因?yàn)樗鼈儼S多指定屬性和元素值的子表達(dá)式。數(shù)組初始化表達(dá)式有一個(gè)稍微簡單的語法,我們將從這些語法開始。

數(shù)組的初始化表達(dá)式是一個(gè)逗號分隔的表達(dá)式列表,包含在方括號中。數(shù)組的初始化表達(dá)式的值是新創(chuàng)建的數(shù)組。此新數(shù)組的元素初始化為逗號分隔的表達(dá)式的值:

[] // 空數(shù)組:括號內(nèi)沒有表達(dá)式表示沒有元素 [1+2,3+4] // 2個(gè)元素?cái)?shù)組。第一個(gè)元素是3,第二個(gè)元素是7

數(shù)組中的元素表達(dá)式本身可以是數(shù)組初始化表達(dá)式,這意味著這些表達(dá)式可以創(chuàng)建嵌套數(shù)組:

let matrix = [[1,2,3], [4,5,6], [7,8,9]];

每次計(jì)算數(shù)組本身的值時(shí),都會(huì)計(jì)算數(shù)組中各項(xiàng)元素表達(dá)式的值。這意味著每次計(jì)算出來的數(shù)組的值可能不同。

只需省略逗號之間的值,就可以在數(shù)組文本中包含未定義的元素。例如,以下數(shù)組包含五個(gè)元素,包括三個(gè)未定義的元素:

let sparseArray = [1,,,,5];

在數(shù)組的初始化表達(dá)式中的最后一項(xiàng)后面允許有一個(gè)尾隨逗號,并且不會(huì)創(chuàng)建未定義的元素。但是訪問數(shù)組最后一項(xiàng)之后的元素都會(huì)被計(jì)算為未定義的值。

對象初始化表達(dá)式類似于數(shù)組初始化表達(dá)式,但方括號替換為大括號,并且每個(gè)子表達(dá)式都以屬性名和冒號作為前綴:

let p = { x: 2.3, y: -1.2 }; //具有兩個(gè)屬性的對象 let q = {}; //沒有屬性的空對象 q.x = 2.3; q.y = -1.2; // 現(xiàn)在q和p有相同的屬性

在ES6中,對象字面量有一個(gè)功能更豐富的語法(您可以在§6.10中找到詳細(xì)信息)。對象字面量可以嵌套。例如:

let rectangle = {upperLeft: { x: 2, y: 2 },lowerRight: { x: 4, y: 5 } };

我們將在第6章和第7章中再次看到對象和數(shù)組初始化表達(dá)式。

4.3 函數(shù)定義表達(dá)式

函數(shù)定義表達(dá)式定義一個(gè)JavaScript函數(shù),這個(gè)表達(dá)式的值就是新定義的函數(shù)。從某種意義上說,函數(shù)定義表達(dá)式是“函數(shù)字面量”,就像對象初始化表達(dá)式是“對象字面量”一樣。函數(shù)定義表達(dá)式通常由關(guān)鍵字function組成,后跟包含零個(gè)或多個(gè)標(biāo)識符(參數(shù)名)的逗號分隔列表的括號和一個(gè)包含在大括號中的JavaScript代碼塊(函數(shù)體)。例如:

// 此函數(shù)返回傳遞給它的值的平方。 let square = function(x) { return x * x; };

函數(shù)定義表達(dá)式還可以包含函數(shù)的名稱。也可以使用函數(shù)聲明而不是函數(shù)表達(dá)式來定義函數(shù)。在ES6和更高版本中,函數(shù)表達(dá)式可以使用一種緊湊的新的“箭頭函數(shù)”語法。有關(guān)函數(shù)定義的完整細(xì)節(jié)見第8章。

4.4 屬性訪問表達(dá)式

屬性訪問表達(dá)式的計(jì)算結(jié)果為對象屬性或數(shù)組元素的值。JavaScript為屬性訪問定義了兩種語法:

表達(dá)式 . 標(biāo)識符
表達(dá)式 [ 表達(dá)式 ]

屬性訪問的第一種樣式是表達(dá)式后面是句點(diǎn)和標(biāo)識符。表達(dá)式指定對象,標(biāo)識符指定所需屬性的名稱。第二種類型的屬性訪問在第一個(gè)表達(dá)式(對象或數(shù)組)后面,把另一個(gè)表達(dá)式放在方括號中。第二個(gè)表達(dá)式指定所需屬性的名稱或所需數(shù)組元素的索引。以下是一些具體的例子:

let o = {x: 1, y: {z: 3}}; // 示例對象 let a = [o, 4, [5, 6]]; // 包含對象的示例數(shù)組 o.x // => 1: 表達(dá)式o的屬性x o.y.z // => 3: 表達(dá)式 o.y 的屬性 z o["x"] // => 1: 對象 o 的屬性x a[1] // => 4: 表達(dá)式a的索引1處的元素 a[2]["1"] // => 6:表達(dá)式a[2]索引1處的元素 a[0].x // => 1: 表達(dá)式a[0]的屬性x

對于任何類型的屬性訪問表達(dá)式,.或[之前的表達(dá)式的值首先被計(jì)算,如果值為null或undefined,表達(dá)式將拋出TypeError,因?yàn)檫@兩個(gè)JavaScript值沒有屬性。如果對象表達(dá)式后面跟著一個(gè)點(diǎn)和一個(gè)標(biāo)識符,那么由該標(biāo)識符命名的屬性的值將被查找并成為表達(dá)式最終的值。如果對象表達(dá)式后面的方括號中有另一個(gè)表達(dá)式,那么計(jì)算第二個(gè)表達(dá)式并將其轉(zhuǎn)換為字符串。然后,表達(dá)式最終的值就是由該字符串命名的屬性的值。在這兩種情況下,如果命名屬性不存在,則屬性訪問表達(dá)式的值是undefined。

.標(biāo)識符語法是兩個(gè)屬性訪問選項(xiàng)中較簡單的一個(gè),但是請注意,只有當(dāng)您要訪問的屬性的名稱是合法標(biāo)識符時(shí),以及在編寫程序時(shí)知道該名稱時(shí),才能使用該語法。如果屬性名稱包含空格或標(biāo)點(diǎn)符號,或者是數(shù)字(對于數(shù)組),則必須使用方括號表示法。當(dāng)屬性名稱不是靜態(tài)的,而是計(jì)算的結(jié)果時(shí),也使用方括號(示例見§6.3.1)。

第6章詳細(xì)介紹了對象及其屬性,第7章介紹了數(shù)組及其元素。

4.4.1 條件屬性訪問

ES2020添加了兩種新的屬性訪問表達(dá)式:

表達(dá)式?. 標(biāo)識符
表達(dá)式 ?.[ 表達(dá)式 ]

在JavaScript中,值null和undefined是唯一兩個(gè)沒有屬性的值。在一般的屬性訪問表達(dá)式中使用.或者[],如果左邊的表達(dá)式計(jì)算結(jié)果為null或undefined,則會(huì)得到一個(gè)TypeError。你可以使用 ?.和?.[]語法防止此類錯(cuò)誤。

考慮一下表達(dá)式a?.b, 如果a為null或undefined,則表達(dá)式的計(jì)算結(jié)果為undefined,而不會(huì)再去嘗試訪問屬性b。如果a是其他值,則a?.b計(jì)算出來的結(jié)果等于a.b計(jì)算的結(jié)果(如果a沒有名為b的屬性,則該值還是undefined)。

這種形式的屬性訪問表達(dá)式有時(shí)稱為“自判斷鏈接”(optional chaining),因?yàn)樗策m用于較長的“鏈接”屬性訪問表達(dá)式,如下面所示:

let a = { b: null }; a.b?.c.d // => undefined

a是一個(gè)對象,所以a.b是一個(gè)有效的屬性訪問表達(dá)式。但是a.b的值為null,因此a.b.c將拋出一個(gè)TypeError。通過使用?.而不是.,我們避免了TypeError,a.b?.c計(jì)算為undefined。這意味著(a.b?.c).d將引發(fā)TypeError,因?yàn)樵摫磉_(dá)式試圖訪問undefined的屬性。但是,“自判斷鏈接”的一個(gè)非常重要的部分,那就是a.b?.c.d(不帶括號)計(jì)算為undefined,不拋出錯(cuò)誤。這是因?yàn)橛?.訪問屬性是“短路”操作:如果?.左邊的子表達(dá)式計(jì)算結(jié)果為null或undefined,則整個(gè)表達(dá)式立即計(jì)算為undefined,而無需進(jìn)行任何進(jìn)一步的屬性訪問嘗試。

譯者注:可以對比看一下

let a = {b:null}; a.b?.c.d; // undefined (a.b?.c).d; // Error: Cannot read property 'd' of undefined

當(dāng)然,如果a.b是一個(gè)對象,并且該對象沒有名為c的屬性,那么
a.b?.c.d將再次拋出一個(gè)TypeError,我們希望使用另一個(gè)條件屬性訪問:

let a = { b: {} }; a.b?.c?.d // => undefined

也可以使用?.[]替換[]進(jìn)行條件屬性訪問。在表達(dá)式a?.[b][c],如果a的值為null或undefined,則整個(gè)表達(dá)式立即計(jì)算為undefined,子表達(dá)式b和c永遠(yuǎn)都不會(huì)計(jì)算。如果這兩個(gè)表達(dá)式中的任何一個(gè)有副作用,則如果a的值是undefined或者是null,則不會(huì)發(fā)生副作用:

let a; // 哦,我們忘了初始化這個(gè)變量! let index = 0; try {a[index++]; // 拋出 TypeError } catch (e) {index // => 1: 在引發(fā)TypeError之前發(fā)生增加了1 } a?.[index++] // => undefined: 因?yàn)閍是未定義的 index // => 1: 未遞增,因?yàn)?.[] 是短路操作 a[index++] // !TypeError: 無法索引未定義的。

條件屬性訪問?.和?.[]是JavaScript的最新功能之一。到2020年初,大多數(shù)主流瀏覽器的當(dāng)前版本或beta版本都支持這種新語法。

4.5 調(diào)用表達(dá)式

調(diào)用表達(dá)式是用于調(diào)用(或執(zhí)行)函數(shù)或方法的JavaScript語法。它以標(biāo)識要調(diào)用的函數(shù)的函數(shù)表達(dá)式開頭。函數(shù)表達(dá)式后面跟著一個(gè)左括號、一個(gè)逗號分隔的零個(gè)或多個(gè)參數(shù)表達(dá)式列表以及一個(gè)右括號。一些例子:

f(0) // f是函數(shù)表達(dá)式;0是參數(shù)表達(dá)式。 Math.max(x,y,z) // Math.max是函數(shù);x、y和z是參數(shù)。 a.sort() // a.sort是函數(shù);沒有參數(shù)。

計(jì)算調(diào)用表達(dá)式時(shí),首先計(jì)算函數(shù)表達(dá)式,然后計(jì)算參數(shù)表達(dá)式以生成參數(shù)值列表。如果函數(shù)表達(dá)式的值不是函數(shù),則引發(fā)TypeError。接下來,參數(shù)值按順序分配給定義函數(shù)時(shí)指定的參數(shù)名,然后執(zhí)行函數(shù)體。如果函數(shù)使用return語句返回值,則該值將成為調(diào)用表達(dá)式的值。否則,調(diào)用表達(dá)式的值是未定義的。關(guān)于函數(shù)調(diào)用的完整細(xì)節(jié),包括解釋當(dāng)參數(shù)表達(dá)式的數(shù)量與函數(shù)定義中的參數(shù)數(shù)量不匹配時(shí)會(huì)發(fā)生什么,請參閱第8章。

每個(gè)調(diào)用表達(dá)式都包含一對括號和左括號前的表達(dá)式。如果該表達(dá)式是屬性訪問表達(dá)式,則該調(diào)用稱為方法調(diào)用。在方法調(diào)用中,作為屬性訪問主題的對象或數(shù)組在執(zhí)行函數(shù)體時(shí)成為this關(guān)鍵字的值。這使得一個(gè)面向?qū)ο蟮木幊谭独蔀榭赡?#xff0c;其中函數(shù)(我們稱之為“方法”)在它們所屬的對象上運(yùn)行。詳見第9章。

4.5.1 條件調(diào)用

在ES2020中,還可以使用?.()調(diào)用函數(shù)而不是()。通常,當(dāng)您調(diào)用函數(shù)時(shí),如果括號左側(cè)的表達(dá)式為null或undefined或任何其他非函數(shù),則會(huì)引發(fā)一個(gè)TypeError。用新的?.()調(diào)用語法,如果?.左側(cè)的表達(dá)式計(jì)算結(jié)果為null或undefined,則整個(gè)調(diào)用表達(dá)式的計(jì)算結(jié)果為undefined,并且不引發(fā)異常。

Array對象有一個(gè)sort()方法,可以選擇傳遞一個(gè)函數(shù)參數(shù),該參數(shù)定義數(shù)組元素所需的排序順序。在ES2020之前,如果您想編寫一個(gè)類似sort()的方法,它接受一個(gè)可選的函數(shù)參數(shù),在函數(shù)體中調(diào)用這個(gè)可選函數(shù)時(shí),您通常會(huì)使用if語句來檢查函數(shù)參數(shù)是否被定義:

function square(x, log) { // 第二個(gè)參數(shù)是可選函數(shù)if (log) { // 如果可選函數(shù)被傳遞log(x); // 調(diào)用它}return x * x; // 返回參數(shù)的平方 }

但是,使用ES2020的條件調(diào)用語法,您可以簡單地使用?.()編寫函數(shù)調(diào)用,并且知道只有在實(shí)際存在要調(diào)用的值時(shí)才會(huì)調(diào)用:

function square(x, log) { // 第二個(gè)參數(shù)是可選函數(shù)log?.(x); // 調(diào)用函數(shù)(如果有)return x * x; // 返回參數(shù)的平方 }

不過,請注意?.()只檢查左邊是null還是undefined。它不會(huì)驗(yàn)證該值是否實(shí)際上是一個(gè)函數(shù)。因此,例如,如果向這個(gè)示例中的square()函數(shù)傳遞了兩個(gè)數(shù)字,它仍然會(huì)引發(fā)異常。

與條件屬性訪問表達(dá)式(§4.4.1)一樣,使用?.()進(jìn)行函數(shù)調(diào)用是短路操作:如果?.左側(cè)的值null或undefined,則不會(huì)計(jì)算圓括號內(nèi)的任何參數(shù)表達(dá)式:

let f = null, x = 0; try {f(x++); // 拋出TypeError,因?yàn)閒為null } catch (e) {x // => 1: 在引發(fā)異常之前,x會(huì)遞增 } f?.(x++) // => undefined: f為null,但沒有引發(fā)異常 x // => 1: 由于短路而跳過增量

帶?.()的條件調(diào)用表達(dá)式對于方法和函數(shù)一樣有效。但是,由于方法調(diào)用也涉及屬性訪問,因此需要花點(diǎn)時(shí)間確保您理解以下表達(dá)式之間的差異:

o.m() // 常規(guī)屬性訪問,常規(guī)調(diào)用 o?.m() // 條件屬性訪問,常規(guī)調(diào)用 o.m?.() // 常規(guī)屬性訪問,條件調(diào)用

在第一個(gè)表達(dá)式中,o必須是具有屬性m的對象,并且該屬性的值必須是函數(shù)。在第二個(gè)表達(dá)式中,如果o為null或undefined,則表達(dá)式的計(jì)算結(jié)果為undefined。但是如果o有其他值,那么它必須有一個(gè)值為函數(shù)的屬性m。在第三個(gè)表達(dá)式中,o不能為null或undefined。如果它沒有屬性m,或者該屬性的值為null,則整個(gè)表達(dá)式的計(jì)算結(jié)果為undefined。

帶?.()的條件調(diào)用是JavaScript的最新特性之一。到2020年的頭幾個(gè)月,大多數(shù)主流瀏覽器的當(dāng)前版本或beta版本都支持這種新語法。

4.6 對象創(chuàng)建表達(dá)式

對象創(chuàng)建表達(dá)式創(chuàng)建一個(gè)新對象并調(diào)用一個(gè)函數(shù)(稱為構(gòu)造函數(shù))來初始化該對象的屬性。對象創(chuàng)建表達(dá)式與調(diào)用表達(dá)式類似,只是它們以關(guān)鍵字new作為前綴:

new Object() new Point(2,3)

如果對象創(chuàng)建表達(dá)式中沒有向構(gòu)造函數(shù)傳遞參數(shù),則可以省略空括號對:

new Object new Date

對象創(chuàng)建表達(dá)式的值是新創(chuàng)建的對象。第9章將更詳細(xì)地解釋構(gòu)造函數(shù)。

4.7 運(yùn)算符概覽

運(yùn)算符用于JavaScript的算術(shù)表達(dá)式、比較表達(dá)式、邏輯表達(dá)式、賦值表達(dá)式等。表4-1總結(jié)了這些運(yùn)算符,可作為參考。

請注意,大多數(shù)運(yùn)算符都用標(biāo)點(diǎn)符號表示,如+和=。但是,有些是用delete和instanceof等關(guān)鍵字表示的。關(guān)鍵字運(yùn)算符是常規(guī)運(yùn)算符,就像用標(biāo)點(diǎn)符號表示的運(yùn)算符一樣;它們的語法不太簡潔。

表4-1按運(yùn)算符優(yōu)先級排列。首先列出的運(yùn)算符的優(yōu)先級高于最后列出的運(yùn)算符。由空行分隔的運(yùn)算符具有不同的優(yōu)先級。標(biāo)記為A的列提供運(yùn)算符結(jié)合性,可以是L(從左到右)或R(從右到左),而列N指定操作數(shù)的數(shù)目。標(biāo)有“類型”的列列出了操作數(shù)的預(yù)期類型和運(yùn)算符的結(jié)果類型(在“→”符號之后)。下表后面的小節(jié)解釋了優(yōu)先級、結(jié)合性和操作數(shù)類型的概念。在討論之后,運(yùn)算符本身被單獨(dú)記錄下來。

表4-1 JavaScript運(yùn)算符

運(yùn)算符操作AN類型
++前增量或后增量R1lval→ num
前減量或后減量R1lval→num
-否定數(shù)R1num→num
+轉(zhuǎn)換為數(shù)字R1any→num
~反轉(zhuǎn)位R1int→int
!反轉(zhuǎn)布爾值R1bool→bool
delete刪除屬性R1lval→bool
typeof確定操作數(shù)類型R1any→str
void返回未定義值R1any→undef
**指數(shù)R2num,num→num
*, /, %乘、除、余數(shù)L2num,num→num
+, -加,減L2num,num→num
+連接字符串L2str,str→str
<<向左移動(dòng)L2int,int→int
>>有符號右移L2int,int→int
>>>無符號右移L2int,int→int
<, <=,>, >=按數(shù)字順序比較L2num,num→bool
<, <=,>, >=按字母順序比較L2str,str→bool
instanceof測試對象類L2obj,func→bool
in測試屬性是否存在L2any,obj→bool
==非嚴(yán)格相等性檢驗(yàn)L2any,any→bool
!=非嚴(yán)格不等性檢驗(yàn)L2any,any→bool
===嚴(yán)格相等性檢驗(yàn)L2any,any→bool
!==嚴(yán)格不相等性檢驗(yàn)L2any,any→bool
&按位與計(jì)算L2int,int→int
^計(jì)算位異或L2int,int→int
|按位或計(jì)算L2int,int→int
&&計(jì)算邏輯與L2any,any→any
||計(jì)算邏輯或L2any,any→any
??選擇第一個(gè)定義的操作數(shù)L2any,any→any
?:選擇第二個(gè)或第三個(gè)操作數(shù)R3bool,any,any→any
=分配給變量或?qū)傩?/td>R2lval,any→any
**=, *=, /=, %=, +=, -=, &=, ^=, |=,<<=, >>=, >>>=操作和分配R2lval,any→any
,丟棄第一個(gè)操作數(shù),返回第二個(gè)操作數(shù)L2any,any→any

4.7.1 操作數(shù)的數(shù)量

運(yùn)算符可以根據(jù)它們期望的操作數(shù)(它們的數(shù)量)進(jìn)行分類。大多數(shù)JavaScript運(yùn)算符,如*乘法運(yùn)算符,是將兩個(gè)表達(dá)式組合成一個(gè)更復(fù)雜的表達(dá)式的二進(jìn)制運(yùn)算符。也就是說,它們需要兩個(gè)操作數(shù)。JavaScript還支持許多一元運(yùn)算符,這些運(yùn)算符將單個(gè)表達(dá)式轉(zhuǎn)換為單個(gè)更復(fù)雜的表達(dá)式。表達(dá)式?x中的?是一元運(yùn)算符,對操作數(shù)x執(zhí)行求反運(yùn)算。最后,JavaScript支持一個(gè)三元運(yùn)算符,即條件運(yùn)算符?:,它將三個(gè)表達(dá)式組合為一個(gè)表達(dá)式。

4.7.2 操作數(shù)和結(jié)果類型

有些運(yùn)算符處理任何類型的值,但大多數(shù)運(yùn)算符都希望其操作數(shù)是特定類型的,而大多數(shù)運(yùn)算符返回(或計(jì)算為)特定類型的值。表4-1中的“類型”列指定了運(yùn)算符的操作數(shù)類型(箭頭之前)和結(jié)果類型(箭頭之后)。

JavaScript運(yùn)算符通常根據(jù)需要轉(zhuǎn)換其操作數(shù)的類型(見§3.9)。乘法運(yùn)算符*需要數(shù)字操作數(shù),但表達(dá)式"3"*"5"是合法的,因?yàn)镴avaScript可以將操作數(shù)轉(zhuǎn)換為數(shù)字。這個(gè)表達(dá)式的值是數(shù)字15,當(dāng)然不是字符串"15"。還要記住,每個(gè)JavaScript值要么是“真值”要么是“假值”,因此期望布爾操作數(shù)的運(yùn)算符將使用任何類型的操作數(shù)。

某些運(yùn)算符的行為因使用的操作數(shù)類型而異。最值得注意的是,+運(yùn)算符將數(shù)字操作數(shù)執(zhí)行加法操作,但字符串操作數(shù)進(jìn)行連接。類似地,<比較運(yùn)算符根據(jù)操作數(shù)的類型以數(shù)字或字母順序執(zhí)行比較。各個(gè)運(yùn)算符的描述解釋了它們的類型依賴關(guān)系,并指定了它們執(zhí)行的類型轉(zhuǎn)換。

請注意,賦值運(yùn)算符和表4-1中列出的一些其他運(yùn)算符都需要左值(lval)類型的操作數(shù)。左值是一個(gè)歷史術(shù)語,意思是“可以合法地出現(xiàn)在賦值表達(dá)式左側(cè)的表達(dá)式”。在JavaScript中,變量、對象屬性和數(shù)組元素都是左值。

4.7.3 運(yùn)算符副作用

計(jì)算像2*3這樣的簡單表達(dá)式永遠(yuǎn)不會(huì)影響程序的狀態(tài),并且您的程序?qū)韴?zhí)行的任何計(jì)算都不會(huì)受到該計(jì)算的影響。然而,有些表達(dá)式有副作用,它們的計(jì)算可能會(huì)影響將來的計(jì)算結(jié)果。賦值運(yùn)算符是最明顯的例子:如果將值賦給變量或?qū)傩?#xff0c;則會(huì)更改使用該變量或?qū)傩缘娜魏伪磉_(dá)式的值。++自增和--自減運(yùn)算符類似,因?yàn)樗鼈儓?zhí)行隱式賦值。delete操作符也有副作用:刪除屬性類似于(但不同于)將未定義的賦值給屬性。

沒有其他JavaScript操作符有副作用,但是如果函數(shù)或構(gòu)造函數(shù)體中使用的任何操作符有副作用,那么函數(shù)調(diào)用和對象創(chuàng)建表達(dá)式將產(chǎn)生副作用。

4.7.4 運(yùn)算符優(yōu)先級

表4-1中列出的運(yùn)算符按從高優(yōu)先級到低優(yōu)先級的順序排列,空行將處于相同優(yōu)先級的運(yùn)算符組分隔開。運(yùn)算符優(yōu)先級控制執(zhí)行操作的順序。優(yōu)先級較高的運(yùn)算符(靠近表的頂部)將在優(yōu)先級較低的運(yùn)算符(更接近底部)之前執(zhí)行。

考慮以下表達(dá)式:

w = x + y*z;

乘法運(yùn)算符*的優(yōu)先級高于加法運(yùn)算符+,因此乘法在加法之前執(zhí)行。此外,賦值運(yùn)算符=的優(yōu)先級最低,因此在右側(cè)的所有操作完成后執(zhí)行賦值。

運(yùn)算符優(yōu)先級可以通過顯式使用括號重寫。要強(qiáng)制先執(zhí)行上一個(gè)示例中的加法,請寫下:

w = (x + y)*z;

請注意,屬性訪問和調(diào)用表達(dá)式的優(yōu)先級高于表4-1中列出的任何運(yùn)算符。考慮以下表達(dá)式:

//my是一個(gè)具有名為functions的屬性的對象,其值為 //函數(shù)數(shù)組。我們調(diào)用函數(shù)位置為x的函數(shù),給函數(shù)傳遞參數(shù)y //,然后我們詢問返回值的類型。 typeof my.functions[x](y)

雖然typeof是優(yōu)先級最高的運(yùn)算符之一,但typeof操作是在屬性訪問、數(shù)組索引和函數(shù)調(diào)用的結(jié)果上執(zhí)行的,所有這些操作的優(yōu)先級都高于運(yùn)算符。

實(shí)際上,如果您根本不確定運(yùn)算符的優(yōu)先級,最簡單的方法是使用括號使求值順序明確。重要的規(guī)則是:乘法和除法在加減法之前執(zhí)行,賦值的優(yōu)先級很低,幾乎總是最后執(zhí)行。

當(dāng)新的操作符被添加到JavaScript中時(shí),它們并不總是自然地適合這個(gè)優(yōu)先方案。這個(gè)??運(yùn)算符(§4.13.2)在表中顯示為比||和&&優(yōu)先級低,但事實(shí)上,它相對于這些運(yùn)算符的優(yōu)先級沒有定義,ES2020要求您在混合使用??和||或&&時(shí)顯式使用括號。類似地,新的**求冪運(yùn)算符相對于一元求反運(yùn)算符沒有定義良好的優(yōu)先級,并且在將求反與求冪組合時(shí)必須使用括號。

4.7.5 運(yùn)算符結(jié)合性

在表4-1中,標(biāo)有A的列指定了運(yùn)算符的結(jié)合性。值L指定從左到右的結(jié)合性,值R指定從右到左的結(jié)合性。指定按同一運(yùn)算符的優(yōu)先順序執(zhí)行的操作。從左到右的結(jié)合性意味著操作從左到右執(zhí)行。例如,減法運(yùn)算符具有從左到右的結(jié)合性,因此:

w = x - y - z;

等同于:

w = ((x - y) - z);

另一方面,以下表達(dá)式:

y = a ** b ** c; x = ~-y; w = x = y = z; q = a?b:c?d:e?f:g;

相當(dāng)于:

y = (a ** (b ** c)); x = ~(-y); w = (x = (y = z)); q = a?b:(c?d:(e?f:g));

因?yàn)榍髢纭⒁辉①x值和三元條件運(yùn)算符具有從右到左的結(jié)合性。

4.7.6 求值順序

運(yùn)算符優(yōu)先級和結(jié)合性指定了在復(fù)雜表達(dá)式中執(zhí)行操作的順序,但它們不指定計(jì)算子表達(dá)式的順序。JavaScript總是嚴(yán)格按照左到右的順序計(jì)算表達(dá)式。例如,在表達(dá)式w = x + y * z中,首先計(jì)算子表達(dá)式w,然后再計(jì)算x、y和z。然后將y和z的值相乘,加到x的值上,并賦給表達(dá)式w指定的變量或?qū)傩浴T诒磉_(dá)式中添加括號可以改變乘法、加法、加法的相對順序,而不是從左到右的求值順序。

只有當(dāng)被求值的表達(dá)式有影響另一個(gè)表達(dá)式值的副作用時(shí),求值順序才有區(qū)別。如果表達(dá)式 x 增加一個(gè)被表達(dá)式 z 使用的變量,那么 x 在 z 之前求值就很重要。

4.8 算術(shù)表達(dá)式

本節(jié)介紹對其操作數(shù)執(zhí)行算術(shù)或其他數(shù)值操作的運(yùn)算符。求冪、乘法、除法和減法運(yùn)算符都很簡單,首先介紹。加法運(yùn)算符會(huì)有專門的小節(jié)介紹,因?yàn)樗€可以執(zhí)行字符串連接,并且有一些不尋常的類型轉(zhuǎn)換規(guī)則。一元運(yùn)算符和位運(yùn)算符也包含在它們各自的小節(jié)中。

這些算術(shù)運(yùn)算符中的大多數(shù)(除了下面提到的)都可以與BigInt(見§3.2.5)操作數(shù)或普通數(shù)字一起使用,只要不混合使用這兩種類型。

基本的算術(shù)運(yùn)算符是**(求冪)、*(乘法)、/(除)、%(模:除后的余數(shù))、+(加法)和-(減法)。如前所述,我們將在一節(jié)中討論+運(yùn)算符。其他五個(gè)基本運(yùn)算符只需計(jì)算其操作數(shù),如有必要,將值轉(zhuǎn)換為數(shù)字,然后計(jì)算冪、積、商、余數(shù)或差。無法轉(zhuǎn)換為數(shù)字的非數(shù)字操作數(shù)將轉(zhuǎn)換為NaN值。如果任一操作數(shù)是(或轉(zhuǎn)換為)NaN,則運(yùn)算結(jié)果(幾乎總是)NaN。

**運(yùn)算符的優(yōu)先級高于*、/、和%(它們的優(yōu)先級又高于+和-)。與其他運(yùn)算符不同,**從右向左工作,因此2**2**3與2**8相同,而不是4**3。像-3**2這樣的表達(dá)式自然存在歧義。根據(jù)一元減數(shù)和求冪的相對優(yōu)先順序,該表達(dá)式可能意味著(-3)**2或-( 3**2)。不同的語言處理這一點(diǎn)的方式不同,JavaScript比較明確,將省略括號作為語法錯(cuò)誤,從而迫使您編寫一個(gè)明確的表達(dá)式。**是JavaScript最新的算術(shù)運(yùn)算符:它是通過ES2016添加到語言中的。Math.pow()函數(shù)從JavaScript的最早版本開始就可用了,它執(zhí)行的操作與**運(yùn)算符完全相同。

運(yùn)算符/將第一個(gè)操作數(shù)除以第二個(gè)操作數(shù)。如果您習(xí)慣于使用區(qū)分整數(shù)和浮點(diǎn)數(shù)的編程語言,那么當(dāng)您將一個(gè)整數(shù)除以另一個(gè)整數(shù)時(shí),您可能希望得到一個(gè)整數(shù)結(jié)果。然而,在JavaScript中,所有的數(shù)字都是浮點(diǎn)的,所以所有除法運(yùn)算都有浮點(diǎn)結(jié)果:5/2的計(jì)算結(jié)果是2.5,而不是2。除以零得到正無窮大或負(fù)無窮大,而0/0的計(jì)算結(jié)果是NaN:這兩種情況都不會(huì)產(chǎn)生錯(cuò)誤。

%運(yùn)算符是第一個(gè)操作數(shù)對第二個(gè)操作數(shù)取余。換句話說,它返回第一個(gè)操作數(shù)除以第二個(gè)操作數(shù)整除之后的余數(shù)。結(jié)果的符號與第一個(gè)操作數(shù)的符號相同。例如,5%2的計(jì)算值為1,而-5%2的計(jì)算值為-1。

雖然取模運(yùn)算符通常用于整數(shù)操作數(shù),但它也適用于浮點(diǎn)值。例如,6.5%2.1的計(jì)算結(jié)果為0.2。
譯者注:實(shí)際運(yùn)行結(jié)果如下

6.5 % 2.1 // 0.19999999999999973

4.8.1 + 運(yùn)算符

二元+運(yùn)算符將數(shù)字操作數(shù)相加或連接字符串操作數(shù):

1 + 2 // => 3 "hello" + " " + "there" // => "hello there" "1" + "2" // => "12"

如果兩個(gè)操作數(shù)的值都是數(shù)字,或者都是字符串,那么+運(yùn)算符的作用就顯而易見了。但是,在任何其他情況下,類型轉(zhuǎn)換是必需的,要執(zhí)行的操作取決于所執(zhí)行的轉(zhuǎn)換。+的轉(zhuǎn)換規(guī)則優(yōu)先考慮字符串連接:如果其中一個(gè)操作數(shù)是字符串或轉(zhuǎn)換為字符串的對象,則另一個(gè)操作數(shù)將轉(zhuǎn)換為字符串并執(zhí)行連接。只有當(dāng)兩個(gè)操作數(shù)都不是字符串類型時(shí)才執(zhí)行加法。

技術(shù)上,+操作符的行為如下:

  • 如果它的任一操作數(shù)值是對象,則使用§3.9.3中描述的對象到原始類型的轉(zhuǎn)換算法。Date對象通過其toString()方法進(jìn)行轉(zhuǎn)換,而所有其他對象則通過valueOf()方法進(jìn)行轉(zhuǎn)換,前提是該方法返回一個(gè)原始值。但是,大多數(shù)對象沒有有用的valueOf()方法,因此它們也通過toString()進(jìn)行轉(zhuǎn)換。
  • 對象到原始類型轉(zhuǎn)換后,如果任一操作數(shù)是字符串,另一個(gè)操作數(shù)轉(zhuǎn)換為字符串并執(zhí)行連接。
  • 否則,兩個(gè)操作數(shù)都轉(zhuǎn)換為數(shù)字(或NaN)并執(zhí)行加法。

以下是一些示例:

1 + 2 // => 3: 加法 "1" + "2" // => "12": 連接 "1" + 2 // => "12": 數(shù)字轉(zhuǎn)換為字符串后連接 1 + {} // => "1[object Object]": 對象轉(zhuǎn)換為字符串后進(jìn)行連接 true + true // => 2: 布爾轉(zhuǎn)換成整數(shù)后進(jìn)行加法 2 + null // => 2: null轉(zhuǎn)換成0后進(jìn)行加法 2 + undefined // => NaN: undefined轉(zhuǎn)換成NaN后進(jìn)行加法

最后,需要注意的是,當(dāng)+運(yùn)算符與字符串和數(shù)字一起使用時(shí),它可能沒有結(jié)合性。也就是說,結(jié)果可能取決于執(zhí)行操作的順序。

例如:

1 + 2 + " blind mice" // => "3 blind mice" 1 + (2 + " blind mice") // => "12 blind mice"

第一行沒有圓括號,而且+運(yùn)算符具有從左到右的結(jié)合性,因此先將兩個(gè)數(shù)字相加,然后將它們的和與字符串連接起來。在第二行中,括號改變了操作的順序:數(shù)字2與字符串連接以產(chǎn)生一個(gè)新的字符串。然后將數(shù)字1與新字符串連接以生成最終結(jié)果。

4.8.2 一元算術(shù)運(yùn)算符

一元運(yùn)算符修改單個(gè)操作數(shù)的值以生成新值。在JavaScript中,一元運(yùn)算符的優(yōu)先級都很高,并且都是右結(jié)合的。如有必要,本節(jié)中描述的算術(shù)一元運(yùn)算符(+、-、++和–)都將其單個(gè)操作數(shù)轉(zhuǎn)換為數(shù)字。注意,標(biāo)點(diǎn)符號+和-同時(shí)用作一元運(yùn)算符和二元運(yùn)算符。

一元算術(shù)運(yùn)算符如下:

一元加 (+)

一元正運(yùn)算符將其操作數(shù)轉(zhuǎn)換為數(shù)字(或NaN),然后返回轉(zhuǎn)換后的值。當(dāng)與已經(jīng)是數(shù)字的操作數(shù)一起使用時(shí),它不執(zhí)行任何操作。此運(yùn)算符不能與BigInt值一起使用,因?yàn)樗鼈儾荒苻D(zhuǎn)換為常規(guī)數(shù)字。

一元減 (-)

當(dāng)-用作一元運(yùn)算符時(shí),如果需要,它將其操作數(shù)轉(zhuǎn)換為數(shù)字,然后更改結(jié)果的符號。

遞增 (++)

++運(yùn)算符遞增(即,將1加到)其單個(gè)操作數(shù),該操作數(shù)必須是左值(變量、數(shù)組的元素或?qū)ο蟮膶傩?#xff09;。運(yùn)算符將其操作數(shù)轉(zhuǎn)換為數(shù)字,將1加到該數(shù)字上,然后將遞增的值賦回變量、元素或?qū)傩浴?/p>

++運(yùn)算符的返回值取決于它相對于操作數(shù)的位置。當(dāng)在操作數(shù)之前使用時(shí),即所謂的前遞增運(yùn)算符,它將操作數(shù)遞增并計(jì)算為該操作數(shù)的增量值。當(dāng)在操作數(shù)之后使用時(shí),即所謂的后遞增運(yùn)算符,它將增加其操作數(shù),但計(jì)算結(jié)果為該操作數(shù)的未遞增值。考慮這兩行代碼之間的區(qū)別:

let i = 1, j = ++i; // i 和 j 都是2 let n = 1, m = n++; // n 是 2, m 是 1

請注意,表達(dá)式x++并不總是與x=x+1相同。++運(yùn)算符從不執(zhí)行字符串連接:它總是將操作數(shù)轉(zhuǎn)換為數(shù)字并遞增。如果x是字符串“1”,++x是數(shù)字2,但x+1是字符串“11”。

另外請注意,由于JavaScript自動(dòng)插入分號,因此不能在后遞增運(yùn)算符和它前面的操作數(shù)之間插入換行符。如果這樣做,JavaScript將把操作數(shù)本身視為一個(gè)完整的語句,并在其前面插入分號。

該運(yùn)算符在其增量前和后兩種形式中,最常用于遞增控制for循環(huán)的計(jì)數(shù)器(§5.4.3)。

遞減 (–)

– 運(yùn)算符需要左值操作數(shù)。它將操作數(shù)的值轉(zhuǎn)換為數(shù)字,減去1,然后將遞減的值賦回操作數(shù)。與++運(yùn)算符一樣,-- 的返回值取決于它相對于操作數(shù)的位置。在操作數(shù)之前使用時(shí),它使操作數(shù)遞減,返回遞減后的值。當(dāng)在操作數(shù)之后使用時(shí),它將使操作數(shù)遞減,但返回未經(jīng)遞減的值。在操作數(shù)之后使用時(shí),操作數(shù)和運(yùn)算符之間不允許換行。

4.8.3 位運(yùn)算符

位運(yùn)算符對數(shù)字的二進(jìn)制表示中的位執(zhí)行低級操作。雖然它們不執(zhí)行傳統(tǒng)的算術(shù)運(yùn)算,但在這里它們被歸類為算術(shù)運(yùn)算符,因?yàn)樗鼈儗?shù)值操作數(shù)進(jìn)行運(yùn)算并返回一個(gè)數(shù)值。其中四個(gè)運(yùn)算符在操作數(shù)的各個(gè)位上執(zhí)行布爾代數(shù),表現(xiàn)得好像每個(gè)操作數(shù)中的每個(gè)位都是布爾值(1=真,0=假)。另外三個(gè)按位運(yùn)算符用于左右移動(dòng)位。這些操作符在JavaScript編程中不常用,如果您不熟悉整數(shù)的二進(jìn)制表示,包括兩個(gè)負(fù)整數(shù)的補(bǔ)碼表示,您可能可以跳過這一節(jié)。

位運(yùn)算符需要整數(shù)操作數(shù),其行為就像這些值表示為32位整數(shù)而不是64位浮點(diǎn)值。如果需要,這些運(yùn)算符將其操作數(shù)轉(zhuǎn)換為數(shù)字,然后通過刪除任何小數(shù)部分和超出32位的任何位將數(shù)值強(qiáng)制轉(zhuǎn)換為32位整數(shù)。移位運(yùn)算符需要一個(gè)介于0和31之間的右側(cè)操作數(shù)。將此操作數(shù)轉(zhuǎn)換為無符號32位整數(shù)后,它們將丟棄第5位以外的任何位,這將生成適當(dāng)范圍內(nèi)的數(shù)字。令人驚訝的是,NaN、Infinity和-Infinity在用作這些按位運(yùn)算符的操作數(shù)時(shí)都會(huì)轉(zhuǎn)換為0。

除>>>之外,所有這些位運(yùn)算符都可以與常規(guī)數(shù)操作數(shù)或BigInt(見§3.2.5)操作數(shù)一起使用。

位與 (&)
& 運(yùn)算符對其整數(shù)參數(shù)的每一位執(zhí)行布爾與操作。只有在兩個(gè)操作數(shù)中都設(shè)置了相應(yīng)的位時(shí),才會(huì)在結(jié)果中設(shè)置一個(gè)位。例如,0x1234和0x00FF的計(jì)算值為0x0034。
位或 (|)
| 運(yùn)算符對其整型參數(shù)的每一位執(zhí)行布爾或運(yùn)算。如果在一個(gè)或兩個(gè)操作數(shù)中設(shè)置了相應(yīng)的位,則在結(jié)果中設(shè)置一個(gè)位。例如,0x1234 | 0x00FF的計(jì)算結(jié)果為0x12FF。
位異或 (^)
^ 運(yùn)算符對其整數(shù)參數(shù)的每一位執(zhí)行布爾異或操作。異或是指第一個(gè)操作數(shù)為true或第二個(gè)操作數(shù)為true,但是不能兩者同時(shí)為true。如果在兩個(gè)操作數(shù)的一個(gè)(但不是兩個(gè))中設(shè)置了相應(yīng)的位,則此操作的結(jié)果中設(shè)置一個(gè)位。例如,0xFF00 ^0xF0F0的計(jì)算值為0x0FF0。
位非 (~)
~運(yùn)算符是一元運(yùn)算符,出現(xiàn)在其單個(gè)整數(shù)操作數(shù)之前。它通過反轉(zhuǎn)操作數(shù)中的所有位來操作。由于JavaScript中有符號整數(shù)的表示方式,對值應(yīng)用~運(yùn)算符相當(dāng)于更改其符號并減去1。例如,~0x0F的計(jì)算結(jié)果為0xFFFFFFF0或?16。
左移 (<<)
<<運(yùn)算符將第一個(gè)操作數(shù)中的所有位向左移動(dòng)第二個(gè)操作數(shù)中指定的位數(shù),第二個(gè)操作數(shù)應(yīng)為0到31之間的整數(shù)。例如,在操作a<<1時(shí),a的第一位成為第二位,a的第二位成為第三位,等等。新的第一位使用零,第32位的值丟失。將值左移一個(gè)位置相當(dāng)于乘以2,移動(dòng)兩個(gè)位置相當(dāng)于乘以4,依此類推。例如,7<<2等于28。
有符號右移 (>>)
>>運(yùn)算符將第一個(gè)操作數(shù)中的所有位向右移動(dòng)第二個(gè)操作數(shù)中指定的位數(shù)(0到31之間的整數(shù))。右移的位丟失。左邊填充的位取決于原始操作數(shù)的符號位,以便保留結(jié)果的符號。如果第一個(gè)操作數(shù)為正,則結(jié)果的高位填充0;如果第一個(gè)操作數(shù)為負(fù),則結(jié)果的高位填充1。將正值右移一位相當(dāng)于除以2(舍棄余數(shù)),右移兩位相當(dāng)于整數(shù)除以4,依此類推。例如,7>>1的計(jì)算結(jié)果為3,但請注意,?7>>1的計(jì)算結(jié)果為?4。
無符號右移 (>>>)
>>>運(yùn)算符與>>運(yùn)算符一樣,只是左側(cè)移入的位始終為零,而不考慮第一個(gè)操作數(shù)的符號。如果要將有符號的32位值視為無符號整數(shù),則這很有用。例如,?1>>4的計(jì)算結(jié)果為?1,但?1>>>4的計(jì)算結(jié)果為0x0FFFFFFF。這是唯一不能與BigInt值一起使用的JavaScript位運(yùn)算符。BigInt不會(huì)像32位整數(shù)那樣設(shè)置高位來表示負(fù)數(shù),而且這個(gè)運(yùn)算符只對特定的2的補(bǔ)碼表示有意義。

4.9 關(guān)系表達(dá)式

本節(jié)介紹JavaScript的關(guān)系運(yùn)算符。這些運(yùn)算符測試兩個(gè)值之間的關(guān)系(例如“等于”、“小于”或“是…屬性”),并根據(jù)該關(guān)系是否存在返回true或false。關(guān)系表達(dá)式總是返回一個(gè)布爾值,該值通常用于控制if、while和語句中的程序執(zhí)行流(參見第5章)。下面的小節(jié)記錄了等式和不等式操作符、比較操作符和JavaScript的另外兩個(gè)關(guān)系操作符in和instanceof。

4.9.1 相等與不等運(yùn)算符

==和===運(yùn)算符使用兩個(gè)不同的相同定義來檢查兩個(gè)值是否相同。兩個(gè)運(yùn)算符都接受任何類型的操作數(shù),如果它們的操作數(shù)相同,則返回true;如果它們不同,則返回false。===運(yùn)算符被稱為嚴(yán)格相等運(yùn)算符(有時(shí)也稱為恒等運(yùn)算符),它使用嚴(yán)格的相同定義檢查兩個(gè)操作數(shù)是否“相同”。==運(yùn)算符被稱為相等運(yùn)算符;它使用允許類型轉(zhuǎn)換的更寬松的相同定義來檢查其兩個(gè)操作數(shù)是否“相等”。

這個(gè)!=還有!==運(yùn)算符測試==和===運(yùn)算符相反的結(jié)果。這個(gè)!=不等式運(yùn)算符根據(jù)==的計(jì)算結(jié)果,如果兩個(gè)值相等,則返回false,否則返回true。這個(gè)!==如果兩個(gè)值嚴(yán)格相等,運(yùn)算符返回false,否則返回true。正如您在§4.10中看到的那樣!運(yùn)算符計(jì)算布爾非運(yùn)算。這樣就很容易記住了!=還有!==表示“不等于”和“不嚴(yán)格等于”。

=,==,=== 運(yùn)算符
JavaScript支持=、==、和===運(yùn)算符。在編碼時(shí),一定要理解賦值,相等,嚴(yán)格相等這些運(yùn)算符之間的區(qū)別,以便做出正確的選擇!盡管將這三個(gè)運(yùn)算符都讀作“相等”很有吸引力,但是如果將“=”讀作“獲取”或“賦值”,”==“讀作“相等”,以及”===“讀作”嚴(yán)格相等“,這可能有助于減少混淆。==操作符是JavaScript的一個(gè)遺留特性,被廣泛認(rèn)為是bug的來源。您幾乎應(yīng)該始終使用===而不是==,!==而不是!=。

如§3.8所述,JavaScript對象是通過引用而不是按值進(jìn)行比較的。一個(gè)對象等于它自己,但不等于任何其他對象。如果兩個(gè)不同的對象具有相同數(shù)量的屬性,并且具有相同的名稱和值,則它們?nèi)匀徊幌嗟取n愃频?#xff0c;兩個(gè)具有相同順序元素的數(shù)組也不相等。

嚴(yán)格相等

嚴(yán)格相等運(yùn)算符===計(jì)算其操作數(shù),然后按如下方式比較兩個(gè)值,不執(zhí)行類型轉(zhuǎn)換:

  • 如果這兩個(gè)值具有不同的類型,則它們不相等。
  • 如果兩個(gè)值都為null或undefined,則它們相等。
  • 如果兩個(gè)值都是布爾值true或兩者都是布爾值false,則它們相等。
  • 如果一個(gè)或兩個(gè)值都是NaN,則它們不相等。(這很奇怪,但是NaN值永遠(yuǎn)不等于任何其他值,包括它本身!要檢查值x是否為NaN,請使用x !==x,或全局 isNaN()函數(shù)。)
  • 如果兩個(gè)值都是數(shù)字且值相同,則它們相等。如果一個(gè)值為0,另一個(gè)值為-0,則它們也相等。
  • 如果兩個(gè)值都是字符串,并且在相同的位置包含完全相同的16位值(見§3.3中的側(cè)欄),則它們相等。如果字符串的長度或內(nèi)容不同,它們就不相等。兩個(gè)字符串可能具有相同的含義和相同的顯示形式,但仍然使用不同的16位值序列進(jìn)行編碼。JavaScript不執(zhí)行Unicode規(guī)范化,這樣的一對字符串被認(rèn)為通過===或==運(yùn)算符的計(jì)算結(jié)果仍然不相等。
  • 如果兩個(gè)值引用同一個(gè)對象、數(shù)組或函數(shù),則它們是相等的。如果它們引用不同的對象,即使兩個(gè)對象具有相同的屬性,它們也不相等。

帶類型轉(zhuǎn)換的相等

相等運(yùn)算符==類似于嚴(yán)格的相等運(yùn)算符,但不太嚴(yán)格。如果兩個(gè)操作數(shù)的值不是同一類型,它將嘗試一些類型轉(zhuǎn)換并再次嘗試比較:

  • 如果這兩個(gè)值具有相同的類型,請按照前面所述測試它們是否嚴(yán)格相等。如果它們嚴(yán)格地說是相等的,那么它們是相等的。如果它們不嚴(yán)格地相等,它們就不相等。
  • 如果這兩個(gè)值的類型不同,==運(yùn)算符仍可能認(rèn)為它們相等。它使用以下規(guī)則和類型轉(zhuǎn)換檢查相等性:
    – 如果一個(gè)值為null,另一個(gè)值是undefined,則它們相等。
    – 如果一個(gè)值是數(shù)字,另一個(gè)是字符串,請將字符串轉(zhuǎn)換為數(shù)字,然后使用轉(zhuǎn)換后的值重試比較。
    – 如果任一值為true,請將其轉(zhuǎn)換為1,然后重試比較。如果任一值都為false,請將其轉(zhuǎn)換為0,然后重試比較。
    – 如果一個(gè)值是對象,另一個(gè)值是數(shù)字或字符串,請使用§3.9.3中描述的算法將對象轉(zhuǎn)換為原始類型,然后重試比較。對象通過其toString()方法或valueOf()方法轉(zhuǎn)換為原始類型。核心JavaScript的內(nèi)置類在toString()轉(zhuǎn)換之前嘗試valueOf()轉(zhuǎn)換,但只做toString()轉(zhuǎn)換的Date類除外。
    – 任何其他值的比較都不相等。

作為相等測試的一個(gè)例子,請考慮以下比較:

"1" == true // => true

此表達(dá)式的計(jì)算結(jié)果為true,說明這些外觀迥異的值實(shí)際上是相等的。布爾值true首先轉(zhuǎn)換為數(shù)字1,然后再次進(jìn)行比較。接下來,字符串“1”被轉(zhuǎn)換為數(shù)字1。由于兩個(gè)值現(xiàn)在相同,因此比較返回true。

4.9.2 比較運(yùn)算符

比較運(yùn)算符測試兩個(gè)操作數(shù)的相對順序(數(shù)字或字母順序):

小于 (<)
如果<運(yùn)算符的第一個(gè)操作數(shù)小于第二個(gè)操作數(shù),則計(jì)算結(jié)果為true
操作數(shù);否則,計(jì)算結(jié)果為false。

大于(>)
如果>運(yùn)算符的第一個(gè)操作數(shù)大于第二個(gè)操作數(shù),則計(jì)算結(jié)果為true
操作數(shù);否則,計(jì)算結(jié)果為false。

小于等于(<=)
如果<=運(yùn)算符的第一個(gè)操作數(shù)小于或等于第二個(gè)操作數(shù),則計(jì)算結(jié)果為true;否則,計(jì)算結(jié)果為false。

大于等于(>=)
如果>=運(yùn)算符的第一個(gè)操作數(shù)大于或等于第二個(gè)操作數(shù),則計(jì)算結(jié)果為true;否則,計(jì)算結(jié)果為false。

這些比較運(yùn)算符的操作數(shù)可以是任何類型。但是,只能對數(shù)字和字符串執(zhí)行比較,因此不是數(shù)字或字符串的操作數(shù)會(huì)進(jìn)行類型轉(zhuǎn)換。

比較和轉(zhuǎn)換規(guī)則如下:

  • 如果任一操作數(shù)的計(jì)算結(jié)果為對象,則該對象將轉(zhuǎn)換為原始值,如§3.9.3末尾所述;如果其valueOf()方法返回一個(gè)原始值,則使用該值。否則,將使用其toString()方法的返回值。
  • 如果在任何必需的對象到原始值轉(zhuǎn)換后,兩個(gè)操作數(shù)都是字符串,使用字母順序比較兩個(gè)字符串,其中“字母順序”是由組成字符串的16位Unicode值的數(shù)字順序定義的。
  • 如果在對象到原始類型轉(zhuǎn)換后,至少有一個(gè)操作數(shù)不是字符串,則兩個(gè)操作數(shù)都將轉(zhuǎn)換為數(shù)字并進(jìn)行數(shù)值比較。
  • 0和-0被認(rèn)為是相等的。Infinity比除它自己以外的任何數(shù)都大,-Infinity 比除它自己以外的任何數(shù)都小。如果其中一個(gè)操作數(shù)是(或轉(zhuǎn)換為)NaN,則比較運(yùn)算符始終返回false。雖然算術(shù)運(yùn)算符不允許將BigInt值與常規(guī)數(shù)字混合使用,但是比較運(yùn)算符允許在數(shù)字和BigInt之間進(jìn)行比較。

請記住,JavaScript字符串是16位整數(shù)值的序列,字符串比較只是兩個(gè)字符串中值的數(shù)值比較。Unicode定義的數(shù)字編碼順序可能與任何特定語言或區(qū)域設(shè)置中使用的傳統(tǒng)排序順序不匹配。請?zhí)貏e注意,字符串比較區(qū)分大小寫,并且所有大寫ASCII字母都“小于”所有小寫ASCII字母。如果您沒有預(yù)料到,此規(guī)則可能會(huì)導(dǎo)致混淆的結(jié)果。例如,根據(jù)<運(yùn)算符,字符串“Zoo”在字符串“aardvark”之前。

要獲得更健壯的字符串比較算法,請嘗試String.localeCompare()方法,它還考慮了特定于區(qū)域設(shè)置的字母順序定義。對于不區(qū)分大小寫的比較,可以使用String.toLowerCase()或String.toUpperCase()。 而且,要獲得更通用、更本地化的字符串比較工具,請使用§11.7.3所描述的Intl.Collator類。

對于數(shù)值操作數(shù)和字符串操作數(shù),+運(yùn)算符和比較運(yùn)算符的行為都不同。+支持字符串:如果其中一個(gè)操作數(shù)是字符串,則執(zhí)行連接運(yùn)算。比較運(yùn)算符偏愛數(shù)字,并且僅當(dāng)兩個(gè)操作數(shù)都是字符串時(shí)才執(zhí)行字符串比較:

1 + 2 // => 3: 相加. "1" + "2" // => "12": 連接. "1" + 2 // => "12": 2 轉(zhuǎn)換成 "2". 11 < 3 // => false: 數(shù)字比較. "11" < "3" // => true: 字符串比較. "11" < 3 // => false: 數(shù)字比較, "11" 轉(zhuǎn)換成 11. "one" < 3 // => false: 數(shù)字比較, "one" 轉(zhuǎn)換成 NaN.

最后,請注意<=(小于或等于)和>=(大于或等于)運(yùn)算符不依賴于相等或嚴(yán)格相等運(yùn)算符來確定兩個(gè)值是否為“相等”。相反,小于或等于運(yùn)算符被簡單定義為“不大于,大于或等于運(yùn)算符被定義為“不小于”。當(dāng)任一操作數(shù)為(或轉(zhuǎn)換為)NaN時(shí)會(huì)發(fā)生特殊情況,所有四個(gè)比較運(yùn)算符都返回false。

4.9.3 in運(yùn)算符

in運(yùn)算符要求左側(cè)操作數(shù)是可以轉(zhuǎn)換為字符串的字符串、符號或值。它期望右邊的操作數(shù)是一個(gè)對象。如果左側(cè)值是右側(cè)對象的屬性名稱,則計(jì)算結(jié)果為true。例如:

let point = {x: 1, y: 1}; // 定義一個(gè)對象 "x" in point // => true: 對象具有名為“x”的屬性 "z" in point // => false: 對象沒有“z”屬性。 "toString" in point // => true: 對象繼承toString方法 let data = [7,8,9]; // 元素(索引)為0、1和2的數(shù)組 "0" in data // => true: 數(shù)組具有元素“0” 1 in data // => true: 數(shù)字被轉(zhuǎn)換成字符串 3 in data // => false: 無元素3

4.9.4 instanceof 運(yùn)算符

instanceof操作符需要一個(gè)左側(cè)操作數(shù),該操作數(shù)是一個(gè)對象,而右側(cè)操作數(shù)用于標(biāo)識對象類。如果左側(cè)對象是右側(cè)類的實(shí)例,則運(yùn)算符的計(jì)算結(jié)果為true,否則計(jì)算結(jié)果為false。第9章解釋了在JavaScript中,對象類是由初始化對象的構(gòu)造函數(shù)定義的。因此,instanceof的右側(cè)操作數(shù)應(yīng)該是一個(gè)函數(shù)。以下是示例:

let d = new Date(); // 使用Date()構(gòu)造函數(shù)創(chuàng)建一個(gè)新對象 d instanceof Date // => true: d是用Date()創(chuàng)建的 d instanceof Object // => true:所有對象都是 Object 的實(shí)例 d instanceof Number // => false: d不是 Number 對象 let a = [1, 2, 3]; // 使用字面量語法創(chuàng)建數(shù)組 a instanceof Array // => true: a 是一個(gè)數(shù)組 a instanceof Object // => true: 所有數(shù)組都是Object實(shí)例 a instanceof RegExp // => false: 數(shù)組不是正則表達(dá)式

請注意,所有對象都是Object的實(shí)例。instanceof在決定一個(gè)對象是否是類的實(shí)例時(shí)會(huì)考慮“超類”。如果instanceof的左側(cè)操作數(shù)不是對象,則instanceof返回false。如果右邊不是一個(gè)對象類,它拋出一個(gè)TypeError。

為了理解instanceof操作符是如何工作的,你必須理解“原型鏈”。這是JavaScript的繼承機(jī)制,§6.3.2對此進(jìn)行了描述。為了計(jì)算表達(dá)式o instanceof f,JavaScript計(jì)算f.prototype,然后在o的prototype鏈中查找該值。如果找到,則o是f的實(shí)例(或f的子類的實(shí)例),運(yùn)算符返回true。如果f.prototype不是o的prototype鏈中的值之一,那么o不是f的實(shí)例,instanceof返回false。

4.10 邏輯表達(dá)式

邏輯運(yùn)算符 &&,||和!執(zhí)行布爾代數(shù),通常與關(guān)系運(yùn)算符結(jié)合使用,將兩個(gè)關(guān)系表達(dá)式組合成一個(gè)更復(fù)雜的表達(dá)式。下面的小節(jié)將介紹這些運(yùn)算符。為了全面理解它們,您可能需要回顧一下§3.4中引入的“真值”和“假值”的概念。

4.10.1 邏輯與(&&)

&&運(yùn)算符可以在三個(gè)不同的層次上理解。在最簡單的層次上,當(dāng)與布爾操作數(shù)一起使用時(shí),&&對兩個(gè)值執(zhí)行布爾與運(yùn)算:當(dāng)且僅當(dāng)其第一個(gè)操作數(shù)和第二個(gè)操作數(shù)都為真時(shí),才會(huì)返回true。如果其中一個(gè)或兩個(gè)操作數(shù)都為false,則返回false。

&&常用作連接兩個(gè)關(guān)系表達(dá)式:

x === 0 && y === 0 // 當(dāng)且僅當(dāng)x和y均為0時(shí)為真

關(guān)系表達(dá)式的計(jì)算結(jié)果總是為true或false,因此當(dāng)這樣使用時(shí),&&運(yùn)算符本身將返回true或false。關(guān)系運(yùn)算符的優(yōu)先級高于&&(和| |),因此這樣的表達(dá)式可以安全地不用括號編寫。

但&&不要求其操作數(shù)是布爾值。回想一下,所有JavaScript值都是“真值”或“假值”。(詳見§3.4。假值為false、null、undefined、0、-0、NaN和""。所有其他的值,包括所有的對象,都是真值。)第二層可以理解為真值和假值的布爾和運(yùn)算符。如果兩個(gè)操作數(shù)都是真值,則運(yùn)算符返回一個(gè)真值。否則,一個(gè)或兩個(gè)操作數(shù)都必須是假值,并且運(yùn)算符返回一個(gè)假值。在JavaScript中,任何期望布爾值的表達(dá)式或語句都將使用真值或假值,因此&&不總是返回true或false這一事實(shí)不會(huì)引起實(shí)際問題。

注意,這個(gè)描述說操作符返回“一個(gè)真值”或“一個(gè)假值”,但沒有指定該值是什么。為此,我們需要在第三層和最后一層的角度進(jìn)行理解。此運(yùn)算符首先計(jì)算第一個(gè)操作數(shù),即左側(cè)的表達(dá)式。如果左邊的值是假值,那么整個(gè)表達(dá)式的值也必須是假值,所以&&只返回左邊的值,甚至不計(jì)算右邊的表達(dá)式。

另一方面,如果左側(cè)的值是真值,則表達(dá)式的整體值取決于右側(cè)的值。如果右邊的值是真值,那么最后結(jié)果的值一定是真值,如果右邊的值是假值,那么最后結(jié)果的值一定是假值。因此,當(dāng)左邊的值為真值時(shí),&&運(yùn)算符計(jì)算并返回右邊的值:

let o = {x: 1}; let p = null; o && o.x // => 1: o 是真值, 所以返回o.x的值 p && p.x // => null: p是假值, 所以返回p,不再計(jì)算p.x

重要的是要了解&&可能會(huì)也可能不會(huì)計(jì)算其右側(cè)操作數(shù)。在這個(gè)代碼示例中,變量p設(shè)置為null,表達(dá)式p.x如果求值,將導(dǎo)致TypeError。但是代碼以慣用的方式使用&&,只有當(dāng)p是真值而不是null或undefined時(shí),才計(jì)算p.x。

&&的行為有時(shí)稱為短路,有時(shí)您可能會(huì)看到一些代碼故意利用此行為有條件地執(zhí)行代碼。例如,以下兩行JavaScript代碼具有等效效果:

if (a === b) stop(); // 僅當(dāng)a===b時(shí)調(diào)用stop() (a === b) && stop(); // 效果相當(dāng)

通常,在&&的右側(cè)編寫具有副作用(賦值、遞增、遞減或函數(shù)調(diào)用)的表達(dá)式時(shí),必須小心。這些副作用是否發(fā)生取決于左側(cè)的值。

盡管這個(gè)運(yùn)算符的實(shí)際工作方式有點(diǎn)復(fù)雜,但它最常用作處理真值和假值的簡單布爾代數(shù)運(yùn)算符。

4.10.2 邏輯或(||)

運(yùn)算符 || 對其兩個(gè)操作數(shù)執(zhí)行布爾或運(yùn)算。如果一個(gè)或兩個(gè)操作數(shù)都是真值,則返回一個(gè)真值。如果兩個(gè)操作數(shù)都是假值,則返回一個(gè)假值。

盡管 || 運(yùn)算符最常用作布爾或運(yùn)算符,但它與&&運(yùn)算符一樣,具有更復(fù)雜的行為。它首先計(jì)算第一個(gè)操作數(shù),即左邊的表達(dá)式。如果第一個(gè)操作數(shù)的值是真值,那么它將短路并返回該真值,而不計(jì)算右側(cè)的表達(dá)式。另一方面,如果第一個(gè)操作數(shù)的值是假值,則 || 計(jì)算第二個(gè)操作數(shù)并返回該表達(dá)式的值。

與&&運(yùn)算符一樣,應(yīng)避免包含副作用的右側(cè)操作數(shù),除非您有意使用右側(cè)表達(dá)式可能無法求值的事實(shí)。

此運(yùn)算符的慣用用法是在一組備選方案中選擇第一個(gè)真值:

// 如果maxWidth是真值,那么就使用它。否則,請?jiān)趐references對象中查找值。 // 如果這都不是真值,使用硬編碼常量。 let max = maxWidth || preferences.maxWidth || 500;

請注意,如果0是maxWidth的合法值,則此代碼將無法正常工作,因?yàn)?是假值。可以查看??運(yùn)算符($4.13.2)做為備選。

在ES6之前,此習(xí)慣用法通常用于函數(shù)中,為參數(shù)提供默認(rèn)值:

// 將o的屬性復(fù)制到p,然后返回p function copy(o, p) {p = p || {}; // 如果沒有給參數(shù)p傳遞對象,則創(chuàng)建一個(gè)新對象。// 函數(shù)體在這里 }

然而,在ES6和更高版本中,不再需要這種技巧,因?yàn)槟J(rèn)參數(shù)值可以簡單地寫入函數(shù)定義本身:functioncopy(o,p={}){ … }.

4.10.3 邏輯非(!)

這個(gè)!運(yùn)算符是一元運(yùn)算符;它放在單個(gè)操作數(shù)之前。它的目的是反轉(zhuǎn)其操作數(shù)的布爾值。例如,如果x是真值,!x的計(jì)算結(jié)果為false。如果x是假值,那么!x是true。

與&&和 || 運(yùn)算符不同,! 運(yùn)算符在反轉(zhuǎn)轉(zhuǎn)換值之前,將其操作數(shù)轉(zhuǎn)換為布爾值(使用第3章中描述的規(guī)則)。這就是說 ! 始終返回true或false,并且您可以通過兩次應(yīng)用此運(yùn)算符將任何值x轉(zhuǎn)換為其等效布爾值:!!x(見§3.9.2)。

作為一元運(yùn)算符, ! 優(yōu)先級高,綁定緊密。如果您想反轉(zhuǎn)像p&&q這樣的表達(dá)式的值,您需要使用括號:!(p&&q)。值得注意的是,我們可以使用JavaScript語法來表達(dá)布爾代數(shù)的兩個(gè)定律:

//德莫根定律 !(p && q) === (!p || !q) //=>true:對于p和q的所有值 !(p || q) === (!p && !q) //=>true:對于p和q的所有值

4.11 賦值表達(dá)式

JavaScript使用=運(yùn)算符為變量或?qū)傩再x值。例如:

i = 0; // 將變量i設(shè)置為0。 o.x = 1; // 將對象o的屬性x設(shè)置為1。

=運(yùn)算符希望其左側(cè)操作數(shù)為左值:變量或?qū)ο髮傩?#xff08;或數(shù)組元素)。它期望其右側(cè)操作數(shù)是任意類型的值。賦值表達(dá)式的值是右側(cè)操作數(shù)的值。作為一個(gè)副作用,=運(yùn)算符將右側(cè)的值分配給左側(cè)的變量或?qū)傩?#xff0c;以便將來對變量或?qū)傩缘囊糜?jì)算為該值。

盡管賦值表達(dá)式通常非常簡單,但有時(shí)您可能會(huì)看到賦值表達(dá)式的值用作較大表達(dá)式的一部分。例如,可以使用以下代碼在同一表達(dá)式中賦值和測試:

(a = b) === 0

如果您這樣做,請確保您清楚地知道 = 和 === 運(yùn)算符之間的區(qū)別!注意,= 的優(yōu)先級很低,當(dāng)賦值的值要用在更大的表達(dá)式中時(shí),括號通常是必需的。

賦值運(yùn)算符具有從右到左的結(jié)合性,這意味著當(dāng)表達(dá)式中出現(xiàn)多個(gè)賦值運(yùn)算符時(shí),它們將從右向左求值。因此,您可以編寫這樣的代碼,將單個(gè)值賦給多個(gè)變量:

i = j = k = 0; // 將3個(gè)變量初始化為0

4.11.1 帶操作的賦值

除了常見的 = 運(yùn)算符外,JavaScript還支持許多其他賦值運(yùn)算符,這些運(yùn)算符通過將賦值與其他一些操作組合起來提供快捷方式。例如,+=運(yùn)算符執(zhí)行加法和賦值。以下表達(dá)式:

total += salesTax;

等同于:

total = total + salesTax;

正如您所料,+=運(yùn)算符適用于數(shù)字或字符串。對于數(shù)值操作數(shù),它執(zhí)行加法和賦值;對于字符串操作數(shù),它執(zhí)行連接和賦值。

類似的運(yùn)算符包括-=,*=,&=,等等。表4-2列出了它們。

表4-2. 賦值運(yùn)算符

運(yùn)算符示例等同于
+=a += ba = a + b
-=a -= ba = a - b
*=a *= ba = a * b
/=a /= ba = a / b
%=a %= ba = a % b
**=a **= ba = a ** b
<<=a <<= ba = a << b
>>=a >>= ba = a >> b
>>>=a >>>= ba = a >>> b
&=a &= ba = a & b
|=a |= ba = a | b
^=a ^= ba = a ^ b

大多數(shù)情況下,表達(dá)式:

a op= b

這里op是一個(gè)運(yùn)算符,等同于如下表達(dá)式:

a = a op b

在第一行中,表達(dá)式a求值一次。第二種方法是求值兩次。只有當(dāng)a包含諸如函數(shù)調(diào)用或增量運(yùn)算符之類的副作用時(shí),這兩種情況才會(huì)不同。例如,以下兩個(gè)賦值就不一樣:

data[i++] *= 2; data[i++] = data[i++] * 2;

4.12 表達(dá)式計(jì)算

與許多解釋語言一樣,JavaScript能夠解釋JavaScript源代碼的字符串,并對其進(jìn)行計(jì)算來生成值。JavaScript使用全局函數(shù)eval()來實(shí)現(xiàn)這一點(diǎn):

eval("3+2") // => 5

對源代碼字符串的動(dòng)態(tài)求值是一個(gè)強(qiáng)大的語言特性,在實(shí)踐中幾乎從來沒有必要。如果您發(fā)現(xiàn)自己正在使用eval(),則應(yīng)仔細(xì)考慮是否確實(shí)需要使用它。尤其是eval()可能是一個(gè)安全漏洞,您永遠(yuǎn)不應(yīng)該將從用戶輸入派生的任何字符串傳遞給eval()。對于像JavaScript這樣復(fù)雜的語言,不可能對用戶輸入進(jìn)行清理以使其安全地與eval()一起使用。由于這些安全問題,一些web服務(wù)器使用HTTP “Content-security-Policy”標(biāo)頭禁用整個(gè)網(wǎng)站的eval()。

下面的小節(jié)介紹eval()的基本用法,并解釋它的兩個(gè)受限制版本,它們對優(yōu)化器的影響較小。

eval()是函數(shù)還是運(yùn)算符?
eval()是一個(gè)函數(shù),但它包含在關(guān)于表達(dá)式的這一章中,因?yàn)樗鼘?shí)際上應(yīng)該是一個(gè)運(yùn)算符。該語言的最早版本定義了eval()函數(shù),從那時(shí)起,語言設(shè)計(jì)者和解釋器編寫者一直對它施加限制,使其越來越像運(yùn)算符。現(xiàn)代JavaScript解釋器執(zhí)行大量的代碼分析和優(yōu)化。一般來說,如果函數(shù)調(diào)用eval(),解釋器將無法優(yōu)化該函數(shù)。將eval()定義為函數(shù)的問題是它可以被賦予其他名稱:

let f = eval; let g = f;

如果允許這樣做,那么解釋器就無法確定哪些函數(shù)調(diào)用eval(),因此無法進(jìn)行積極的優(yōu)化。如果eval()是運(yùn)算符(和保留字),則可以避免此問題。我們將學(xué)習(xí)(在§4.12.2和§4.12.3)對eval()的限制,使其更像運(yùn)算符。

4.12.1 eval()

eval()需要一個(gè)參數(shù)。如果傳遞字符串以外的任何值,它只返回該值。如果傳遞一個(gè)字符串,它將嘗試將該字符串解析為JavaScript代碼,如果失敗,則拋出SyntaxError。如果成功解析字符串,則計(jì)算代碼并返回字符串中最后一個(gè)表達(dá)式或語句的值;如果最后一個(gè)表達(dá)式或語句沒有值,則返回未定義的值。如果計(jì)算的字符串引發(fā)異常,則該異常將從eval()調(diào)用中傳播。

eval()的關(guān)鍵在于它使用調(diào)用它的代碼的變量環(huán)境。也就是說,它查找變量的值,并以與本地代碼相同的方式定義新的變量和函數(shù)。如果函數(shù)定義了局部變量x,然后調(diào)用eval('x'),它將獲得局部變量的值。如果調(diào)用eval('x=1'),則會(huì)更改局部變量的值。如果函數(shù)調(diào)用eval('var y=3'),它將聲明一個(gè)新的局部變量y。另一方面,如果被求值的字符串使用let或const,則聲明的變量或常量將是求值的局部變量,并且不會(huì)在調(diào)用環(huán)境中定義。

類似地,一個(gè)函數(shù)可以使用如下代碼聲明局部函數(shù):

eval("function f() { return x+1; }");

如果從頂層代碼調(diào)用eval(),它當(dāng)然會(huì)對全局變量和全局函數(shù)進(jìn)行操作。

請注意,傳遞給eval()的代碼字符串本身必須具有語法意義:不能使用它將代碼片段粘貼到函數(shù)中。例如,編寫eval('return;')是沒有意義的,因?yàn)閞eturn只在函數(shù)內(nèi)是合法的,而且被求值的字符串使用與調(diào)用函數(shù)相同的變量環(huán)境這一事實(shí)并不能使其成為函數(shù)的一部分。如果字符串作為一個(gè)獨(dú)立的腳本(即使是很短的腳本,比如x=0),那么傳遞給eval()是合法的。否則,eval()將拋出一個(gè)SyntaxError。

4.12.2 全局 eval()

eval()更改局部變量的能力給JavaScript優(yōu)化器帶來了很大的問題。不過,作為一種解決方法,解釋器只需對調(diào)用eval()的函數(shù)進(jìn)行較少的優(yōu)化。但是,如果腳本為eval()定義了別名,然后用另一個(gè)名稱調(diào)用該函數(shù),JavaScript解釋器應(yīng)該怎么做呢?JavaScript規(guī)范聲明,當(dāng)eval()被“eval”以外的任何名稱調(diào)用時(shí),它應(yīng)該像頂級全局代碼一樣計(jì)算字符串。被求值的代碼可以定義新的全局變量或全局函數(shù),也可以設(shè)置全局變量,但它不會(huì)使用或修改調(diào)用函數(shù)的任何局部變量,因此不會(huì)干擾局部優(yōu)化。

“直接調(diào)用”是對eval()函數(shù)的調(diào)用,該函數(shù)的表達(dá)式使用精確的、不限定的名稱“eval”(它開始感覺像是一個(gè)保留字)。對eval()的直接調(diào)用使用調(diào)用上下文的變量環(huán)境。任何其他調(diào)用–間接調(diào)用–使用全局對象作為其變量環(huán)境,不能讀、寫或定義局部變量或函數(shù)。(直接調(diào)用和間接調(diào)用都只能用var定義新變量。在求值字符串中使用let和const會(huì)在求值上下文中創(chuàng)建對應(yīng)的變量和常量,并且不會(huì)更改調(diào)用或全局環(huán)境。)

以下代碼演示:

const geval = eval; // 使用其他名稱定義一個(gè)全局eval let x = "global", y = "global"; // 兩個(gè)全局變量 function f() { // 此函數(shù)執(zhí)行局部evallet x = "local"; //定義局部變量eval("x += 'changed';"); // 直接調(diào)用修改局部變量return x; // 返回已更改的局部變量 } function g() { // 此函數(shù)執(zhí)行全局evallet y = "local"; // 局部變量geval("y += 'changed';"); // 間接調(diào)用修改全局變量return y; // 返回未更改的局部變量 } console.log(f(), x); // 局部變量已更改:打印"localchanged global": console.log(g(), y); // 全局變量已更改:打印"local globalchanged":

請注意,執(zhí)行全局eval的能力不僅僅是為了滿足優(yōu)化器的需要;它實(shí)際上是一個(gè)非常有用的特性,它允許您執(zhí)行代碼字符串,就好像它們是獨(dú)立的頂級腳本一樣。正如本節(jié)開頭所述,真正需要計(jì)算一個(gè)代碼字符串的情況很少。但如果你真的覺得有必要的話,你更愿意做一個(gè)全局eval,而不是一個(gè)局部eval。

4.12.3 嚴(yán)格 eval()

嚴(yán)格模式(見§5.6.3)對eval()函數(shù)的行為,甚至對標(biāo)識符“eval”的使用施加了進(jìn)一步的限制。當(dāng)eval()從嚴(yán)格模式代碼調(diào)用時(shí),或者當(dāng)要求值的代碼字符串本身以“use strict”指令開頭時(shí),eval()使用私有變量環(huán)境執(zhí)行局部eval。這意味著在嚴(yán)格模式下,計(jì)算的代碼可以查詢和設(shè)置局部變量,但不能在局部范圍內(nèi)定義新的變量或函數(shù)。

此外,嚴(yán)格模式通過有效地將“eval”變成一個(gè)保留字,使eval()更像運(yùn)算符。不允許用別名覆蓋eval()函數(shù)。并且不允許聲明名為“eval”的變量、函數(shù)、函數(shù)參數(shù)或catch塊參數(shù)。

4.13 其他運(yùn)算符

JavaScript支持許多其他的操作符,如下節(jié)所述。

4.13.1 條件運(yùn)算符 ( ?: )

條件運(yùn)算符是JavaScript中唯一的三元運(yùn)算符(三個(gè)操作數(shù)),有時(shí)實(shí)際稱為三元運(yùn)算符。這個(gè)運(yùn)算符通常寫作 ?:,盡管在代碼中并不是這樣。因?yàn)檫@個(gè)運(yùn)算符有三個(gè)操作數(shù),所以第一個(gè)操作數(shù)在?之前,第二個(gè)在?和:中間,第三個(gè)在:后面。它是這樣使用的:

x > 0 ? x : -x // x的絕對值

條件運(yùn)算符的操作數(shù)可以是任何類型。計(jì)算第一個(gè)操作數(shù)并將其解釋為布爾值。如果第一個(gè)操作數(shù)的值是真值,則計(jì)算第二個(gè)操作數(shù),并返回其值。否則,如果第一個(gè)操作數(shù)是假值,則計(jì)算第三個(gè)操作數(shù)并返回其值。只計(jì)算第二個(gè)和第三個(gè)操作數(shù)中的一個(gè),而不是同時(shí)計(jì)算這兩個(gè)操作數(shù)。

雖然使用if語句(§5.3.1)可以獲得類似的結(jié)果,但是 ?:運(yùn)算符通常提供方便快捷的方式。下面是一個(gè)典型的用法,它檢查以確保定義了一個(gè)變量(并且有一個(gè)有意義的、真實(shí)的值),如果定義了,就使用它;如果沒有,則提供一個(gè)默認(rèn)值:

greeting = "hello " + (username ? username : "there");

這相當(dāng)于以下if語句,但是更緊湊:

greeting = "hello "; if (username) {greeting += username; } else {greeting += "there"; }

4.13.2 第一定義選擇運(yùn)算符 (??)

運(yùn)算符??計(jì)算結(jié)果是第一個(gè)定義的操作數(shù):如果其左操作數(shù)不為null且不等于undefined,則返回該值。否則,返回右操作數(shù)的值。像&&和| |運(yùn)算符一樣,??也是短路操作:僅當(dāng)?shù)谝粋€(gè)操作數(shù)的計(jì)算結(jié)果為null或undefined時(shí),才計(jì)算第二個(gè)操作數(shù)。如果表達(dá)式a沒有副作用,那么表達(dá)式a ?? b相當(dāng)于:

(a !== null && a !== undefined) ? a : b

當(dāng)您要選擇第一個(gè)定義的操作數(shù)而不是第一個(gè)真值操作數(shù)時(shí),?? 是 | |(§4.10.2)的有效替代。盡管| |名義上是一個(gè)邏輯或運(yùn)算符,但它也慣用地用于選擇第一個(gè)非假值操作數(shù),代碼如下:

//如果maxWidth是真值,那么就使用它。否則, //請?jiān)趐references對象中查找值。如果都不是真值,就使用硬編碼常量500。 let max = maxWidth || preferences.maxWidth || 500;

這種慣用用法的問題是零、空字符串和false都是假值,在某些情況下可能完全有效。在這個(gè)代碼示例中,如果maxWidth為零,則該值將被忽略。但是如果我們把| |運(yùn)算符改為??,我們得到一個(gè)表達(dá)式,其中0是有效值:

//如果maxWidth定義過,那么就使用它。否則, //請?jiān)趐references對象中查找值。如果也沒有定義,就使用硬編碼常量500。 let max = maxWidth ?? preferences.maxWidth ?? 500;

這里有更多的例子說明當(dāng)?shù)谝粋€(gè)操作數(shù)是假值時(shí),??是如何起作用的。如果這個(gè)操作數(shù)是假值,但是定義了,那么 ?? 返回它。只有當(dāng)?shù)谝粋€(gè)操作數(shù)為“空”(即null或undefined)時(shí),此運(yùn)算符才計(jì)算并返回第二個(gè)操作數(shù):

let options = { timeout: 0, title: "", verbose: false, n: null }; options.timeout ?? 1000 // => 0: 在對象中有定義 options.title ?? "Untitled" // => "": 在對象中有定義 options.verbose ?? true // => false: 在對象中有定義 options.quiet ?? false // => false: 在對象中沒有定義 options.n ?? 10 // => 10: 屬性是null

注意,如果我們使用 || 而不是 ??,這里的timeout、title和verbose表達(dá)式將具有不同的值。

這個(gè) ?? 運(yùn)算符與&&和 || 運(yùn)算符類似,但其優(yōu)先級不高于或低于它們。如果你要在表達(dá)式中使用這些運(yùn)算符的話,必須使用圓括號指明先執(zhí)行哪個(gè)運(yùn)算:

(a ?? b) || c // 先 ??, 后 || a ?? (b || c) // 先 || , 后 ?? a ?? b || c // SyntaxError: 括號是必須的

這個(gè) ?? 運(yùn)算符由ES2020定義,到2020年初,所有主流瀏覽器的當(dāng)前版本或beta版本都對其進(jìn)行了新的支持。這個(gè)操作符被正式稱為“nullish coalescing”操作符,但是我避免使用這個(gè)術(shù)語,因?yàn)檫@個(gè)操作符選擇了它的一個(gè)操作數(shù),但是沒有以我能看到的任何方式“合并”它們。

4.13.3 typeof 運(yùn)算符

typeof是一元運(yùn)算符,放在單個(gè)操作數(shù)之前,操作數(shù)可以是任何類型。它的值是指定操作數(shù)類型的字符串。表4-3指定了任何JavaScript值的typeof運(yùn)算符的值。

表4-3 typeof運(yùn)算符返回的值

xtypeof x
undefined“undefined”
null“object”
true 或 false“boolean”
任意數(shù)字 或 NaN“number”
任意 BigInt“bigint”
任意字符串“string”
任意符號“symbol”
任意函數(shù)“function”
任意非函數(shù)對象“object”

可以在如下表達(dá)式中使用typeof運(yùn)算符:

// 如果值是字符串,請用引號括起來,否則,轉(zhuǎn)換 (typeof value === "string") ? "'" + value + "'" : value.toString()

注意,如果操作數(shù)值為null,typeof返回“object”。如果要區(qū)分null和對象,就必須顯式地測試這個(gè)特殊情況的值。

盡管JavaScript函數(shù)是一種對象,但是typeof操作符認(rèn)為函數(shù)之間的差別很大,以至于它們有自己的返回值。

因?yàn)閠ypeof對于除函數(shù)之外的所有對象和數(shù)組值的計(jì)算結(jié)果都是“object”,所以只有將對象與其他基本類型區(qū)分開來才有用。為了區(qū)分一種對象和另一種對象,必須使用其他技術(shù),如instanceof操作符(見§4.9.4)、類屬性(見§14.4.3)或constructor屬性(見§9.2.2和§14.3)。

4.13.4 delete 運(yùn)算符

delete是一元運(yùn)算符,它嘗試刪除指定為其操作數(shù)的對象屬性或數(shù)組元素。與賦值、遞增和遞減運(yùn)算符一樣,delete通常用于屬性刪除的副作用,而不是用于它返回的值。一些例子:

let o = { x: 1, y: 2}; // 從一個(gè)對象開始 delete o.x; // 刪除其屬性之一 "x" in o // => false: 該屬性已不存在 let a = [1,2,3]; // 從數(shù)組開始 delete a[2]; // 刪除數(shù)組最后一個(gè)元素 2 in a // => false: 數(shù)組元素2不再存在 a.length // => 3: 不過,請注意,數(shù)組長度不會(huì)改變

請注意,已刪除的屬性或數(shù)組元素不僅僅設(shè)置為undefined。刪除屬性后,該屬性將不再存在。嘗試讀取不存在的屬性會(huì)返回undefined,但您可以使用in運(yùn)算符測試屬性的實(shí)際存在性(§4.9.3)。刪除數(shù)組元素會(huì)在數(shù)組中留下一個(gè)“洞”,并且不會(huì)更改數(shù)組的長度。得到的數(shù)組是稀疏的(§7.3)。

delete要求其操作數(shù)為左值。如果不是左值,則運(yùn)算符不執(zhí)行任何操作并返回true。否則,delete將嘗試刪除指定的左值。如果delete成功刪除指定的左值,則返回true。然而,并非所有屬性都可以刪除:不可配置屬性(§14.1)不會(huì)被刪除。

在嚴(yán)格模式下,如果delete的操作數(shù)是非限定標(biāo)識符(如變量、函數(shù)或函數(shù)參數(shù)),則delete將引發(fā)SyntaxError:它只在操作數(shù)是屬性訪問表達(dá)式(§4.4)時(shí)有效。嚴(yán)格模式還指定,如果要求刪除任何不可配置(即不可刪除)的屬性,delete將引發(fā)TypeError。在嚴(yán)格模式之外,在這些情況下不會(huì)發(fā)生異常,delete只返回false,表示無法刪除操作數(shù)。

下面是delete運(yùn)算符的一些用法示例:

let o = {x: 1, y: 2}; delete o.x; // 刪除其中一個(gè)對象屬性;返回true。 typeof o.x; // 屬性不存在;返回“undefined”。 delete o.x; // 刪除不存在的屬性;返回true。 delete 1; // 這沒有意義,但它只是返回true。 // 無法刪除變量;返回false或者在嚴(yán)格模式下引發(fā)SyntaxError。 delete o; // 不可刪除屬性:返回false或者在嚴(yán)格模式下引發(fā)TypeError。 delete Object.prototype;

我們將在§6.4中再次看到 delete 操作符。

4.13.5 await 運(yùn)算符

await是在ES2017中引入的,它使JavaScript中的異步編程更加自然。你需要閱讀第13章來理解這個(gè)運(yùn)算符。但是,簡單地說,await期望一個(gè)Promise對象(表示異步計(jì)算)作為它的唯一操作數(shù),它使程序的行為就像是在等待異步計(jì)算完成一樣(但是它不會(huì)實(shí)際阻塞,也不會(huì)阻止其他異步操作同時(shí)進(jìn)行)。await操作符的值是Promise對象的最終返回值。重要的是,await僅在使用async關(guān)鍵字聲明為異步的函數(shù)中才是合法的。同樣,請參閱第13章了解詳細(xì)信息。

4.13.6 void 運(yùn)算符

void是一元運(yùn)算符,出現(xiàn)在其單個(gè)操作數(shù)之前,可以是任何類型。此運(yùn)算符不常見且不常使用;它計(jì)算其操作數(shù),然后丟棄該值并返回undefined。由于操作數(shù)值被丟棄,因此只有當(dāng)操作數(shù)有副作用時(shí),使用void運(yùn)算符才有意義。

void操作符非常晦澀,很難給出一個(gè)實(shí)際的例子來說明它的用法。一種情況是定義一個(gè)不返回任何結(jié)果的函數(shù),但也使用箭頭函數(shù)快捷語法(見§8.1.3),其中函數(shù)體是一個(gè)被計(jì)算并返回的表達(dá)式。如果只考慮表達(dá)式的副作用而不想返回其值,則最簡單的方法是在函數(shù)體周圍使用大括號。但是,在這種情況下,也可以使用void操作符:

let counter = 0; const increment = () => void counter++; increment() // => undefined counter // => 1

4.13.7 逗號運(yùn)算符 (,)

逗號運(yùn)算符是一種二元運(yùn)算符,其操作數(shù)可以是任何類型。它先計(jì)算左操作數(shù),再計(jì)算右操作數(shù),然后返回右操作數(shù)的值。因此,以下一行:

i=0, j=1, k=2;

計(jì)算結(jié)果是2,相當(dāng)于:

i = 0; j = 1; k = 2;

總是對左邊表達(dá)式求值,但它的值被丟棄,這意味著只有在左邊表達(dá)式有副作用時(shí)使用逗號運(yùn)算符才有意義。唯一常用逗號運(yùn)算符的情況是具有多個(gè)循環(huán)變量的for循環(huán)(§5.4.3):

//下面的第一個(gè)逗號是let語句語法的一部分,第二個(gè)逗號是逗號運(yùn)算符: // 它允許我們將2個(gè)表達(dá)式(i++和j--)壓縮到一條語句(for循環(huán))中。 for(let i=0, j=10; i < j; i++,j--) {console.log(i+j); }

4.14 總結(jié)

這一章涵蓋了各種各樣的主題,這里有很多參考資料,在以后繼續(xù)學(xué)習(xí)JavaScript時(shí),您可能需要重新閱讀。但是,需要記住的一些關(guān)鍵點(diǎn)是:

  • 表達(dá)式是JavaScript程序的基本單元。
  • 任何表達(dá)式都可以計(jì)算為JavaScript的一個(gè)值。
  • 表達(dá)式除了產(chǎn)生值外,還可能有副作用(例如變量賦值)。
  • 簡單的表達(dá)式(如字面量、變量引用和屬性訪問)可以與運(yùn)算符組合以生成更大的表達(dá)式。
  • JavaScript定義了用于算術(shù)、比較、布爾邏輯、賦值和位操作的運(yùn)算符,以及一些其他運(yùn)算符,包括三元條件運(yùn)算符。
  • JavaScript+運(yùn)算符用于數(shù)字加法和連接字符串。
  • 邏輯運(yùn)算符&&和| |有特殊的“短路”行為,有時(shí)只計(jì)算它們的一個(gè)參數(shù)。常見的JavaScript習(xí)慣用法要求您理解這些運(yùn)算符的特殊行為。

總結(jié)

以上是生活随笔為你收集整理的《JavaScript权威指南第7版》第4章 表达式和运算符的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。