JavaScript 從入門到放棄 - 3 - 表達式和運算符

表達式和運算符

  • 程序中最簡單的表達式就是,程序中的常量
  • 變量名也是一種簡單的表達式
  • 復雜的表達式是由簡單的表達式組成的
  • 函數調用表達式是由函數對象的表達式和0個或多個參數表達式構成
  • 可以使用運算符來將簡單的表達式來組合成復雜的表達式

原始表達式

“原始表達式”,說簡單點就是最簡單的表達式,并且不再包含其他表達式

js中原始表達式有以下:

  • 常量
  • 直接量
  • 關鍵字
  • 變量

栗子:

//直接量          
1.23 // 數字直接量
"hello" // 字符串直接量
/pattern/ // 正則表達式直接
//保留字   
true
false
null // 返回空
this // 返回“當前對象”
//變量         
i // 返回i的值
sum // 返回sum的值
undefined // undefined是全局變量,和null不同,不是一個關鍵字

對象和數組的初始化表達式

對象和數組的初始化表達式實際上是一個新創建的對象和數組,并不是原始表達式

數組初始化表達式

栗子:

  • [] //空數組
  • [1+2,3+4] // 2個元素數組 [3,7]
  • var matrix = [[1,2],[3,4]] // 數組可嵌套

js對數組初始化表達式進行求值時候,數組表達式中的所有元素表達式也會各自計算一次

數組直接量的元素表達式在逗號之間元素可以省略,空位默認填充undefined。
var array = [1,,3] // 數組包含3個元素。[1,undefined,3]

但是列表結尾處可以留下單個逗號,這時不會創建新的undefined元素

對象初始化表達式

對象初始化表達式與數組初始化表達式非常接近,將[]替換成{},每個子表達式都包含一個屬性名和冒號為前綴

栗子

var p = { x:2, y:3 }; // 帶2個屬性的對象p
var q = {}; // 空對象
q.x = 2; q.y =3; // q的屬性成員與p一致

對象直接量也是允許嵌套的

var rect = {
upLeft: { x:2, y:3},
bottomRight: { x:4, y:1}
};


js求對象初始化表達式時候,對象元素表達式也會各自都計算一次,并且元素表達式不必包含常數值,可以是
任意的js表達式。

