【讀書筆記】《JavaScript權威指南》第4章表達式和運算符

  爲什麼要理解表達式,運算符和語句呢?拋開可以加深你對計算機語言語法的理解這種有一些虛幻縹緲的原因,最直接的原因是我們在面試題上會遇到,它們經常會把語句寫的長長的,錯綜複雜,不加分號和空格,然後問你運行結果,這時候就考你什麼是語句,表達式,什麼會觸發計算,怎麼把它斷句,組成正確的運行順序。

概念(掌握)

表達式

  表達式(expression)JavaScript中的一個短語,JavaScript解釋器會將其計算(evaluate)出一個結果。
  程序中的常量是最簡單的一類表達式。變量名也是一種簡單的表達式,它的值就是賦值給變量的值。
  複雜表達式是由簡單表達式組成的。比如,數組訪問表達式是由一個表示數組的表達式、左方括號、一個整數表達式和右方括號構成。它們所組成的新的表達式的運算結果是該數組的特定位置的元素值。同樣的,函數調用表達式由一個表示函數對象的表達式和0個或多個參數表達式構成。

運算符

  將簡單表達式組合成複雜表達式最常用的方法就是使用運算符(operator)。運算符按照特定的運算規則對操作數(通常是兩個)進行運算,並計算出新值。
  乘法運算符“*”
是比較簡單的例子。表達式x*y是對兩個變量表達式x和y進行運算並得出結果。有時我們更願意說運算符返回了一個值而不是“計算”出了一個值。

表達式(掌握)

原始表達式

  原始表達式是表達式的最小單位——它們不再包含其他表達式。JavaScript中的原始表達式包含常量或直接量、關鍵字和變量。

1.23 //數字直接量
"hel1o" //字符串直接量
/pattern/ //正則表達式直接量

  JavaScript中的一些保留字構成了原始表達式:

true//返回一個布爾值:真
false//返回一個布爾值:假
null//返回一個值:空
this//返回“當前“對象

  最後,第三種原始表達式是變量:

i//返回變量i的值
sum//返回sum的值
undefined//undefined是全局變量,和null不同,它不是一個關鍵字

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

  對象和數組初始化表達式實際上是一個新創建的對象和數組。這些初始化表達式有時稱做“對象直接量”和“數組直接量”。然而和布爾直接量不同,它們不是原始表達式,因爲它們所包含的成員或者元素都是子表達式。

數組初始化表達式

  數組初始化表達式是通過一對方括號和其內由逗號隔開的列表構成的。初始化的結果是一個新創建的數組。數組的元素是逗號分隔的表達式的值:

[] //一個空數組:[]內留空即表示該數組沒有任何元素
[1+2,3+4] //擁有兩個元素的數組,第一個是3,第二個是7

  數組直接量中的列表逗號之間的元素可以省略,這時省略的空位會填充值undefined。例如,下面這個數組包含5個元素,其中三個元素是undefined:

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

  數組直接量的元素列表結尾處可以留下單個逗號,這時並不會創建一個新的值爲undefined的元素。

對象初始化表達式

  對象初始化表達式和數組初始化表達式非常類似,只是方括號被花括號代替,並且每個子表達式都包含一個屬性名和一個冒號作爲前綴:

var p={x:2.3,y:-1.2};//一個擁有兩個屬性成員的對象
var q={};//一個空對象
q.x=2.3;q-y=-1.2;//q的屬性成員和p的一樣

  對象直接量中的屬性名稱可以是字符串而不是標識符

var side=1; 
var square={"upperLeft":{x:p.x,y:p.y},
            'lowerRight':{x:p.x+side,y:p.y+ side}};

函數定義表達式

  函數定義表達式定義一個JavaScript函數。表達式的值是這個新定義的函數。從某種意義上講,函數定義表達式可稱爲“函數直接量”。
  一個典型的函數定義表達式包含關鍵字function,跟隨其後的是一對圓括號,括號內是一個以逗號分割的列表,列表含有0個或多個標識符(參數名),然後再跟隨一個由花括號包裹的JavaScript代碼段(函數體),例如:

