表達式是javascript中的一個短語,javascript解釋器會將其計算出一個結果。程序中常用量是最簡單的一類表達式就是變量。變量名也是一種簡單的表達式,它的值就是賦值給變量的值。
復雜的表達式是由簡單的表達式組成的。比如數組訪問表達式是由一個表示數組的表達式,方括號、一個整數表達式構成。它們所組成新的表達式運算結果是該數組特定位置的元素值。同樣的函
數調用表達式由一個表示函數對象的表達式和0個多個參數表達式構成。將簡單表達式組成復雜表達式最常用的方法就是運算符。
本章(本文)將講解所有javascript運算符。同時也講解不涉及運算符的表達式(比如訪問數組元素和函數調用),其語法和編程風格和c語言都很相似。
1.元素表達式
最簡單的表達式是“原始表達式”,原始表達式是表達式的最小的單位--它們不包含其他表達式。javascript中的原始表達式包含常量或直接量。關鍵字和變量。
直接量是直接在程序中出現的常數值。它們看起來像:
代碼如下:
1.23 //數字直接量
"hello" //字符串直接量
/pattern/ //正則表達式直接量
javascript中的一些保留字構成了原始表達式
代碼如下:
true //布爾值:真
false //假
null //返回一個值:空
this //返回"當前"對象
通過第三章的學習,和其它關鍵字不同,this並不是一個常量,他在程序的不同地方返回的值也不相同。this關鍵字經常在面向對象編程中出現。this返回方格方法的對象。
最後,第三種原始表達式是變量
代碼如下:
i //返回變量i的值
sum //返回sum的值
undefined //是全局變量,和null不同,它不是一個關鍵字
2.對象和數組的初始化表達式。
對象和數組初始化實際上是新創建的對象和數組,這些初始化的表達式有時候叫做“對象直接量”和“數組直接量”。然而和布爾直接量不同,他們不是原始表達式,因為他們所包含的成員或者元素都子表達式。
數組的初始化表達式語法非常簡單,我們以下開始
數組的初始化表達式是通過一對方括號和其內由逗號隔開的列表構成的,初始化結果是一個新創建的數組。數組的元素是逗號分隔表達式的值。
[] //一個空數組;[]內留空即表示該數組沒有任何元素
[1+2,3+4] //有兩個元素的數組,第一個3,第二個是7
數組初始化表達式中的元素初始化表達式可以是數組初始化表達式。也就是說表達式是可以嵌套的
代碼如下:
var mat = [[1,2,3],[4,5,6],[7,8,9]];
數組直接量中列表之間的元素可以省略,空位就會填充undefined.例如下面:
代碼如下:
var a=[1,,,,5]
其中4個元素是undefined.數組直接量的結尾處留下逗號,這時不會創建一個新的值為undefined的元素。
對象初始化表達式和數組初始化表達式非常相似,只是方括號被花括號代替。並每個字表達式包含一個屬性名和非冒號作為前綴。
代碼如下:
var p = {x: 2.1,y: -3} //一個擁有兩個屬性成員的對象
var q = {}; //空對象
q.x=2.1;q.y=-3; //q的屬性成員和p的一樣
對象直接量也可以嵌套,比如
代碼如下:
var anh = {left:{x:2,y:3},
right:{x:4,y:5}}
javascript在計算對象初始化表達式的值時候,對象表達式都會各自計算一次,並且他們不必包含常數值:它們可以是任意javascript表達式。同樣,對象直接量中屬性的名稱可以是字符串而不是標識符。(在在那行只能使用保留字或一些非法標識符作為屬性名的時候非常有用)
代碼如下:
var side = 1;
var square = {"left":{x:p.x,y:p.y},
'right':{x:p.x+side,y:p.y+side}}
第6 7章還會再次討論對象和數組的初始化表達式。
3.函數表達式
函數定義表達式定義一個javascript函數。表達式的值是這個新定義的函數。從某種意義上將,函數定義表達式可以成為函數直接量,函數表達式可稱為“函數直接量”,畢竟對象初始化表達式也稱為“對象直接量”。一個典型的函數定義表達式包含關鍵字function,其後是一對圓括號,括號以內是逗號分隔的列表,列表包含0或多個標識符(參數名)。然後跟隨花括號包裹的javascript代碼段(函數體).
var square = function(x){ return x*x};
函數定義表達式同樣可以包含函數的名字。函數也可以通過函數語句來定義,而不是函數表達式。更多內容會在第八章描述。
4.屬性訪問表達式
屬性訪問表達式運算得到一個對象或者一個數組元素的值。javascript為屬性訪問定義了兩種方法。
代碼如下:
expression . indentifier
expression [expression]
第一種寫法是一個表達式後跟隨一個句點和標識符。表達式指定對象,標識符則指定要訪問的屬性明川。
第二章寫法是使用方括號,方括號內是一個表達式(這種方法適用於對象和數組)。第二個表達式指定要訪問的屬性的明川或者代表要訪問數組元素的索引。這裡有一些具體的例子
代碼如下:
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,表達式會拋出類型錯誤異常,因為這兩個值都不能包含任意屬性。如果運算結果不是對象或數組,javascript會將其轉換為對象(3章6節內容)
雖然.identifier的寫法更加簡單,但需要注意的是,這種方式只適用於要訪問的屬性名稱是合法的標識符。並且需要知道要訪問的屬性名字。如果屬性名稱是一個保留字或者包含空格和標點符號,是一個數字(對於數組來說),則必須使用方括號的寫法。當屬性名是通過運算符得出的值而不是固定值的時候,這時候必須使用方括號的寫法。(6章2節1小節)
5.調運表達式
javascript中的調用表達式(invocation expression)是一種調用(或者執行)函數或方法的語法表示。它以一個函數表達式開始,這個函數表達式指代了要調用的函數。函數表達式後跟隨一對圓括號,括號內是一個以逗號隔開的參數列表。參數可以有0個也可以有多個。
f(0) //f是一個函數表達式:0是一個參數表達式。
Math.max(x,y,z) //Math.max是一個函數;x,y和z是參數
a.sort() //a.sort()是一個函數,它沒有參數。
當調用表達式進行求值的時候,首先計算函數表達式,然後計算參數表達式,得到一組參數值。如果函數表達式的值不是一個可調用的對象,則拋出一個類型錯誤異常.然後參數的值依次被賦值給形參,這些形參是定義函數時定義的。接下來執行函數體。如果函數使用return語句給出一個返回值,那麼這個返回值就是整個調用表達式的值。否則,調用表達式的值就是undefined.函數調用--包括形參表達式的個數和函數定義中實參的個數不匹配的時候運行的情況--的細節將會在第八章詳細說明。
任何一個調用表達式都包含一對圓括號和左圓括號之前的表達式,如果這個表達式是一個屬性訪問表達式,那麼這個調用叫做“方法調用”(method invication)。在方法調用中執行函數體的時候,作為屬性訪問主體的對象和數組便是其調用方法內this的指向。這種特性使得在面向對象編程的范例中,函數(其OO名稱為“方法”)可調用其宿主對象(第9章會有更多相關內容)。
6.對象創建表達式
對象創建表達式(object creation expression)創建一個對象並調用一個函數(構造函數)來初始化對象的屬性。對象創建表達式和函數調用表達式非常類似,只是對象創建表達式之前多了一個關鍵字new:
new Object()
new Point(2,3)
如果對象創建表達式不需要傳入任何參數給構造函數的話,那麼這對圓括號是可以省略掉的,更多構造函數的細節將在9章說明
new Object
new Point
7.運算符概述
javascript中的運算符用於算表表達式, 比較表達式, 邏輯表達式 ,賦值表達式等
需要注意的是大多運算符都是標點符號來表示的,比如delete和instanceof.無論是關鍵字運算符還是符號運算符,所表示的運算符一樣都是正規運算符,他們的語法都非常言簡意赅。
下標運算符的優先級來排序的,前邊的運算符優先級高於後邊的運算符優先級。被水平華豐隔開的運算符具有不同的優先級。
A表示運算符的結合性。
L從左至右或者R(從右至左)
標題N的列表表示操作數的個數。
類型表示期望的操作數的類型,以及運算符的結果類型(在"→"符號之後)
運算符 操作 A N 類型 ++ 前/後增量 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 ?: 條件運算符 R 3 bool,any,any→any = 變量賦值或對象屬性賦值 R 2 lval,any→any*= /= %= += -= &=
^= |= <<= >>= >>>=
運算且賦值 R 2 lval,any→any ,忽略第一個操作數,
返回第二個操作數。
L 2 any,any→anyi.操作數的個數
運算符可以通過操作數的個數進行分類。
javascript中的大多數運算符是二元運算符,將兩個表達式合並成一個稍微復雜的表達式。
javascript也支持一些一元運算符,它們將一個表達式轉換為另一個稍微復雜的表達式。表達式-x中的"-"運算符就是一個一元運算符。是將x求負值。
javascript支持一個三元運算符:條件判斷運算符“?:”,它將三個表達式合並為一個表達式
ii.操作數類型和結果類型
一些運算符可以用於任何數據類型,但仍然希望它們操作指定的類型的數據。
iii.左值
在表中的賦值運算符和其它少數運算符期望它們的操作數lval類型,左值是一個古老的術語。它是指“表達式只能出現在賦值運算符的左側”。javascript中,變量、對象屬性和數組元素均是左值。ECMAScript規范允許范圍內置函數返回一個左值,但定義的函數則不能返回左值。
iiii.運算符的優先級
在上表中,所示的運算符是按照優先級從高到低排序的,每個水平分隔線內一組運算符有相同的優先級。運算符優先級優先控制著運算符的執行順序。運算符高的(表頂)的執行總是高於優先級低的(表格的底部)運算符。
看如下表達式
w=x+y*z;
乘法運算符“*”比加法“+”具有更高的優先級,所以乘法先執行。然後,由於賦值運算符“=”具有最低優先級。因此賦值操作是在右側的表達式計算出結果後進行的。
運算符的優先級可以使用園括號來從寫。以上表達式可以這樣寫。
w = (x + y) * z;
需要注意的是,屬性訪問表達式和調用表達式的優先級要比表中的所有運算符都要高。
typeof my.Function[x](y)
盡管typeof是優先級最高的運算符之一,但typeof也是在兩次屬性訪問和函數調用後執行的。
事實上,如果你真的不確定你所使用的運算符優先級,最簡單的方法就是使用園括號來強行指定運算次序。有些重要的規則則要熟記:乘法和除法高於加減法,賦值運算的優先級非常低,通常是最後執行的。
iiiiii.運算符的結合性
在本節表中,標題為A的列說明了運算符的結核性。L指從左至右結合,R指從右至左結合。結核性指定了在多個具有同樣優先級的運算符表達式中的運算順序。
例如,減法運算按照由左到右的執行結合性。
代碼如下:
w = x - y - z
和這段代碼一樣:
代碼如下:
w = ((x - y) - z)
反過來講,下面這個表達式:
代碼如下:
x = ~-y;
w = x = y = z;
q=a?b:c?d:e?f:g;
和這段代碼一模一樣
代碼如下:
x=~(-y);
w=(x=(y=z));
q=a?b:(c?d:(e?f:g))
因為一元操作符、賦值和三元條件運算符都具有從右至左的結合性。
iiiiiii.運算順序
運算符的優先級和結合性規定了它們在賦值的運算式中的運算順序,但並沒有規定字表達式的計算過程中的運算順序。javascript總是嚴格按照從左至右的順序計算表達式,例如:
在表達式 w=x+y*z 中,將首先計算表達式w,然後計算x、y和z,然後,y的值和z相乘,在加上x的值。最後將其表達式w所指代的變量或屬性。給表達式添加園括號將會改變乘法,加法和賦值運算的關系。但從左至右的順序是不會改變的。
8.算術表達式
本節涵蓋了那些算術計算的運算符、以及對操作數的算術操作。乘法、除法和減法運算符非常簡單。加法運算單獨一節,因為加法運算符可以操作字符串的連接,並且其類型轉換有些特殊。
基本的算術運算符是*、/、%、+、-。除了+加法,其它的運算符特別簡單,只是在必要的時候操作符轉化為數字而已,然後求積、商、余(模)和差。所有那些無法轉換為數字的操作都將轉換為NaN值。如果操作數(或者轉換結果)是NaN值,算術運算結果也是NaN
運算符“/”用第二個操作數來除以第一個操作數,如果你使用過那些區分整數型和浮點數型的編程語言。那麼用一個整數除以一個整數時,則希望得到的結果也是整數。在javascript中所有的數字都是浮點數型的,除法運算的結果也是浮點型。比如5/2結果是2.5,而不是2。除數為0的運算結果為正無窮大或負無窮大。而0/0的結果是NaN。所有這些運算均不會報錯。
運算符“%”計算的是第一個操作數對第二個操作數的模,換句話說,就是第一個操作數除以第二個操作鼠的余數。結果的符號和第一個操作鼠(被除數)符號保持一致。例如5%2的結果為1,-5%2為-1。
求余運算符的操作數通常都是整數,但也適用於浮點數。6.5%2.1結果是0.2。(0.19999999999999973)
i.“+”運算符
二元加法運算符“+”可以對兩個數字做加法,也可以做字符串連接操作:
代碼如下:
1+2 //=> 3
"hello" + "" + "there" // =>"hello there"
"1"+"2" //=>"12"
當兩個操作數都是數字或都是字符串的時候,計算結果是顯而易見的。然而對於其他情況來說,則要進行一些必要的類型轉換。並且運算符的行為依賴於類型的轉換的結果。從技術上來講,加法操作符的行為表現為:
如果一個操作數是對象,則對象會遵循對象到原始值的轉換規則為原始類值(參照3章8節3小節)。日期對對象toString()方法執行轉換,其他對象則通過valueOf()方法執行轉換(如果valueOf()方法返回一個原始值的話)。由於多數對象都不具備可用的valueOf()方法,因此他們會通過toString()方法來執行抓換
在進行了對象到原始值的轉換後,如果其中一個操作鼠是字符串的話,另一個操作數也會轉換為字符串。然後進行字符串連接。
否則,兩個操作數都將轉換為數字(或者NaN),然後進行加法操作。
這裡有一些例子
代碼如下:
1 + 2 //=>3 加法
"1" + "2" //=>"12" 字符串連接
"1" + 2 //=>"12"數字轉換為字符串後進行字符串連接
1 + {} //=>"1[object object]":對象轉換為字符串後進行字符串連接
true + true //=>2 布爾值轉換為數字後做加法
2 + null //=>2 null轉換為0後做加法
2 + undefined //=>NaN undefined轉換為NaN做加法
最後,特別要注意的是。當加號運算符合字符串一起使用時,要考慮加法對運算順序的影響。也就是說,運算結果是依賴於運算符的運算順序的,比如
代碼如下:
1 + 2 + "bmice" //=> "3 bmice"
1 + (2 + "bmice") => "12bmice"
ii.一元運算符
一元運算符作用於一個單獨的操作數。並產生一個新值。在javascript中,一元運算符具有很高的優先級,而且都是右結合。本節講述一元運算符(+,-,++和--),必要時,他們將操作轉換為數字。需要注意的的是+ -是一元運算符,也是二元運算符、
一元加法+
一元加法運算符把操作數數轉換為數字(或者NaN),並且返回這個轉換後的數字。如果操作數本身就是數字,則直接返回這個數字。
一元減法-
當-號做一元運算符時,它會根據需要把操作數轉換為數字,然後改變運算結果的符號、
遞增++
遞增“++”運算符對其操作數進行增量(+1)的操作,操作數一個左值(變量、數組元素或者對象屬性)。運算符將操作數轉換為數字。然後給數字加1、並將加1後的數值重新賦值給變量、數組元素或者對象屬性。
遞增++ 運算返回值依賴它對操作數的位置。
當操作符在操作數數之前,稱為“前增量”(pre-increment)運算符,它對操作數進行增量計算,並返回計算後的值。
當操作符在操作數之後,稱為"後增量"(post-increment)運算符,它對操作數進行增量計算,但返回為做增量計算的(unincremented)值。如
var i = 1,j = ++i //i和j的值都是2
var i = 1,j = i++; //i是2,j是1
需要注意的是,便打算++x並總和x=x+1完全一樣,“++”運算符從不進行字符串連接操作,它總會將操作數轉換為數字並增1.如果x是字符串“1”,++x的結果就是數字2,而x+1是字符串"11"
遞減和遞增的操作方式是同樣的,它把操作數轉換為數組,然後減1.
iii.位運算符
位運算符可以對數字表示的二進制數據進行更低層級的按位運算。盡管它們不是傳統的純數學運算,但這裡也歸類為算術運算符,因為他們作用於數值類型的操作並返回數字。這些運算符在javascript不常見。(此處不描述,詳情自行百度~-~)
9.關系表達式
本節講述javascript的關系運算符,關系運算符用於測試兩個值之間中的關系(相等、小於或“是...的屬性”),根據關系是否存在而返回true和false.關系表達式總是返回一個布爾值,通常在if while或者for語句(第五章)中使用關系表達式,以控制程序的執行流程。
接下來幾節會講述相等和不等運算、比較運算符和javascript中其它兩個關系符in和instanceof
i相等和不等運算符
“==”和"==="運算符用於比較兩個值是否相等,兩個運算符允許任意類型的操作符。如果相等則返回true,否則返回false.“===”也稱為嚴格相等運算符(有時稱為恆等運算符),它用於檢測兩個操作數是否嚴格相等。“==”運算符稱作相等運算符,它用來檢測兩個操作數是否相等,這裡的相等定義寬松,可以允許進行類型轉換。
javascript支持“=”,“==”,“===”運算符,你應當理解(賦值,相等,恆等)運算符之間的區別。並在編程中小心使用。為了減少混淆,應該把“=”稱作“得到或賦值”,把“==”稱作“相等”,把“===”稱作“嚴格相等”。
“!=”和“!==”運算符規則是“==”,“===”運算符的求反,“!”是布爾非運算符,我們將“!=”,“!==”稱為不相等,不嚴格相等
javascript對象的比較是引用的比較,而不是值的比較。對象和本身是相等的,但和人和對象都不相等。如果兩個對象具有相同數量的屬性,相同的屬性名和值,它們依然是不相等的。相應位置的數組元素是相等的兩個數組也是不相等的。
嚴格相等運算符"==="首先計算操作數的值,然後比較這兩個值,比較過程沒有任何類型轉換。
如果兩個值類型不想同,則它們不相等
如果兩個值都是null或者undefined,則它們不相等
如果兩個值都是布爾值true或者false, 則它們相等
如果其中一個值是NaN,或者兩個值都是NaN ,則它們不相等,NaN和其它值都是不相等的,包括它本身。
如果兩個值為數字且相等,則它們相等。如果一個值為0,令一個值為-0,則它們同樣相等。
如果兩個值為字符串,並且所含對應位上的16位數(參照3章2節)完全相等,則它們相等。如果他們的長度或內容不同,則不相等。兩個字符串可能函數完全一樣並且所顯示出的字符也一樣,但具有不用編碼的16位值,javascript並不對Unicode進行標准轉換,因此這樣的字符串通過"==="和"=="運算符的比較結果也不相等。第三部分的String.localeCompare()提供了另外一種比較字符串的方法。
如果兩個引用值指向同一個對象,數組或函數,則它們是相等的。如果指向不同的對象,則它們是不等的,盡管兩個對象有完全相同的屬性。
相等運算符"=="和恆等運算符相似,但相等運算符比較並不嚴格。如果兩個數不是同一類型,那麼相等運算符會嘗試進行一些類型轉換,然後進行比較。
如果兩個操作類型相同,則和上文相等運算符的比較規則一樣。如果嚴格相等,那麼比較結果相等。如果他們不嚴格相等,則比較結果不相等。
如果兩個操作類型不同,“==”相等操作符也會認為它們相等。檢測相等會遵循如下的規則和類型轉換:
如果一個類型是null,令一個是undefined,則它們相等
如果一個值是數字,另一個是字符串,先將字符串轉換為數字,然後使用轉換後的值進行比較。
如果一個值是true,則將其轉換為1再進行比較,如果一個值是false,則轉換為0比較。
如果一個值是對象,另一個值是數字或字符串,則使用3章8節3小節的方法的轉換規則將對象轉換為原始值,然後進行比較。對象通過toString()方法或者valueOf()方法轉換為原始值。javascript語言核心的內置類首先嘗試使用valueOf()再嘗試使用toString(),除了日期類,日期類只能通過toString()轉換。那些不是javascript 語言核心中的對象則通過實現中定義的方法轉換為原始值。
其它不同類型之間的比較均不相等
這裡有一個判斷相等的小例子
"1" == true
這個表達式的結果為true,這表明完全不同類型的值比較結果為相等。布爾值true首先轉換為數字1 ,然後再執行比較。接下來字符串“1”也會轉換為數字1 ,因為兩個數字的值相等,因此結果為true.
ii.比較運算符
小於(<)
如果第一個操作數小於第二個操作數,則“<”運算結果我true,否則為false
小於等於(<=)
大於(>)
大於等於(>=)
....(不詳細介紹含義)
比較操作符的操作數可能是任意類型。然而只有數字和字符串才能真正執行比較操作符,因此,那些不是數字和字符串的操作數都將進行類型轉換。類型轉換規則如下:
如果操作數為對象,則按照3章8節3小節處鎖描述的轉換規則轉換為原始值:如果valueOf()返回一個原始值,那麼直接使用這個原始值。否則使用toString() 的轉換結果進行比較。
在對轉換為原始值之後,如果兩個操作數都是字符串,那麼將依字母表的順序對兩個字符串進行比較,這裡提到的“字母表順序”是組成這兩個字符串的16位Unicode字符的索引順序。
在對象轉換為原始值之後,如果至少一個操作數不去是字符串,那麼兩個操作數都將為數字進行數值的比較。0和-0是相等的。Infinty壁其它任何數字都大(除了infinty本身),-infinty比任何數字都小(除了它自己本身。)如果一個操作數(或轉換後)為NaN,那麼比較符總是返回false
對於數字和字符串操作符來說,加號運算符和比較運算符的行為有所不同 ,前者更偏愛字符串,如果它的其中一個操作數是字符串的話,則進行字符串連接操作。而比較運算符則更偏愛數字,只有在兩個操作數都是字符串串的時候。才會進行字符串的比較。
代碼如下:
1 + 2 //=>3 加法,結果為3
"1" + "2" //字符串連接,結果為"12"
"1" + 2 //字符串連接,2轉換為"2",結果"12"
11 < 3 //數字比較,結果true
"11" < "3" //字符串比較,結果為true
"11" < 3 //數字的比較,“11”轉換為11,結果為true
"one" < 3 //數字比較,"one"轉換為NaN,結果為falase
最後需要注意的是,“<=”和“>=”運算符在判斷相等的時候,並不依賴相等運算符和和嚴格相等運算比較規則。相反,小於等於運算符芝是簡單的“不大於”,大於等於運算只是“不小於”。只有一個例外,的那個一個操作數(後轉換後)是NaN的時候,所有4個比較運算符均會返回fasle.
iii.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 = [7, 8, 8]
"0" in data //=>true 數組包含0
1 in data //=>true 數字轉換為字符串
3 in data //=>fase 沒有索引為3的元素
iiii.instanceof運算符
instanceof運算符希望左操作符為一個對象,右操作數標示對象的類。如果左側的對象是右側類的實例,則表達式返回true;負責返回false.第9章將會講到。javascript對象的類是通過初始化他們的構造函數的來定義的。這樣的話,instanceof的右操作數應當是一個函數。比如:
代碼如下:
var d = new Date(); //構造一個新對象
d instanceof Date; //計算結果為true, d是Date() 創建的
d instanceof Object //計算結果為true ,所有的對象都是Object的實例
d instanceof Number //計算結果為 false,d不是一個Number對象
var a = [1,2,3] //數組直接量創建數組
a instanceof Array //計算結果true a為數組
a instanceof Object //true 所有的數組都是對象
a instanceof RegExp //fasle 數組不是正則表達式
需要注意的是,所有對象都是Object的實例。當通過instanceof盤對一個對象是否為一個類的實例的時候,這個判斷也叫“父類”(superclass)的檢測,如果instanceof的左側操作對象不是對象的話,instanceof返回false。如果右側操作不是函數,則拋出類型錯誤的異常。
為了理解instanceof運算符是如何工作的,必須首先理解“原型類”(prototype chain),原型鏈作為javascript的繼承機制,將在6章2節2小節詳細描述。
為了計算表達式o instanceof f ,javascript筆仙首先計算f.prototyoe,然後在原型鏈中查詢o,如果找到,那麼o是f(或者f的父類)的一個實例,那麼返回true。反之false
10.邏輯表達式
邏輯運算符"&&"、“||”、“!”是對操作進行布爾算術運算,經常和關系運算符一起配合使用,邏輯運算符將多個關系表達式組合起來組成一個更復雜的表達式。
i.邏輯與
"&&"運算符可以從三個不同的層次進行理解。最簡單一層理解是,當操作數都是布爾值是,“&&”對兩個布爾值執行布爾與(AND)操作,只有在第一個操作數和第二個操作數都是true的時候,它才返回true.如果其中有一個操作數為false.則它返回false.
"&&"長用來連接兩個關系表達式
x == 0 && y == 0; //只有在x和y都是0時,才返回true
關系表達式總是返回true或false,因此當這樣使用的時候,“&&”本身也返回true或 false。關系運算符的優先級要比"&&"(和“||”)要高,因此類似這種表達式可以放心地書寫,而不用補充園括號。
"&&"操作數並不一定是布爾值,回想一下,有些值是可以當做“真值”和“假值”的。(如3章3節,假值是:false null undefined 0 -0 NaN和"",所有和其它的值包括所有的對象都是真值)。對“&&”第二層理解是,“&&”是可以對真值和假值進行布爾與(AND)操作。如果兩個操作數都是真值的,則那麼返回一個真值;否則,至少一個操作數是假值的。javascript中在任何使用布爾值的地方的時候,表達式語句都會將其當做真值或假值來對待,因此實際上“&&”並不總是返回true和false.但也並無大礙。
需要注意的是,上文提到了運算符返回“真值”和“假值”,但並沒說說明這個“真值”或者“假值”到底是什麼值,為此我們深入討論對“&&”第三層的理解。運算符首先計算左操作數的值,即首先計算“&&”左側的表達式,如果計算結果是假值,那麼整個表達式的結果一定是假值,因此“&&”這時簡單的返回左操作的值,而並不會對右邊的操作數進行計算。
代碼如下:
var o = {
x: 1
};
var p = null;
o && o.x; //=>1 : 1:0是真值,因此返回值是o.x
p && p.x //= null :p是假值,因此將其返回,而並不計算p.x
這對於理解“&&”可能不計算右操作數的情況至關重要,在上述代碼中,變量P的值是null,而如果計算p.x的話則會拋出一個異常錯誤,因此,只有p為真值(不能是null或undefined)的情況下才計算p.x
"&&"的行為有時候被稱為“短路”(short circuiting),我們經常能看到很多代碼利用了這一也行來有條件的執行代碼。例如下面的兩條代碼是等價的
代碼如下:
if (a == b) stop(); //只有a==b時才能調運stop()
(a == b) && stop(); //同上
一般來講,當“&&”右側的表達式具有副作用的時候(賦值,遞增,遞減和函數調用表達式)要格外小心。因為這些帶有副作用的表達式的執行時候,依賴於左操作鼠的計算結果。
盡管“&&”可以按照第二層和第三層的理解進行一些復雜的表達式運算,但大多數的情況下,“&&”僅用來對真值和假值的做布爾計算。
ii.邏輯或(||)
"||"運算符對兩個操作數做布爾或(OR)運算。如果其中一個為真值,則返回真值,兩個操作數都為假值,返回假值。
盡管“||”運算符大多情況下只是做簡單的布爾或(OR)運算,和“&&”一樣,也具備一些更復雜的行為,它首先計算第一個操作數的值,也就是說回首先計算左側的表達式,如果計算結果為真,則返回真值,否則,再計算第二個值。
和“&&”一樣,用於應該避免右操作數包含一些具有副作用的表達式,除非你目地明確在右側使用帶副作用的表達式,而有可能不會計算右側的表達式。
這個運算符最常用的方式是用來從一組備選的表達中選取第一個真值的表達式。
代碼如下:
//如果max_width已經定義了,則直接使用它。賦值在preferences對象中查找max_width
//如果沒有定義它,則使用一個寫死的常量。
var max =max_width || preferences.max_windth || 500;
這種貫用法通常在函數體內,用來給參數提供默認值。
代碼如下:
//將o成功的屬性復制到p中,並返回p
function copy(o, p) {
p = p || {}; //如果向參數p沒有傳入任何對象,則使用一個新創建對象。
//函數體內的主邏輯
iii.邏輯非(!)
"!"運算符是一元運算符,它放置在一個單獨操作數之前。它的目的是將操作數的布爾值求反。
和"&&"、"||"運算符不同,“!”運算符首先將其操作數轉換為布爾值(參考第三章的講訴規則),然後再對布爾值求反。也就是"!"總是返回true和 false。並且,可以通過使用兩次邏輯非運算來得到一個值的布爾值:(!!x,參照第三章第八節第2小節)
“!”具有很高的優先級,並且和操作數緊密的綁在一起,如果希望對p && q,則需要園括號!(p && q)。如下代碼:
代碼如下:
!(p && q) === !p || !q
!(p || q) === !p && !q
對於p和q取任何值,這兩個表達式永遠成立。
11.賦值表達式
javascript使用"="運算符給變量或者屬性來賦值,例如:
代碼如下:
i = 0 //將變量i設置為0
o.x = 1 //將對象o的屬性x 設置為1
“=”運算符希望它的左操作數為一個左值:一個變量或者對象屬性(或數組元素),它的右操作鼠可以是任意的類型的任意值。賦值表達式的值就是右操作數的值。賦值表達式的副作用是,右操作數的值賦值給左側的變量或對象屬性。這樣的話,後續對這個變量和對象的屬性的引用都將得到這個值。
盡管賦值表達式的值非常簡單,但有時候會看到一些復雜表達式包含賦值表達式的情況。例如:將賦值和檢測操作放在一個表達式中:
代碼如下:
(a = b) == 0
如果這樣的話,應該清楚地知道"="和"=="區別!,需要注意的是,“=”有非常低的優先級,通常在一個較長的表達式中用到一條賦值語句時,需要補充園括號以保障正確的運算順序。
賦值操作符的結合性是從右至左,也就是說,一個表達式中出現了多個賦值運算符,運算順序也從右至左,因此,可以通過以下方式對多個變量賦值。
代碼如下:
i=j=k=0; //把三個變量初始化為0
帶操作的賦值運算:
除了常規的賦值運算外,javascript還支持需要其他的賦值運算符,這些運算符將賦值運算符合其他的運算符連接起來。提供一種更為快捷的運算方式。例如+=運算符執行的是加法運算符和賦值操作,下面的表達式:
total += salaes_tax;
和下面的表達式等價的
total = total + salaes_tax
運算符“+=”可以作用於數字或字符串,如果其操作是數字,它將執行加法運算和賦值操作;如果是字符串,他就執行字符串的連接和賦值操作。
此類型的運算符還包括,"-=","*=","&="等,如下表賦值運算符
運算符 示例 等價於
+= a+=b a=a+b
-= a-=b a=a-b
*= a*=b a=a*b
/= a/=b a=a/b
%= a%=b a=a%b
<<= a<<=b a=a<<b
>>= a>>=b a=a>>b
>>>= a>>>=b a=a>>>b
&= a&=b a=a&b
|= a|=b a=a|b
^= a^=b a=a^b
大多數情況下,表達式為
a op =b
這裡的op代表一個運算符,這個表達式等價於
a =a op b
在第一行中,表達式a計算了一次,在第二行中,表達式a計算了兩次。
只有 a包含具有副作用的表達式(比如函數調用和賦值操作)的時候,兩者才不等價。如下兩個表達式不等價
代碼如下:
data[i++] *= 2;
data[i++] = data[i++] * 2
12.表達式計算
和很多解釋性語言一樣,javascript同樣可以解釋運行由javascript源代碼組成的字符串,並產生一個值。javascript通過全局函數eval()來完成這個工作。
eval("3+2") //=>5
動態判斷源代碼中的字符串是一種強大語言的特性,幾乎沒有必要在實際中應用。如果你使用了eval(),你應該仔細考慮真的需要它。
下面降價eval()基礎用法,並介紹兩種嚴格使用它的方法,從代碼優化的角度來講,這兩種方法對原有的代碼影響是最小的。
i.eval(eval()是一個函數,但由於它已經被當做運算符來對待了。)
eval()只有一個參數,如果傳入的參數不是字符串,它直接返回這個參數。如果參數是字符串,它會把字符串當成javascript進行編譯(parse),如果編譯失敗則拋出一個語法錯誤(SyntaxError)。如果編譯成功,則開始執行這段代碼,並返回字符串中最後一個表達式或語句的值,如果最後一個表達式沒有語句或者值,則最終返回undefined。如果字符串拋出一個異常,這個異常把該調用的傳給eval()
關於eveal()最重要的是,它使用了調用它的變量作用域環境,也就是說,它查找變量的值和定義新變量和函數的操作和局部的代碼作用域中的代碼一樣。如果一個函數定義了一個局部變量x,然後調用了eval("x"),它會返回局部變量的值。如果它調用eval("x=1"),它會改變局部變量的值。如果函數調用了eval("var y=3;")它聲明一個新的局部變量y。同樣的,一個函數可以通過如下代碼聲明一個局部函數:
eval("function f(){return x+1;}");
如果最頂層的代碼中調用了eval()。當然它會作用於全局變量和全局函數。
ii.全局eval()
eval()具有改變局部變量的能力,這對javascript優化器來說,是一個很大的問題,然而作為一種權宜之計,javascript征對那行調用了eval()函數所做的優化並不多。但當腳本定義了一個別名,並且用令一個名稱來調用它,javascript解釋器又如何工作呢,為了javascript解釋器更加簡化。ECMAScipt3標准規定了任何解釋器都不允許對eval()賦予別名。如果eval()使用別的別名來調用的話,則會拋出EvalError異常。
實際上,大多數的實現並不是這樣做的。當通過別名調用時,eval()會將其字符串當成頂層的全局代碼來執行。執行代碼可能會定義新的全局變量和全局函數。執行的代碼可能會定義新的全局變量和全局函數,或者給全局變量賦值。但卻不能修改或修改主調函數中的局部變量,因此這不會影響到函數內的代碼優化。
ECMAScript5是反對使用EvalError的,並且規范了eval()的行為。“直接的eval”,當直接使用非限定的“eval”名稱,來調用eval()函數時,它總共是在它的上下文作用域內支線。其它間接調用則使用全局函數為其上下文作用域。並且無法讀、寫、定義局部變量和函數。下面有一段代碼實例:
代碼如下:
var geval = eval; //使用別名調用eval將是全局eval
var x = "global",
y = "global"; //兩個全局變量
function f() { //函數內執行的局部eval
var x = "local" //定於局部變量
eval("x += 'changed';"); //直接eval更改了局部變量的
return x; //返回更改後的局部變量
}
function g() { //這個函數執行了全局eval
var y = "local" //定義了局部變量
geval("y += 'changed';"); //間接改變了局部變量的值
return y; //返回未更改的局部變量
}
console.log(f(), x); //更改了局部變量,輸出local changed global
console.log(g(), y); //更改了全局變量,輸出local globalchanged
13.其它運算符。
javascript支持很多其它各種各樣的運算符。
i.條件運算符(?:)
條件運算符是javascript中的唯一一個三元運算符。通常這個運算符寫成"?:",這個運算符擁有三哥操作數,第一個操作數在"?"之前,第二個操作數在“?”和":"之間。第三個操作數早在":"之後,例如
x > 0 ? x : -x; //求x的絕對值
條件運算符的操作數可以是任意類型。第一個操作數當成布爾值,如果它是真值,那麼將計算第二個操作數,並返回計算結果。賦值如果第一個值操作數是假值,那麼將計算第三個操作數。並返回計算結果。第二個和第三個操作數總會計算其中之一。不可能兩者同時進行。其實使用if語句也達到同樣的效果(5.4.1),“?:”運算符只是提供了一種簡寫形式。這裡是一個"?:"的典型使用場景,判斷一個變量是否有定義,如果有定義則使用它,如果無定義,則使用一個默認值。
代碼如下:
grett = "hello" + (username ? username : "three");
和以下的代碼是等價的,但上面的更加簡潔
代碼如下:
grett = "hello";
if (username)
grett += username;
else
grett + "three"
ii.typeof()運算符
typeof是一元運算符,放在單個操作數前面,操作數可以是任何類型,返回值表示操作類型的一個字符串。
代碼如下:
x __ typeof x
undefined __ "undefined"
null __ "object"
ture或false __"boolean"
任意數字或NaN __ "Number"
任意字符串 __ "String"
任意函數 __ "function"
任意內容對象(非函數)__ "object"
任意宿主對象 __ 由編譯器各自實現的字符串,但不是"undefined" "boolean" "number" "string"
typeof最常用的用法寫在表達式中們就像這樣
(typeof value == "string") ? "" + value + "":value;
typeof運算符同樣在swith語句中(5.4.3)非常有用,需要注意的是,typeof運算可以帶上園括號。這樣讓typeof看起來像一個函數名,而非關鍵字
typeof(i)
iii.delete運算符
delete是一元操作符,它用來刪除對象的屬性或者數組的元素。就像賦值、遞增、遞減運算符一樣。delete也是具有副作用的。它是用來做刪除操作的。不是用來返回一個值的。
代碼如下:
var o = {
x: 1,
y: 2
}
delete o.x;
"x" in o; //=>false
var a = [1, 2, 3];
delete a[2]; // 刪除數組中最後一個元素
2 in a; //=> false 元素2已經在數組中不存在了
a.length; //=>3,注意,數組長度並沒有改變,盡管上一行刪除了這個元素,但刪除操作留下了一個洞。實際上並沒有修改數組的長度,因此a的長度仍然為3
需要注意的是,刪除屬性或刪除數組元素不僅僅設置了一個undefined值,當刪除一個屬性時,這個屬性不復存在。讀取一個不存在的值將會返回undefined.關於delete刪除還有嚴格模式下的一些情況,需要學習的人自己試驗,這裡給一些例子。
代碼如下:
var o = {x: 1,y: 2};
delete o.x; //刪除一個對象屬性,返回true
typeof o.x; //屬性不存在,返回"undefined"
delete o.x; //刪除不存在的屬性,返回true;
delete o; //不能刪除通過var關鍵字聲明的變量,返回false
delete 1; //參數不是一個左值。
this.x = 1;// 給全局定義一個屬性,這裡沒有使用var
delete x ; //試圖刪除它,在非嚴格模式下返回true
//在嚴格模式下回拋出異常,這時使用"delete this.x"來代替
x; //運行時出錯,沒有定義x
6章第三節還有關於delete的討論。
iii.void運算符。
void是一元運算符,在出現操作數之前,操作數可以是任何類型。這個運算符並不是經常使用:操作數會照常計算,但會忽略計算結果並返回undefined。由於void會忽略操作數的值,因此在操作數具有副作用時使用void來程序更有意義。
這個最常用的帶客戶端url.在url寫帶有副作用的表達式,而void則讓浏覽器不顯示在這個表達式的運算結果。
代碼如下:
<a href="javascript:void window.open();">new</a>
iiii.逗號運算符。(,)
逗號運算符是二元運算符,它的操作數可以是任意類型。它首先計算左操作數,然後計算右操作數。
代碼如下:
i = 0, j = 1, k = 2;
它和下面的代碼基本上等價的
i = 0; j = 1; k = 2;
總是會計算左側的表達式,但計算結果忽略掉,也就是說,只有左側表達式具有副作用,才會使用逗號運算讓代碼變得更通暢。逗號運算符最常用的場景是for循環中,這個for循環通常有多個循環變量。
代碼如下:
//for循環中的第一個逗號是var語句的一部分
//第二個逗號是逗號運算符
//它將兩個表達式(i++和j++)放在一條(for循環中)語句中
for (var i = 0, j = 10; i < j; i++, j--);
console.log(i + j);