對象直接量中屬性的名字可以是字符串而不是標示符
>```
var side = 1;
var square = {
  "upLeft": { x: p.x, y: p.y},
  "bottomRight": {x: p.x+side, y: p.y+side}
};

函數表達式

函數定義表達式定義一個js函數,表達式的值是這個新定義的函數。如下

var square = function (x){
  return x * x;
};

屬性訪問表達式

屬性訪問表達式得到一個對象屬性或一個數組元素的值。主要有.[]兩種形式

栗子:

var o = { x:1, y:{ z:3 } };
var a = [o, 4, [5,6]];
o.x // =>1 表達式o的x屬性
o.y.z // =>3 表達式o.y的z屬性
o["x"] // =>1 表達式o的x屬性
a[1] // =>4 表達式a的索引為1的元素
a[2]["1"] // =>6 表達式a[2] 的索引為1的元素
a[0].x // =>1 表達式a[0]的x屬性

.[]之前的表達式總會首先計算。如果計算出結果為null 或者 undefined,表達式會拋出類型錯誤異常。
如果運算結果不是對象或者數組,js會將其轉換為對象。

如果對象表達式后面跟隨句點和標示符,則會查找由這個標示符所指定的屬性的值,然后作為整個表達式的值返回。

如果表達式后面跟隨一對括號,則會計算方括號里面表達式值并轉換為字符串,然后查找對應屬性的值

如果以上兩種情況,命名屬性并不存在,則整個屬性訪問表達式的值就是undefined

  • .寫法適合要訪問的屬性名是合法標示符,并且需要知道要訪問屬性名字
  • [] 寫法適合要訪問的屬性名不是合法字符,或者訪問的屬性名是需要運算得出的。對于數組則必須使用這種寫法

調用表達式

js調用表達式,是一種調用(執行)函數或方法的語法。如下

func(0) // f是一個函數表達式,0是一個參數表達式
Math.max(x,y,z) // Math.max是一個函數,x,y,z是參數
a.sort() // a.sort是一個函數。沒有參數

對調用表達式進行求值時候,首先計算函數表達式,然后計算參數表達式,得到一組參數值。

如果函數表達式不是一個可以調用的對象,會拋出類型錯誤異常

如果函數表達式使用return語句返回一個值,那么這個值就是整個調用表達式的值,否則表達式的值就是undefined

對象創建表達式

對象創建表達式,顧名思義,就是創建一個對象,并且調用一個函數,初始化新對象的屬性。

new Object()
new Point(2,6)

如果一個對象創建表達式不需要傳入任何參數給構造函數,那么空括號可以省略

new Object
new Date

運算符概述

大多數運算符都是由標點符號表示的,如"+","="。而另外一些運算符則是由關鍵字表示的,比如delete和instanceof。

優先級從高到低,虛線分割開的運算符不同優先級。

運算符 操作 結合性 操作數個數 類型
++ 前/后增量 R 1 lval => num
-- 前/后減量 R 1 lval => num
- 求反 R 1 num => num
+ 轉換為數字 R 1 num => num
~ 按位求反 R 1 int => int
! 邏輯非 R 1 bool => bool
delete 刪除屬性 R 1 lval => bool
typeof 檢測操作數類型 R 1 any => str
void 返回undefined R 1 any => undef
--------------- --------------- --- -- ---------
*,/,% 乘、除、取余 L 2 num,num => num
--------------- --------------- --- -- ---------
+、- 加、減 L 2 num,num => num
+ 字符串連接 L 2 str,str => str
--------------- --------------- --- -- ---------
<< 左移位 L 2 int,int => int
>> 無符號右移位 L 2 int,int => int
>>> 有符號右移位 L 2 int,int => int
--------------- --------------- --- -- ---------
<,<=,>,>= 比較數字順序 L 2 num,num => bool
<,<=,>,>= 比較在字母表順序 L 2 str,str => bool
instanceof 測試對象類 L 2 obj,func => bool
in 測試屬性是否存在 L 2 str,obj => bool
-------------------- --------------- --- -- ---------
== 判斷相等 L 2 any,any =>bool
!= 判斷不等 L 2 any,any => bool
=== 判斷恒等 L 2 any,any => bool
!== 判斷非恒等 L 2 any,any => bool
--------------- --------------- --- -- ---------
& 按位與 L 2 int,int => int
--------------- --------------- --- -- ---------
^ 按位異或 L 2 int,int => int
--------------- --------------- --- -- ---------
按位或 L 2 int,int => int
--------------- --------------- --- -- ---------
&& 邏輯與 L 2 any,any => any
--------------- --------------- --- -- ---------
|| 邏輯或 L 2 any,any => any
--------------- --------------- --- -- ---------
?: 條件運算符 L 3 bool,any,any => any
--------------- --------------- --- -- ---------
= 賦值運算符 R 2 lval,any => any
*=,/=,%=,+=,-=,&= 運算且賦值 R 2 lval,any => any
^=,||=,<<=,>>=,>>>= 運算且賦值 R 2 lval,any => any
--------------- --------------- --- -- ---------
, 忽略第一個操作數,返回第二個操作數 L 2 any,any => any

左值

上表中出現的lval指的是左值,意思是表達式只能出現在賦值運算符的左側

在js中,變量、對象屬性、和數組元素都是左值。

ECMAScript允許內置函數返回左值,但自定義函數不能返回左值

操作數類型和結果類型

js運算符通常會根據需要對操作數進行類型轉換

*希望操作數為數字,但是表達式"3"*"5"卻是合法的,因為js會把操作數轉換為數字

有些操作符對操作數類型有一定程度依賴,比如運算符。可以對數字進行加法運算,也可以對字符串進行連接。

運算符優先級

上表中運算符按照優先級從高到低排序,每個虛線內的一組運算符具有相同優先級。

優先級高的運算符執行總是先于優先級低的運算符
舉個栗子:m = x + y*z;
*運算符比運算符優先級高,優先計算y*z,獲得結果再與x相加。=賦值運算符優先級最低,右側表達式
計算出結果后賦值給m

很多時候為了代碼邏輯清晰,加上一些括號來重寫優先級,來避免一些優先級引起的bug或者執行順序與設計不符
m = (x + y) * z

運算符的結合性

上表中說明了運算符的結合性。

  • L 指從左到右結合,執行時按照從左到右的順序進行
  • R 指從右到左結合,執行時按照從右到左的順序進行

舉個栗子:-運算符從左到右結合,因此w = x - y - z 等價于 w = ((x - y) - z)

運算順序

運算符的優先級和結合性規定了在復雜表達式中的運算順序,但是沒有規定子表達式的計算過程中的運算順序

js中,總是嚴格按照從左到右計算子表達式。例如w=x+y*z,首先計算w,然后計算x,y,z的值,然后y的值和z的值相承
之后,再加上x的值,最后將其結果賦值給w。給表達式加括號會改變乘法加法和賦值運算的順序,但是子表達式的計算
順序仍然是從左至右的順序

只有一種情況例外,當任何一個表達式具有副作用而影響其他表達式時候,求值順序才會有所不同。
例如,表達式中x的一個變量自增1,這個變量在z中使用,那么實際上是先計算了x的值再計算z的值,這一點一定要注意

下面這個栗子:

a = 1;
b = (a++) + a;

如果按照前面那種不考慮副作用時的順序是 1) 計算b, 2)計算a++為c, 3)計算a,4)計算c+a, 5)將c+a結果賦值給b

按照++的影響,1) 計算b, 2)a++結果仍然為1,c=1,隨即a立即自增1, 3)計算a,a已經是2,4)計算c+a=3,5)將c+a結果賦值給b,所以b=3

切記,a增1的操作是在表達式計算中就已經執行了,不是在整個表達式計算完成之后執行的

算術表達式

基本算數運算符包括+ - * / %

"+"運算符

  • 對2個數字進行加法操作
  • 字符串連接操作

針對不同操作數,+運算符行為表現有些不同

  • 一個操作數是對象,對象會遵循對象到原始值的轉換規則轉換為原始值
  • 日期對象:toString()執行轉換
  • 其他對象通過valueOf()轉換,如果valueOf()不可用,會通過toString()方法轉換
  • 在進行對象到原始值的轉換后,如果其中一個操作數是字符串,另一個操作數也會轉換成字符串,然后連接
  • 否則,兩個操作數都轉換成數字或者NaN,然后進行加法操作

下面是一些栗子:

1 + 2 // =>3
"1" + 2 // => "12"
"12" + "3" // => "123"
1 + {} // "1[object object]",對象轉換為字符串
true + true // 2 ,bool轉換為數字做加法

2 + null // =>2,null轉換為0
2 + undefined // => NaN, undefined轉換為NaN后做加法

最后,還需要考慮,加法的結合性對運算順序的影響

1 + 2 + "hello" // "3hello"
1 + (2 + "hello") // "12hello"

一元算術運算符

一元運算符作用于一個單獨操作數,產生一個新值

js中一元運算符優先級很高,并且都是右結合

+/-,既是一元運算符,也是二元運算符

  • 一元加法(+)

操作數轉換為數字(或者NaN),并且返回這個轉換后的數字。如果已經是數字,直接返回

  • 一元減法(-)

操作數轉換為數字(或者NaN),并且返回這個轉換后的數字,然后改變運算結果符號

  • 遞增(++)

前增量 ++a,先進行增量運算并且返回運算結果
后增量 a++,先進行增量計算,返回未做增量運算的值

var i=1, j=i++; // i=2,j=1
var i=1, j=++i; // i=2,j=2


- 遞減(`--`)
> 前減量 `--a`,先進行減量運算并且返回運算結果
> 后減量 `a--`,先進行減量計算,返回未做減量運算的值
> ```
var i=1, j=i--; // i=0,j=1
var i=1, j=--i; // i=0,j=0