//這個函數返回傳入參數值的平方
var square=function(x) {return x*x;}

  函數定義表達式同樣可以包含函數的名字。函數也可以通過函數語句來定義,而不是函數表達式。更多詳情會在後面中討論。

屬性訪問表達式

  屬性訪問表達式運算得到一個對象屬性或一個數組元素的值。JavaScript爲屬性訪問定義了兩種語法:

expression.identifier 
expression[expression]

  第一種寫法是一個表達式後跟隨一個句點和標識符。表達式指定對象,標識符則指定需要訪問的屬性的名稱。
  第二種寫法是使用方括號,方括號內是另外一個表達式(這種方法適用於對象和數組)。第二個表達式指定要訪問的屬性的名稱或者代表要訪問數組元素的索引。
  不管使用哪種形式的屬性訪問表達式,在“.”和“[”之前的表達式總是會首先計算。

  • 如果計算結果是null或者undefined,表達式會拋出一個類型錯誤異常,因爲這兩個值都不能包含任意屬性。
  • 如果運算結果不是對象(或者數組),JavaScript會將其轉換爲對象。
  • 如果對象表達式後跟隨句點和標識符,則會查找由這個標識符所指定的屬性的值,並將其作爲整個表達式的值返回。
  • 如果對象表達式後跟隨一對方括號,則會計算方括號內的表達式的值並將它轉換爲字符串。
  • 不論哪種情況,如果命名的屬性不存在,那麼整個屬性訪問表達式的值就是undefined。

  顯然.identifier的寫法更加簡單,但需要注意的是,這種方式只適用於要訪問的屬性名稱是合法的標識符,並且需要知道要訪問的屬性的名字。
  如果屬性名稱是一個保留字或者包含空格和標點符號,或是一個數字(對於數組來說),則必須使用方括號的寫法。
  當屬性名是通過運算得出的值而不是固定的值的時候,這時必須使用方括號寫法。

調用表達式

  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 invocation)。在方法調用中,執行函數體的時候,作爲屬性訪問主題的對象和數組(也就是"."的對象或數組)便是其調用方法內this的指向。這種特性使得在面向對象編程範例中,函數(其OO名稱爲“方法”)可以調用其宿主對象。
  在ECMAScript 5中,那些通過嚴格模式定義的函數在調用時將使用undefined作爲this的值,this不會指向全局對象。

對象創建表達式

  對象創建表達式(object creation expression)創建一個對象並調用一個函數(這個函數稱做構造函數)初始化新對象的屬性。對象創建表達式和函數調用表達式非常類似,只是對象創建表達式之前多了一個關鍵字new:

new Object()
new Point(2,3)

  如果一個對象創建表達式不需要傳入任何參數給構造函數的話,那麼這對空圓括號是可以省略掉的:

new Object
new Date

  JavaScript首先創建一個新的空對象,然後,JavaScript通過傳入指定的參數並將這個新對象當做this的值來調用一個指定的函數。這個函數可以使用this來初始化這個新創建對象的屬性。
  那些被當成構造函數的函數不會返回一個值,並且這個新創建並被初始化後的對象就是整個對象創建表達式的值。如果一個構造函數確實返回了一個對象值,那麼這個對象就作爲整個對象創建表達式的值,而新創建的對象就廢棄了。

運算符(掌握)

運算符概述

  需要注意的是,大多數運算符都是由標點符號表示的,比如“+”和“=”。而另外一些運算符則是由關鍵字表示的,比如delete和instanceof。關鍵字運算符和標點符號所表示的運算符一樣都是正規的運算符,它們的語法都非常言簡意賅。
  下表是按照運算符的優先級排序的,前面的運算符優先級要高於後面的運算符優先級。
被水平分割線分隔開來的運算符具有不同的優先級。標題爲A的列表示運算符的結合性,L(從左至右)或R(從右至左),標題爲N的列表示操作數的個數。標題爲“類型”的列表示期望的操作數類型,以及運算符的結果類型(在“一”符號之後)。下表之後的段落會解釋優先級、結合性和操作數類型的概念。
image

操作數的個數

  運算符可以根據其操作數的個數進行分類。JavaScript中的大多數運算符(比如“*”乘法運算符)是一個二元運算符(binary operator),將兩個表達式合併成一個稍複雜的表達式。換言之,它們的操作數均是兩個。
  JavaScript同樣支持一些一元運算符(unary operator),它們將一個表達式轉換爲另一個稍複雜的表達式。表達式一x中的“-”運算符就是一個一元運算符,是將操作數x求負值。
  最後,JavaScript支持一個三元運算符(ternary operator),條件判斷運算符“?:”,它將三個表達式合併成一個表達式。

操作數類型和結果類型

  一些運算符可以作用於任何數據類型,但仍然希望它們的操作數是指定類型的數據,並且大多數運算符返回(或計算出)一個特定類型的值。在上表標題爲“類型”的列中列出了運算符操作數的類型(箭頭前)和運算結果的類型(箭頭後)。

左值

  上表中的賦值運算符和其他少數運算符期望它們的操作數是1va1類型。左值(lvalue)是一個古老的術語,它是指“表達式只能出現在賦值運算符的左側”。在JavaScript中,變量、對象屬性和數組元素均是左值。ECMAScript規範允許內置函數返回一個左值,但自定義的函數則不能返回左值。

運算符的副作用

  計算一個簡單的表達式(比如2*3)不會對程序的運行狀態造成任何影響,程序後續執行的計算也不會受到該計算的影響。而有一些表達式則具有很多副作用,前後的表達式運算會相互影響。
  賦值運算符是最明顯的一個例子:如果給一個變量或屬性賦值,那麼那些使用這個變量或屬性的表達式的值都會發生改變。
  “++”和“--”遞增和遞減運算符與此類似,因爲它們包含隱式的賦值。delete運算符同樣有副作用:刪除一個屬性就像(但不完全一樣)給這個屬性賦值undefined。
  其他的JavaScript運算符都沒有副作用,但函數調用表達式和對象創建表達式有些特別,在函數體或者構造函數內部運用了這些運算符併產生了副作用的時候,我們說函數調用表達式和對象創建表達式是有副作用的。

運算符優先級

  上表中所示的運算符是按照優先級從高到低排序的,每個水平分割線內的一組運算符具有相同的優先級。運算符優先級控制着運算符的執行順序。優先級高的運算符(表格的頂部)的執行總是先於優先級低(表格的底部)的運算符。
  運算符的優先級可以通過顯式使用圓括號來重寫。
  需要注意的是,屬性訪問表達式和調用表達式的優先級要比上表中列出的所有運算符都要高。看一下這個例子:

typeof my.functions[x](y)

  儘管typeof是優先級最高的運算符之一,但typeof也是在兩次屬性訪問和函數調用之後執行的。
  實際上,如果你真的不確定你所使用的運算符的優先級,最簡單的方法就是使用圓括號來強行指定運算次序。有些重要規則需要熟記:乘法和除法的優先級高於加法和減法,賦值運算的優先級非常低,通常總是最後執行的。

運算符的結合性

  結合性指定了在多個具有同樣優先級的運算符表達式中的運算順序。在上表中標題爲A的列說明了運算符的結合性。L指從左至右結合,R指從右至左結合。
  一元操作符、賦值和三元條件運算符都具有從右至左的結合性。

運算順序

  運算符的優先級和結合性規定了它們在複雜的表達式中的運算順序,但並沒有規定子表達式的計算過程中的運算順序。
  JavaScript總是嚴格按照從左至右的順序來計算表達式。
  例如,在表達式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。按照“+ +”的定義,第2)步中a++的結果依然是1,即c爲1,隨後a立即增1,因此在執行第3)步時,a的值已經是2。所以b的結果爲3。很多初學者會誤認爲a增1的操作是在表達式計算完畢後執行的。