位運算符

  • & 按位與

0x1234 & 0x00ff = 0x0034

  • | 按位或

0x1234 | 0x00ff = 0x12ff

  • ^ 按位異或

0xff00 ^ 0xf0f0 = 0x0ff0

  • ~ 按位非

~0x0f = 0xfffffff0

  • << 左移

7 << 2 = 28,左移一位相當于第一個操作數乘以2
移動位數 0~31

  • >> 帶符號右移

帶符號右移時候填補在左邊的位由原來的數的符號決定,以便保持和原操作數一致
移動位數 0~31
7 >> 1 = 3
-7 >> 1 = -4

  • >>> 無符號右移

無符號右移時候填補在左邊的位直接填補0,與原操作數無關
移動位數 0~31
-1 >> 4 = 1
-1 >>> 4 = 0x0fffffff

關系表達式

主要包括相等和不相等運算符、比較運算符、in、instanceof

相等和不相等運算符

js定義了4個符號==,===,!=,!==

  • ==:相等
  • ===: 恒等
  • !=: 不相等
  • !==: 不恒等

嚴格相等運算符===首先計算其操作數的值,然后比較這兩個值,沒有類型轉換

  • 如果兩個值類型不相同,則它們不相等
  • 如果兩個值都是null或者都是undefined,則它們不相等
  • 如果兩個值都是布爾值true或者false,,則它們相等
  • 如果其中一個值是NaN,或者2個值都是NaN,則它們不相等。NaN和其他任何值都是不相等的,包括自身。通過X!==X來判斷x是否為NaN,只有x為NaN時候,表達式才為true
  • 如果兩個值為數字且數值相等,則他們相等。如果一個值為0,另一個為-0,同樣相等
  • 如果兩個值為字符串,且所含對應位上16位數完全相等,則他們相當。如果它們長度或內容不同,則它們不相等。
  • 兩個字符串可能含義完全一樣且顯示字符一樣,但具有不同編碼的16位值。js不會對Unicode進行標準化轉換,像這樣字符串通過"==="和"=="運算符比較結果也是不相等
  • 如果兩個引用值指向同一個對象、數組或者函數,則相等。如果指向不同對象,則它們不相等,盡管兩個對象完全一樣的屬性。

相等運算符==和恒等運算符相似,但相等運算符并不嚴格。如果兩個操作數不是同一類型,那么相等的運算符會進行一些類型轉換,然后進行比較

  • 如果兩個操作數的類型相同,則和上文所屬的嚴格相等的比較規則一樣。如果嚴格相等,則比較結果相等,如果不嚴格相等,則它們不相等
  • 如果兩個操作數類型不同,==相等操作符也可能認為他們相等。檢測相等會遵守以下規則和類型轉換
  • 如果一個值是null,另一個是undefined,則他們相等
  • 如果一個值是數字,另一個是字符串,先將字符串轉換成數字,然后使用轉換后的值,進行比較
  • 如果其中一個值是true,則將其轉換為1再進行比較。如果其中一個值是false,則將其轉化為0,在進行比較
  • 如果一個值是對象,另一個值是數字或字符串,則會使用之前提到的對象到數字或字符串的轉換規則將對象轉換為原始值,然后進行比較。
  • 其他的類型之間的比較均不相等

舉個栗子:"1" == true
這個表達式結果是true,表明不同類型之間的值比較結果相等。布爾值首先轉換為數字1,然后字符串1也轉換成數字1,因為兩個數字相等,所以結果為true

比較運算符

比較運算符有4個

  • < 小于
  • > 大于
  • <= 小于等于
  • >= 大于等于

比較運算符的操作數可以是任何類型,然后只有字符串和數字才能真正的執行比較操作,不是這兩種類型的都將進行類型轉換。
類型轉換規則:

  • 如果操作數為對象,這個對象將按照對象到原始值的轉換(具體可以看上篇)
  • 在對象轉換到原始值后,如果兩個操作數都是字符串,那么將按照字母表順序進行比較(字母表指的unicode 16位字符的索引順序)
  • 對象轉換為原始值后,如果一個操作數不是字符串,那么兩個操作數轉換為數字之后進行比較。
  • 0和-0是相等的
  • Infinity比其他任何數字都大(除了自身)
  • -Infinity比其他數字都小(除了自身)
  • 如果一個操作數轉換成數字之后是NaN,那么比較操作符總是返回false