算術表達式

“+”運算符

  二元加法運算符“+”可以對兩個數字做加法,也可以做字符串連接操作。
  加號的轉換規則優先考慮字符串連接,如果其中一個操作數是字符串或者轉換爲字符串的對象,另外一個操作數將會轉換爲字符串,加法將進行字符串的連接操作。如果兩個操作數都不是類字符串(string-like)的,那麼都將進行算術加法運算。

  • 如果其中一個操作數是對象,則對象會遵循對象到原始值的轉換規則轉換爲原始類值:日期對象通過toString()方法執行轉換,其他對象則通過valueOf()方法執行轉換(如果value0f()方法返回一個原始值的話)。由於多數對象都不具備可用的valueof()方法,因此它們會通過tostring()方法來執行轉換。
  • 在進行了對象到原始值的轉換後,如果其中一個操作數是字符串的話,另一個操作數也會轉換爲字符串,然後進行字符串連接。
  • 否則,兩個操作數都將轉換爲數字(或者NaN),然後進行加法操作。
1+{} //=>"1[object Object]":對象轉換爲字符串後進行字符串連接
2+undefined //=>NaN:undefined轉換爲NaN後做加法

一元算術運算符

  一元運算符作用於一個單獨的操作數,併產生一個新值。在JavaScript中,一元運算符具有很高的優先級,而且都是右結合(right-associative)。

一元加法(+)

  一元加法運算符把操作數轉換爲數字(或者NaN),並返回這個轉換後的數字。如果操作數本身就是數字,則直接返回這個數字。

一元減法(-)

  當“-”用做一元運算符時,它會根據需要把操作數轉換爲數字,然後改變運算結果的符號。

遞增(++)

  遞增“+ +”運算符對其操作數進行增量(加一)操作,操作數是一個左值(lvalue)(變量、數組元素或對象屬性)。運算符將操作數轉換爲數字,然後給數字加1,並將加1後的數值重新賦值給變量、數組元素或者對象屬性。
  遞增“+ +”運算符的返回值依賴於它相對於操作數的位置。當運算符在操作數之前,稱爲“前增量”(pre-increment)運算符,它對操作數進行增量計算,並返回計算後的值。當運算符在操作數之後,稱爲“後增量”(post-increment)運算符,它對操作數進行增量計算,但返回未做增量計算的(unincremented)值。
  需要注意的是,表達式++x並不總和x=x+1完全一樣,“+ +”運算符從不進行字符串連接操作,它總是會將操作數轉換爲數字並增1。如果x是字符串“1”,++x的結果就是數字2,而x+1是字符串“11”。
  同樣需要注意的是,由於JavaScript會自動進行分號補全,因此不能在後增量運算符和操作數之間插入換行符。如果插入了換行符,JavaScript將會把操作數當做一條單獨的語句,並在其之前補上一個分號。

遞減(--)

  遞減“一”運算符的操作數也是一個左值。它把操作數轉換爲數字,然後減1,並將計算後的值重新賦值給操作數。和“++”運算符一樣,遞減“--”運算符的返回值依賴於它相對操作數的位置,當遞減運算符在操作數之前,操作數減1並返回減1之後的值。當遞減運算符在操作數之後,操作數減1並返回減1之前的值。當遞減運算符在操作符的右側時,運算符和操作數之間不能有換行符。

位運算符

  位運算符可以對由數字表示的二進制數據進行更低層級的按位運算。儘管它們並不是傳統的數學運算,但這裏也將其歸類爲算術運算符,因爲它們作用於數值類型的操作數並返回數字。這些運算符在JavaScript編程中並不常用,如果你對十進制整數的二進制表示並不熟悉的話,你可以跳過本節內容。所以我跳過了。。。。