在上面規則中,字符串比較需要注意:

  • 字符串比較是區分大小寫的,所有的大寫ascii字符都是小于小寫的ascii字符
  • 對于數字和字符串比較,只有兩個操作數都是字符串時,才會進行字符串比較

in運算符

in運算符的左邊總是希望是一個字符串,右邊操作數總是希望是一個對象,如果右邊對象擁有左操作值的屬性名,會返回true

對象,會傾向查找屬性名

var point = { x:1, y:1};
"x" in point // => true, 對象擁有名為“x”的屬性
"z" in point // => false, 對象不存在名為"z"的屬性
"toString" in point // => true,對象繼承了默認的toString()方法

數組,會傾向查找索引

var data = [1,2,3]
"0" in data // true,數組包含索引0的元素
1 in data // true,數組包含索引1的元素
3 in data // false 數組不包含索引3的元素

instanceof 運算符

instanceof運算符希望左操作數是一個對象,右操作數標識對象的類,如果左側的對象是右側類的實例,則返回true,否則返回false

var d = new Date();
d instanceof Date;  // true,d是由Date()創建的
d instanceof Object; //  true,所有對象都是Object實例
d instanceof Number; // false, d不是Number的實例

邏輯表達式

邏輯運算符是進行布爾運算使用的,主要有

  • && 邏輯與
  • || 邏輯或
  • ! 邏輯非

邏輯與(&&)

當操作數都是布爾值時,&&對兩個值進行布爾與操作,第一個與第二個操作數都是true時,才返回true,其中一個是false,就會返回false

當操作數不都是布爾值時,&&不一定會返回布爾值。

  • 邏輯與運算符,首先計算左側的值,如果計算結果是假植,則整個表達式都是假植,因此會簡單的返回左側操作數的值
  • 如果左側值是真值,那么整個表達式結果依賴于右側的值,因此,&&運算符符會計算右側操作數的值,并且將其返回作為整個表達式的計算結果
var o ={ x:1 };
var p = null;
o && o.x; // =>1 ,o對象是真值,返回o.x
p && p.x; // => null ,p是假值,將其返回,不會計算p.x

上面那種行為,被稱為 短路,這一特性非常有用,可以選擇性執行代碼。 例如:
if ( a == b ) stop(); 等價于 ( a == b ) && stop();

邏輯或(||)

當操作數都是布爾值時,||對兩個操作數作布爾或運算,兩個操作數有一個為真,返回true,兩個操作數都是假,才會返回false

當操作數不都是布爾值,||不一定返回布爾值

  • 邏輯或運算符,首先計算左側的值,如果計算結果是真值,則整個表達式都是真值,因此會返回這個真值
  • 否則再計算第二個操作數的值,再返回這個表達式的計算結果

||同樣是非常有用,比如從一組備選表達式中選出第一個真值表達式。這種做法經常在函數體內,給參數提供默認值

var max = max_width || preference.max_width || 500;

邏輯非(!)

! 運算符是一元運算符,放在一個單獨的操作數前,對操作數布爾值進行求反

!運算符首先將操作數轉換為布爾值,再進行求反,最終只會返回true或者false

作為一元運算符,優先級非常高,并且和操作數密切綁定。
德摩根公式:

!(p && q) === !p || !q
!(p || q) === !p && !q

賦值表達式

js 使用=運算符給變量或者屬性進行賦值

i = 0;
0.x = 1;

除了常規的賦值運算符,還有一些帶賦值操作的運算符+=,-=,*=,&=等等
只有+=可以用于數字或字符串連接,其他都偏向于數值操作

a = 10;
b = '1';
a += 10; // => 10 + 10 =20
b += 10; // => '1'+10 = "110"
a -= 10; // => 20 - 10 = 10
b -= 10; // => 110 - 10 = 10