關係表達式

  關係運算符用於測試兩個值之間的關係(比如“相等”,“小於”,或“是.的屬性”),根據關係是否存在而返回true或false。關係表達式總是返回一個布爾值。

相等和不等運算符

  “= =”和“= = =”運算符用於比較兩個值是否相等,兩個運算符允許任意類型的操作數,如果操作數相等則返回true,否則返回false。
  “= = =”也稱爲嚴格相等運算符(strict equality)(有時也稱做恆等運算符(identity operator)),它用來檢測兩個操作數是否嚴格相等。“==”運算符稱做相等運算符(equality operator),它用來檢測兩個操作數是否相等,這裏“相等”的定義非常寬鬆,可以允許進行類型轉換。
  “!=”和“!= =”運算符的檢測規則是“= =”和“= = =”運算符的求反。如果兩個值通過“= =”的比較結果爲true,那麼通過“!=”的比較結果則爲false。如果兩值通過
“= = =”的比較結果爲true,那麼通過“!= =”的比較結果則爲false。“!=”稱做“不相等”、“!= =”稱做“不嚴格相等”。

嚴格相等運算符“===”計算規則
  • 如果兩個值類型不相同,則它們不相等。
  • 如果兩個值都是nul1或者都是undefined,則它們不相等。如果兩個值都是布爾值true或都是布爾值false,則它們相等。
  • 如果其中一個值是NaN,或者兩個值都是NaN,則它們不相等。
  • 如果兩個值爲數字且數值相等,則它們相等。如果一個值爲0,另一個值爲-0,則它們同樣相等。
  • 如果兩個值爲字符串,且所含的對應位上的16位數(參照3.2節)完全相等,則它們相等。如果它們的長度或內容不同,則它們不等。兩個字符串可能含義完全一樣且所顯示出的字符也一樣,但具有不同編碼的16位值。JavaScript並不對Unicode進行標準化的轉換,因此像這樣的字符串通過“= = =”和“= =”運算符的比較結果也不相等。
  • 如果兩個引用值指向同一個對象、數組或函數,則它們是相等的。如果指向不同的對象,則它們是不等的,儘管兩個對象具有完全一樣的屬性。
相等運算符“==”計算規則
  • 如果兩個操作數的類型相同,則和上文所述的嚴格相等的比較規則一樣。如果嚴格相等,那麼比較結果爲相等。如果它們不嚴格相等,則比較結果爲不相等。
  • 如果兩個操作數類型不同,“==”相等操作符也可能會認爲它們相等。檢測相等將會遵守如下規則和類型轉換:
    • 如果一個值是null,另一個是undefined,則它們相等。
    • 如果一個值是數字,另一個是字符串,先將字符串轉換爲數字,然後使用轉換後的值進行比較。
    • 如果其中一個值是true,則將其轉換爲1再進行比較。如果其中一個值是false,則將其轉換爲0再進行比較。
    • 如果一個值是對象,另一個值是數字或字符串,則使用轉換規則將對象轉換爲原始值,然後再進行比較。對象通過toString()方法或者value0f()方法轉換爲原始值。JavaScript語言核心的內置類首先嚐試使用valueof(),再嘗試使用toString(),除了日期類,日期類只使用toString()轉換。那些不是JavaScript語言核心中的對象則通過各自的實現中定義的方法轉換爲原始值。
    • 其他不同類型之間的比較均不相等。

比較運算符

  比較操作符的操作數可能是任意類型。然而,只有數字和字符串才能真正執行比較操作,因此那些不是數字和字符串的操作數都將進行類型轉換,類型轉換規則如下:

  • 如果操作數爲對象,那麼這個對象將依照轉換規則轉換爲原始值:如果valueof()返回一個原始值,那麼直接使用這個原始值。否則,使用tostring()的轉換結果進行比較操作。
  • 在對象轉換爲原始值之後,如果兩個操作數都是字符串,那麼將依照字母表的順序對兩個字符串進行比較,這裏提到的“字母表順序”是指組成這個字符串的16位Unicode字符的索引順序。
  • 在對象轉換爲原始值之後,如果至少有一個操作數不是字符串,那麼兩個操作數都將轉換爲數字進行數值比較。0和一0是相等的。Infinity比其他任何數字都大(除了Infinity本身),-Infinity比其他任何數字都小(除了它自身)。如果其中一個操作數是(或轉換後是)NaN,那麼比較操作符總是返回false。

  需要注意的是,JavaScript字符串是一個由16位整數值組成的序列,字符串的比較也只是兩個字符串中的字符的數值比較。由Unicode定義的字符編碼順序和任何特定語言或者本地語言字符集中的傳統字符編碼順序不盡相同。注意,字符串比較是區分大小寫的,所有的大寫的ASCII字母都“小於”小寫的ASCII字母。
  參照String.localCompare()方法來獲取更多字符串比較的相關信息,String.1ocalCompare()方法更加健壯可靠,這個方法參照本地語言的字母表定義的字符次序。對於那些不區分字母大小寫的比較來說,則需要首先將字符串轉全部換爲小寫字母或者大寫字母,通過String.toLowerCase()和String.toupperCase()做大小寫的轉換。

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,9];//擁有三個元素的數組
"o" in data//=>true:數組包含元素”0”
1 in data//=>true:數字轉換爲字符串
3 in data//=>false:沒有索引爲3的元素

instanceof 運算符

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

var d=new Date();//通過Date()構造函數來創建一個新對象
d instanceof Date;//計算結果爲true,d是由Date()創建的
d instanceof Object;//計算結果爲true,所有的對象都是0bject的實例
d instanceof Number;//計算結果爲false,d不是一個Number對象
vara=[1,2,3];//通過數組直接量的寫法創建一個數組
a instanceof Array;//計算結果爲true,a是一個數組
a instanceof Object;//計算結果爲true,所有的數組都是對象
a instanceof RegExp;//計算結果爲false,數組不是正則表達式

  需要注意的是,所有的對象都是0bject的實例。當通過instanceof判斷一個對象是否是一個類的實例的時候,這個判斷也會包含對“父類”(superclass)的檢測。如果instanceof的左操作數不是對象的話,instanceof返回false。如果右操作數不是函數,則拋出一個類型錯誤異常。

邏輯表達式

邏輯與(&&)

  一般來講,當“&&”右側的表達式具有副作用的時候(賦值、遞增、遞減和函數調用表達式)要格外小心。因爲這些帶有副作用的表達式的執行依賴於左操作數的計算結果。

邏輯或(||)

  和“&8”運算符一樣,同樣應當避免右操作數包含一些具有副作用的表達式,除非你目地明確地在右側使用帶副作用的表達式,而有可能不會計算右側的表達式。

邏輯非(!)

  “!”運算符是一元運算符。它放置在一個單獨的操作數之前。它的目的是將操作數的布爾值進行求反。
  作爲一個一元運算符,“!”具有很高的優先級,並且和操作數緊密綁定在一起。如果你希望對類似p8&q的表達式做求反操作,則需要使用圓括號:!(p&&q)。

賦值表達式

  “=”具有非常低的優先級,通常在一個較長的表達式中用到了一條賦值語句的值的時候,需要補充圓括號以保證正確的運算順序。

(a=b)==0

  賦值操作符的結合性是從右至左,也就是說,如果一個表達式中出現了多個賦值運算符,運算順序是從右到左。

帶操作的賦值運算

image

  只有在data[i++]包含具有副作用的表達式(比如函數調用和賦值操作)的時候,兩者纔不等價。比如,下面兩個表達式就不等價:

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

表達式計算

  和其他很多解釋性語言一樣,JavaScript同樣可以解釋運行由JavaScript源代碼組成的字符串,併產生一個值。JavaScript通過全局函數eval()來完成這個工作。