表達式計算

js可以通過eval()來動態判斷源代碼中的字符串,并且執行

eval()只有一個參數,如果傳入的參數不是字符串,直接返回這個參數。如果參數是字符串,則會把字符串當成代碼進行編譯。
如果編譯失敗,則返回一個語法錯誤異常。如果編譯成功,則會執行這段代碼,并且返回字符串最后一個表達式或者語句的值。
如果最后一個表達式或語句沒有值,則最終返回undefined

eval()使用來調用它的變量作用域環境,也就是說查找變量會和局部作用域代碼完全一樣。

如果將eval()重命名為其他方式來調用,則使用全局對象作為上下文作用域,并且無法讀、寫、定義局部變量

var geval = eval; // geval 是調用全局eval
var x = "global";
var y = "global";
function f(){
  var x = "local";  // 定義局部變量 x,局部作用域x = “local”
  eval("x+='changed';"); //  直接eval更改局部變量x的值
  return x; // 返回更改后的x值
}
function g(){
  var y = "local";  // 定義局部變量 y,局部作用域y = “local”
  geval("y+='changed';"); // 間接調用改變全局變量的值
  return y; // 返回為更改的局部變量
}
console.log(f(),x); // 更改來局部變量,輸出"localchanged global"
console.log(g(),y); // 更改全局變量,輸出“local globalchanged”

其他運算符

條件運算符(?:)

條件運算符是唯一一個三元運算符
(---1---) ? (---2---) : (---3---)

第一個操作數當成布爾值,如果是真值,那么計算第二個操作數,并返回結果。否則如果第一個操作數是假植,那么計算第三個數,返回計算結果

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

typeof運算符

x typeof x
undefined "undefined"
null "object"
true or false "boolean"
任意數字或NaN "number"
任意字符串 "string"
任意函數 "function"
任意內置對象 "object"
任意宿主對象 由編譯器實現的字符串,但不是以上出現的字符串

這是一個一元運算符,返回表示操作數類型的一個字符串

x typeof x
undefined "undefined"
null "object"
true or false "boolean"
任意數字或NaN "number"
任意字符串 "string"
任意函數 "function"
任意內置對象 "object"
任意宿主對象 由編譯器實現的字符串,但不是以上出現的字符串

delete運算符

delete運算符,用來刪除對象屬性或者數組元素

var o = { x:1, y:2};
delete o.x; // 刪除一個屬性
"x" in o; // false,o對象中不存在屬性x

var a = [1,23,4];
delete a[2]; //刪除第三個元素
2 in a ; // false, 索引2的元素在數組中已經不存在

delete刪除成功會返回true。然后不是所有屬性都可刪除的

  • 一些內置核心和客戶端屬性是不能刪除的
  • 使用var語句生聲明的變量不能刪除
  • 通過function定義的函數和函數參數不能刪除。
var 0 = { x:1, y:2};
delete o.x; // true, 刪除對象屬性成功
typeof o.x; // undefined , 屬性不存在
delete o.x; // true, 刪除不存在的屬性,返回true
delete o; // false ,不能刪除var聲明變量

delete 1; // true, 參數不是一個左值,返回true
this.x = 1; // 定義全局對象的一個屬性
delete x; // 試圖刪除全局變量的屬性,非嚴格模式下,返回true
x; // 運行時錯誤,沒有定義x

void 運算符

void 是一元運算符,操作數照常計算,但是忽略計算結果并且返回undefined

這個操作符經常用作客戶端URL-javascript:URL,通過使用void則讓瀏覽器不必顯示這個表達式計算結果

<a href="javascript:void window.open();">打開一個新窗口</a>

逗號運算符

逗號運算符,首先計算左操作數,然后計算右操作數,最后返回右操作數的值。

總會計算左側的表達式,但計算結果忽略掉,也就是說,只有左側表達式有副作用時,,才會使用逗號表達式讓代碼更通順。

經常在for循環中使用

for(var i=0,j=10;i<j;i++,j--){
  console.log(i+j);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容