eval()講解

  eva1()只有一個參數。如果傳入的參數不是字符串,它直接返回這個參數。如果參數是字符串,它會把字符串當成JavaScript代碼進行編譯(parse),如果編譯失敗則拋出一個語法錯誤(SyntaxError)異常。如果編譯成功,則開始執行這段代碼,並返回字符串中的最後一個表達式或語句的值,如果最後一個表達式或語句沒有值,則最終返回undefined。如果字符串拋出一個異常,這個異常將把該調用傳遞給eval()。
  關於eva1()最重要的是,它使用了調用它的變量作用域環境。也就是說,它查找變量的值和定義新變量和函數的操作和局部作用域中的代碼完全一樣。如果一個函數定義了一個局部變量x,然後調用eval("x"),它會返回局部變量的值。如果它調用eval("x=1"),它會改變局部變量的值。如果函數調用了eval("var y=3;"),它聲明一個新的局部變量y。同樣地,一個函數可以通過如下代碼聲明一個局部函數:

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

  如果在最頂層代碼中調用eval(),當然,它會作用於全局變量和全局函數。
  需要注意的是,傳遞給eva1()的字符串必須在語法上講的通—不能通過eval()往函數中任意粘貼代碼片段,比如,eval("return;")是沒有意義的,因爲return只有在函數中才起作用,並且事實上,eva1的字符串執行時的上下文環境和調用函數的上下文環境是一樣的,這不能使其作爲函數的一部分來運行。如果字符串作爲一個單獨的腳本是有語義的(就像諸如x=0的短代碼),那麼將其傳遞給eva1()作參數是完全沒有問題的,否則,eval()將拋出語法錯誤異常。

全局eval()(瞭解)

  ECMAScript5是反對使用EvalError的,並且規範了eval()的行爲。“直接的eval”,當直接使用非限定的“eval”名稱(eval看起來像是一個保留字)來調用eval()函數時,通常稱爲“直接eval”(direct eval)。直接調用eva1()時,它總是在調用它的上下文作用域內執行。其他的間接調用則使用全局對象作爲其上下文作用域,並且無法讀、寫、定義局部變量和函數。下面有一段示例代碼:

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="1ocal";//定義局部變量
    geval("y+='changed';");//間接調用改變了全局變量的值
    return y;
    }//返回未更改的局部變量
console.1og(f(),x);//更改了局部變量:輸出"local changed global":
console.1og(g(),y);//更改了全局變量:輸出“local globalchanged”:

嚴格eval()

  ECMAScript 5嚴格模式(參照5.7.3節)對eval()函數的行爲施加了更多的限制,甚至對標識符eval的使用也施加了限制。當在嚴格模式下調用eva1()時,或者eva1()執行的代碼段以“use strict”指令開始,這裏的eva1()是私有上下文環境中的局部eval。也就是說,在嚴格模式下,eval執行的代碼段可以查詢或更改局部變量,但不能在局部作用域中定義新的變量或函數。
  此外,嚴格模式將“eval”列爲保留字,這讓eva1()更像一個運算符。不能用一個別名覆蓋eva1()函數。並且變量名、函數名、函數參數或者異常捕獲的參數都不能取名爲“eval”。

其他運算符

條件運算符(?:)

  條件運算符的操作數可以是任意類型。第一個操作數當成布爾值,如果它是真值,那麼將計算第二個操作數,並返回其計算結果。否則,如果第一個操作數是假值,那麼將計算第三個操作數,並返回其計算結果。第二個和第三個操作數總是會計算其中之一,不可能兩者同時執行。

typeof運算符

  typeof是一元運算符,放在其單個操作數的前面,操作數可以是任意類型。返回值爲表示操作數類型的一個字符串。表4-3列出了任意值在typeof運算後的返回值:
image
  儘管JavaScript中的函數是對象的一種,但typeof運算符還是將函數特殊對待,對函數做typeof運算有着特殊的返回值。在JavaScript中,函數和“可執行的對象”(callable object)有着微妙的區別。所有的函數都是可執行的(callable),但是對象也有可能是可執行的,可以像調用函數一樣調用它,但它並不是一個真正的函數。ECMAScript 5規範則擴充至所有可執行對象,包括內置對象(native object)和宿主對象(host object),所有可執行對象進行typeof運算都將返回“function”。
  大多數瀏覽器廠商也將JavaScript的原生函數對象(native function object)當成它們的宿主對象的方法來使用。但微軟卻一直將非原生可執行對象(non-native callableobject)當成其客戶端的方法來使用,在IE9之前的版本中,非原生可執行對象的typeof運算將返回“object”,儘管它們的行爲和函數非常相似。而在IE9中,這些客戶端方法是真正的內置函數對象(native function object)。

delete運算符

  delete是一元操作符,它用來刪除對象屬性或者數組元素。就像賦值、遞增、遞減運算符一樣,delete也是具有副作用的,它是用來做刪除操作的,不是用來返回一個值的。
  delete希望他的操作數是一個左值,如果它不是左值,那麼delete將不進行任何操作同時返回true。否則,delete將試圖刪除這個指定的左值。如果刪除成功,delete將返回true。
  然而並不是所有的屬性都可刪除,一些內置核心和客戶端屬性是不能刪除的,用戶通過var語句聲明的變量不能刪除。同樣,通過function語句定義的函數和函數參數也不能刪除。
  在ECMAScript 5嚴格模式中,如果delete的操作數是非法的,比如變量、函數或函數參數,delete操作將拋出一個語法錯誤(SyntaxError)異常,只有操作數是一個屬性訪問表達式的時候它纔會正常工作。在嚴格模式下,delete刪除不可配置的屬性時會拋出一個類型錯誤異常。在非嚴格模式下,這些delete操作都不會報錯,只是簡單地返回false,以表明操作數不能執行刪除操作。

var o={x:1,y:2};//定義一個變量,初始化爲對象
delete o.x;//刪除一個對象屬性,返回true
typeof o.x;//屬性不存在,返回“undefined”
delete o.x;//刪除不存在的屬性,返回true
delete o;//不能刪除通過var聲明的變量,返回false
∥在嚴格模式下,將拋出一個異常
delete 1;//參數不是一個左值,返回true
this.x=1;//給全局對象定義一個屬性,這裏沒有使用var
delete x;//試圖刪除它,在非嚴格模式下返回true
//在嚴格模式下會拋出異常,這時使用“delete this.x“來代替
x;//運行時錯誤,沒有定義x

void運算符

  void是一元運算符,它出現在操作數之前,操作數可以是任意類型。這個運算符並不是經常使用:操作數會照常計算,但忽略計算結果並返回undefined。由於void會忽略操作數的值,因此在操作數具有副作用的時候使用void來讓程序更具語義。
  這個運算符最常用在客戶端的URL—javascript:URL中,在URL中可以寫帶有副作用的表達式和運算符|,而void則讓瀏覽器不必顯示這個表達式的計算結果。例如,經常在HTML代碼中的<a>標籤裏使用void運算符:

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

逗號運算符(,)

  逗號運算符是二元運算符,它的操作數可以是任意類型。它首先計算左操作數,然後計算右操作數,最後返回右操作數的值。
  總是會計算左側的表達式,但計算結果忽略掉,也就是說,只有左側表達式具有副作用,纔會使用逗號運算符讓代碼變得更通順。逗號運算符最常用的場景是在for循環中,這個for循環通常具有多個循環變量。

//for循環中的第一個逗號是var語句的一部分
//第二個逗號是逗號運算符
//它將兩個表達式(i++和j--)放在一條(for循環中的)語句中
for(var i=0,j=10;i<j;i+,j--)
console.1og(i+j);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章