p29:
IE4 提 供 的DHTML模 型 已 經(jīng) 可 以 動(dòng) 態(tài) 更 新 網(wǎng) 頁(yè) 了( 使 用insertAdjacentHTML),而NN4仍只能調(diào)用document.write來(lái)修改頁(yè)面。
p32
ErikArvidsson擁有一個(gè)知名的個(gè)人網(wǎng)站W(wǎng)ebFX(http://webfx.eae.net/),從1997開始,
他在WebFX上公布他關(guān)于瀏覽器上開發(fā)的體驗(yàn)的文章和代碼。他可能是最早
通過JavaScript+DHTML實(shí)現(xiàn)menu、tree 以及tooltip 的人。1998年未,它就
已經(jīng)在個(gè)人網(wǎng)站上實(shí)現(xiàn)了一個(gè)著名的WebFXDynamicWebBoard:
這套界面 完整地模仿了Outlook,因而是 在RichWebClient上實(shí)現(xiàn)類
Windows界面的經(jīng)典之作.
p34
ErikArvidsson使用JavaScript原生的(native)代碼在執(zhí)行期建立框架,并將這一方法用在了一個(gè)著名的項(xiàng)目Bindows上。
p45
作為一項(xiàng)基本輸入輸出的要求,本書設(shè)定宿主環(huán)境在全局應(yīng)當(dāng)支持如下方法:
alert(sMessage) 顯示一個(gè)消息文本(字符串),并等待用戶一次響應(yīng)。調(diào)用者將忽略響應(yīng)的返回信息。
confirm(sMessage) (同alert,)顯示一個(gè)消息文本(字符串),并在交互環(huán)境中等待用戶一次響應(yīng)。響應(yīng)將返回布爾值true/false,調(diào)
用者可以據(jù)此做后結(jié)操作。
prompt(sMessage,sDefaultValue) 顯示一個(gè)消息文本(字符串),并在交互環(huán)境中等待用戶
一次 輸 入 和 響 應(yīng) 。 如 果 用 戶 取 消 響 應(yīng) , 則 返 回undefined;如果用戶確認(rèn)響應(yīng),則返回字符串,如果
該字符串能被轉(zhuǎn)換為數(shù)值,則轉(zhuǎn)換為數(shù)值返回。
write(sText,...) 輸出一個(gè)段文本,多個(gè)參數(shù)將被連接成單個(gè)字符串文本。
writeln(sText,...) (同wrrite,)輸出一段文本,多個(gè)參數(shù)將被連接成單個(gè)字
符串文本。并在文本未尾追加一個(gè)換行符(\n)。
不同的宿主來(lái)說,這些方法依賴于不同的對(duì)象層次的“頂層對(duì)象”。
例如瀏覽器宿主依賴于Window對(duì)象,而WSH宿主則依賴于WScript對(duì)象。
但在本書中,這些方法的調(diào)用將略去這個(gè)對(duì)象(實(shí)例),不使用方法的全名。因
此,至少它看起來(lái)很象是Global對(duì)象上的方法(事實(shí)上,大多數(shù)的宿主默認(rèn)“頂
層對(duì)象”不需要使用全名的約定)。例如:
//示例1:.NET Framework使用JScript 8.0,頂層對(duì)象是System
// (注:JScript.NET中的腳本需要編譯執(zhí)行)
import System.Windows.Forms;
function alert(sMessage) {
MessageBox.Show(sMessage);
}
alert('Hello, World!')
//示例2:瀏覽器環(huán)境中使用的頂層對(duì)象是window
alert('Hello, World!');
//示例3:WSH環(huán)境中使用的頂層對(duì)象是WScript, 但必須使用全名
function alert(sMessage) {
WScript.Echo(sMessage);
}
alert('Hello, World!');
通常運(yùn)行期環(huán)境其實(shí)就是由“腳本引擎(JavaScript Engines)”
決定 的 。 其 中 一 些 常 見 的 引 擎 包 括 在Windows和Internet Explorer中的
jscript.dll,以及在firefox里面的js3250.dll。
其它一些實(shí)現(xiàn)ECMAScript262-4或JavaScript2.0(JS2)的引擎有:
1.6. [XXX] 回顧
DMonkey Delphi 對(duì)JavaScript2.0有部分實(shí)現(xiàn),并擴(kuò)展了大量用于Win32環(huán)境的內(nèi)置對(duì)象。
JScript.NET .NET Framework 微軟自JScript5.6自后推出的JS2引擎。
MonoJScript 兼容.NET框架 C# MonoProject的一部分。
DotGNUJScript 兼容.NET框架 C# DotGNUProject的一部分。
p49
程序語(yǔ)言中“聲明”的意義(而不是定義):所謂聲明,即是約定變量的生存周期和邏輯的作用域。
由于這里的“聲明”已經(jīng)概含了邏輯與數(shù)據(jù)(這相當(dāng)于“程序”的全部),
因此整個(gè)編程的過程,其實(shí)被解釋成了“說明邏輯和數(shù)據(jù)”的過程:
純粹陳述“值”的過程,被稱為變量和類型聲明;
純粹陳述“邏輯”的過程,被稱為語(yǔ)句(含流程控制子句);
表達(dá)式首先是與“值”相關(guān)的,但因?yàn)榇嬖谶\(yùn)算的先后順序,因此它也有與“邏輯”相關(guān)的含義。
在JavaScript中,邏輯語(yǔ)句是有值的,因此它也是值相關(guān)的。這一點(diǎn)與其它多數(shù)語(yǔ)言都不一樣。
陳述“值與(算法的)邏輯”的關(guān)系的過程,被稱為表達(dá)式。
識(shí)別語(yǔ)法錯(cuò)誤與運(yùn)行錯(cuò)誤
一般來(lái)說,JavaScript引擎會(huì)在代碼裝入時(shí)先進(jìn)行語(yǔ)法分析,如果語(yǔ)法分
析通不過,整個(gè)腳本代碼塊都不執(zhí)行;當(dāng)語(yǔ)法分析通過時(shí),才會(huì)執(zhí)行這段腳本
代碼。若在執(zhí)行過程中出錯(cuò),那么在同一代碼上下文中、出錯(cuò)點(diǎn)之后的代碼將
不再執(zhí)行。
不同引擎處理這兩類錯(cuò)誤的提示的策略并不相同。
要在不同的腳本引擎中簡(jiǎn)單地區(qū)別二種錯(cuò)誤,較為通行的方法是在代碼片斷的最
前面加上一行輸出,例如使用alert()來(lái)顯示一個(gè)信息
在該行之前,則是語(yǔ)法分析期錯(cuò)。如果在該行之后,則是執(zhí)行期錯(cuò)。例如:
var head ='alert("loaded.");';
讀者可以簡(jiǎn)單地認(rèn)為:所謂全局變量是指在函數(shù)外聲明的變量,局部變量則是在函數(shù)或子函數(shù)內(nèi)聲明的變量。
JavaScript識(shí)別6種數(shù)據(jù)類型,并在運(yùn)算過程中自動(dòng)處理語(yǔ)言類型的轉(zhuǎn)換。
這些類型包括:
類型 含義 說明
undefined 未定義 未聲明的變量,或聲明過但未賦值的變量的值,會(huì)是undefined;可以顯式或隱式地給一個(gè)變量賦值為undefined。
number 數(shù)值 除賦值操作之外,只有數(shù)值與數(shù)值的運(yùn)算結(jié)果是數(shù)值;一些函數(shù)/方法的運(yùn)回值是數(shù)值。
string 字符串 不能直接讀取或修改字符串中的單一字符。
boolean 布爾值 true/false
function 函數(shù) JavaScript中的函數(shù)存在多重含義。(1)
object 對(duì)象 基于原型繼承的面向?qū)ο蟆?2)
任何一個(gè)變量或值的類型都可以(而且應(yīng)當(dāng)首先)使用typeof 運(yùn)算來(lái)得到。
typeof 是JavaScript 內(nèi)部保留的一個(gè)關(guān)鍵字,與其它語(yǔ)言不一樣的是,typeof
關(guān)鍵字可以象函數(shù)調(diào)用一樣,在后面跟上一對(duì)括弧。例如:
//示例1:取變量的類型
var str ='this is atest.';
alert(typeof str);
// or
// alert(typeof(str));
//示例2:對(duì)直接量取類型值
alert(typeof 'test!');
但這個(gè)typeof()中的括弧,只是產(chǎn)生了一種“使typeof看起來(lái)象一個(gè)函數(shù)”
的假象。關(guān)于這個(gè)假象的由來(lái),我隨后會(huì)在“1.6運(yùn)算符的二義性”再講——現(xiàn)
在,你只需要知道,它的確可以這樣使用就足夠了。
typeof 運(yùn)算總是以字符串形式返回上述6種類型值之一。
變量不但有數(shù)據(jù)類型之別,而且還有值類型與引用類型之別——這種分類
方式主要約定了變量的使用方法。在JavaScript的六種類型中:
數(shù)據(jù)類型 值/引用類型 備注
undefined 值類型 無(wú)值。
number 值類型
boolean 值類型
string 值類型 ???
function 引用類型
object 引用類型
在JavaScript中 ,“全等(===)運(yùn)算符”用來(lái)對(duì)值類型/引用類型的實(shí)際數(shù)
據(jù)進(jìn)行比較和檢查。按照約定:
一般表達(dá)式運(yùn)算的結(jié)果總是值(或undefined);
函數(shù)/方法調(diào)用的結(jié)果可以返回值或者引用(或undefined);
值與引用、值與值之間即使等值(==),也不一定全等(===);
兩個(gè)引用之間如果等值(==),則一定全等(===)。
至少表面上來(lái)看,一個(gè)值應(yīng)該與其自身“等值/全等”。但事實(shí)上,在
JavaScript中這存在一個(gè)例外:一個(gè)NaN值,與自身并不等值,也不全等。
顯式聲明,一般是指用關(guān)鍵字var進(jìn)行的聲明。
var str ='test';
for (var nin Object)
顯式聲明的最后一種情況是函數(shù)中的函數(shù)名聲明。例如:
//聲明函數(shù)foo
function foo() {}
隱式聲明則不用關(guān)鍵字var聲明。例如:
//當(dāng)aVar未被聲明時(shí),以下語(yǔ)句將隱式地聲明它
aVar =100;
解釋器總是將顯式聲明理解為“變量聲明”。而對(duì)隱式聲明則不一定:
如果變量未被聲明,則該語(yǔ)句是變量聲明(并立即賦給值)。
如果變量已經(jīng)聲明過,則該語(yǔ)句是變量賦值語(yǔ)句。
我的理解賦值語(yǔ)句的主要作用是賦值,對(duì)不存在的變量有聲明的副作用.
直接量類似于匯編語(yǔ)言中的立即值,是無(wú)需聲明就可以立即使用的常值。
接量聲明的數(shù)據(jù)類型和對(duì)象:
類型 直接量聲明 包裝對(duì)象 說明
undefined v = undefined 無(wú)
string v = '...' String 參見:字符串直接量、轉(zhuǎn)義符
number v = 1234 Number 參見:數(shù)值聲明
boolean v = true v = false Boolean
function v = function() { ... } Function 參見:函數(shù)直接量聲明
object v = { ... } v = null Object
array v = [ ... ] Array 參見:正則表達(dá)式的常見問題
regexp v = / ... /... RegExp 參見:數(shù)組的常見問題
ECMA標(biāo)準(zhǔn)統(tǒng)一要求JavaScript中的字符串必須是Unicode字符序列。
轉(zhuǎn)義符主要用于在字符串中包含控制字符,以及當(dāng)前操作系統(tǒng)語(yǔ)言(以及
字符集)不能直接輸入的字符;也可以用于字符串中嵌套引號(hào)(不過你總是可
以在單引號(hào)聲明的字符串中直接使用雙引號(hào),或反過來(lái)在雙引號(hào)中使用單引
號(hào))。轉(zhuǎn)義符總是用一個(gè)反斜線字符“\”引導(dǎo).
當(dāng)反斜線字符“\”位于一行的末尾(其后立即是代碼文本中的換行)時(shí),也用于表示連續(xù)的字符串聲明。
這在聲明大段文本塊時(shí)很有用。
不能在這種表示的文本行末使用注釋。
在JavaScript中,NUL字符是允許存在的。
// str= String.fromCharCode(0, 0, 0, 0,0);
str ='\0\0\0\0\0';
可以用一對(duì)不包含任意字符的單引號(hào)與雙引號(hào)來(lái)表示一個(gè)空字符串(NullString),其長(zhǎng)度值總是為0。
數(shù)值直接量
如果以0x或0X開始,則表明是一個(gè)十六進(jìn)制數(shù)值;
如果僅以0開始,則表明是一個(gè)八進(jìn)制數(shù)值;
其它情況下,表明是一個(gè)十進(jìn)制整數(shù)或浮點(diǎn)數(shù)。
字符串 '056'被隱式轉(zhuǎn)換時(shí),0被忽略,當(dāng)作10進(jìn)制.
函數(shù)直接聲明的語(yǔ)法是:
function functionName()
// ...
}
當(dāng)functionName 是一個(gè)有效標(biāo)識(shí)符時(shí),表示聲明一個(gè)具名函數(shù);如果省略 ,
則表示聲明一個(gè)匿名(anonymous)函數(shù)。functionName 后使用一對(duì)不能省略的
“()”來(lái)表示形式參數(shù)表。
p60
正則表達(dá)式
JavaScript的數(shù)組本質(zhì)是一維的。
高維不過是“數(shù)組的數(shù)組”這種嵌套特性。
換成最直接的表達(dá)式方式,我們并不能用類似于:arr =new Array(10, 10);
這樣的方式來(lái)得到一個(gè)每維10個(gè)分量的二維數(shù)組(JavaScript中上述語(yǔ)法會(huì)得到包含兩個(gè)元素的一個(gè)一維數(shù)組),
但可以用:
arr =[[1,2],[3,4]];
arr[1,2,3]并不返回某個(gè)多維數(shù)組指定為“1,2,3”的下標(biāo)元素,而只是返回
arr[3]這個(gè)元素。因?yàn)镴avaScript將“1,2,3”解釋為連續(xù)運(yùn)算,并返回最后一個(gè)
表達(dá)式的值“3”(表達(dá)式運(yùn)算的細(xì)節(jié)可以參考“1.3JavaScript的語(yǔ)法:表達(dá)式
運(yùn)算”和“1.6.5逗號(hào)“,”的二義性”)。如果你試圖訪問交錯(cuò)數(shù)組的某個(gè)下標(biāo)
分量,應(yīng)該用類似如下的語(yǔ)法:arr[1][2][3]
我的理解:除了值類型的變量,其實(shí)就是關(guān)聯(lián)數(shù)組(或者說map).
使用數(shù)組下標(biāo)時(shí)也可以使用數(shù)值字符串,但這時(shí)在語(yǔ)義上卻并不是對(duì)索引
數(shù)組的下標(biāo)存取,而是對(duì)關(guān)聯(lián)數(shù)組中的“名-值”存取。這是因?yàn)镴avaScript
中的一個(gè)數(shù)組,既是一個(gè)以數(shù)組下標(biāo)順序存取的索引數(shù)組,也是一個(gè)可存取屬
性的關(guān)聯(lián)數(shù)組。為了減少二者之間的差異,在將數(shù)組視為普通對(duì)象并用for..in
語(yǔ)句列舉時(shí),可以列舉到那些數(shù)值的索引下標(biāo)。
.JavaScript的語(yǔ)法:表達(dá)式運(yùn)算
在JavaScript中,運(yùn)算符大多數(shù)是特殊符號(hào),但也有少量單詞。例如我們
在前面用來(lái)取數(shù)據(jù)類型的typeof,其實(shí)就是一個(gè)運(yùn)算符。相較與其它語(yǔ)言,
JavaScript在運(yùn)算符上還有一種特殊性:許多語(yǔ)句/語(yǔ)法分隔符也同時(shí)是運(yùn)算
符——它們的含義當(dāng)然是不同的,我的意思只是強(qiáng)調(diào)它們使用了相同的符號(hào)。
下表列舉這些單詞形式的運(yùn)算符,你應(yīng)當(dāng)避免把它們誤解成語(yǔ)句:
單詞形式的運(yùn)算符
運(yùn)算符/符號(hào) 運(yùn)算符含義 備注
typeof 取變量或值的類型的字符串 參見:1.3.7 特殊作用的運(yùn)算符
void 運(yùn)算表達(dá)式并忽略值 避免表達(dá)式返回值 使表達(dá)式總是返回值undefined
new 創(chuàng)建指定類的對(duì)象實(shí)例與面向?qū)ο笙嚓P(guān)。new constructor[(arguments)] 創(chuàng)建指定類的對(duì)象實(shí)例 參見:1.5 面向?qū)ο缶幊痰恼Z(yǔ)法概要
in 檢查對(duì)象屬性 in propertyNamein object 檢查對(duì)象屬性
instanceof 檢查變量是否是指定類的實(shí)例
delete 刪除實(shí)例屬性
typeof是運(yùn)算符,而不是語(yǔ)句的語(yǔ)法關(guān)鍵字,typeof 運(yùn)算符并不訪問變量的值,而是取值的類型信息。
“void”與“()”等等這些運(yùn)算符的運(yùn)算對(duì)象,是“表達(dá)式”而非變量/值運(yùn)算元。
運(yùn)算符的后面不能是語(yǔ)句(很顯然,這是語(yǔ)法規(guī)則)。因此,下面的代碼是
不合法的:
//用{}表示的復(fù)合語(yǔ)句不能作為void的運(yùn)算對(duì)象
void {
// ...
}
. object.Identifier 存取對(duì)象成員(屬性、方法)
[ ] object[string_expression]
for ... in for([var ]variable in <object| Array>)statements;列舉對(duì)象成員
with with (object)statements; 設(shè)定語(yǔ)句默認(rèn)對(duì)象
: 根據(jù)條件執(zhí)行兩個(gè)表達(dá)式中的一個(gè) 也稱“三元(三目)條件運(yùn)算符”
() 表達(dá)式分組和調(diào)整運(yùn)算次序 也稱“優(yōu)先級(jí)運(yùn)算符”,
, 表達(dá)式順序地連續(xù)執(zhí)行 也稱“多重求值”或“逗號(hào)運(yùn)算符”
括號(hào)“()”作為運(yùn)算符時(shí),是指強(qiáng)制運(yùn)算并求值
表達(dá)式由運(yùn)算符與運(yùn)算元構(gòu)成。運(yùn)算元除了包括變量,還包括函數(shù)(或方法)的返回值,此外也包括直接量。
JavaScript中可以存在沒有運(yùn)算符的表達(dá)式,這稱為“單值表達(dá)式”。單值表達(dá)式有 值的含義,表達(dá)式 的結(jié)果即是該值 。
表達(dá)式總是有結(jié)果值——一值類型或引用類型的數(shù)據(jù),或者undefined。單值表達(dá)式的結(jié)果是值本身,其
它表達(dá)式的結(jié)果是運(yùn)算的結(jié)果值(也因此必然有運(yùn)算符)。
表達(dá)式的本質(zhì)目的,還是在于求值。
位運(yùn)算操作中,JavaScript強(qiáng)制運(yùn)算目標(biāo)為一個(gè)有符號(hào)的32位整型數(shù)
如果目標(biāo)是非數(shù)值,那么會(huì)被強(qiáng)制轉(zhuǎn)換為數(shù)值;如果目標(biāo)是浮點(diǎn)數(shù),那么會(huì)被取整;否則,將目標(biāo)識(shí)別有符號(hào)整型數(shù)。
一般語(yǔ)言中,邏輯運(yùn)算與布爾運(yùn)算是等義的,其運(yùn)算元與目標(biāo)類型都是布
爾值(true/false)。 JavaScript當(dāng)然支持這種純布爾運(yùn)算,上一小節(jié)已經(jīng)對(duì)此
有過敘述。不但如此,JavaScript還包括另外一種邏輯運(yùn)算,它的表達(dá)式結(jié)果
類型是不確定的。
只有“邏輯或(||)”和“邏輯與(&&)”兩種運(yùn)算能做這樣的事。它們的使
用方法與運(yùn)算邏輯都與基本的布爾運(yùn)算一致,例如:
var str ='hello';
var obj ={};
x= str ||obj;
y= str &&obj;
這種運(yùn)算的特別之處在于:運(yùn)算符“||”與“&&”既不改變運(yùn)算元的數(shù)據(jù)
類型,也不強(qiáng)制運(yùn)算結(jié)果的數(shù)據(jù)類型。除此之外,還有以下兩條特性:
運(yùn)算符會(huì)將運(yùn)算元理解為布爾值,以進(jìn)行布爾運(yùn)算;
運(yùn)算過程(與普通布爾運(yùn)算一樣)是支持布爾短路的。
連續(xù)的邏輯運(yùn)算也可以用來(lái)替代語(yǔ)句。
類型 運(yùn)算規(guī)則
兩個(gè)值類型進(jìn)行比較 轉(zhuǎn)換成相同數(shù)據(jù)類型的值進(jìn)行“數(shù)據(jù)等值”比較。
值類型與引用類型比較 將引用 類型 的 數(shù)據(jù) 轉(zhuǎn)換 為 與值 類型 數(shù) 據(jù)相 同的 數(shù)據(jù),再進(jìn)行“數(shù)據(jù)等值”比較。
兩個(gè)引用類型比較 比較引用(的地址)
檢測(cè)中“相同”的問題。它遵循如下的運(yùn)算規(guī)則:
類型 運(yùn)算規(guī)則
兩個(gè)值類型進(jìn)行比較 轉(zhuǎn)換成相同數(shù)據(jù)類型的值進(jìn)行“數(shù)據(jù)等值”比較。
值類型與引用類型比較 將引用 類型 的 數(shù)據(jù) 轉(zhuǎn)換 為 與值 類型 數(shù) 據(jù)相 同的 數(shù)
據(jù),再進(jìn)行“數(shù)據(jù)等值”比較。
兩個(gè)引用類型比較 比較引用(的地址)。
類型 運(yùn)算規(guī)則
兩個(gè)值類型進(jìn)行比較 如同數(shù)據(jù)類型不同,則必然“不相同”;
數(shù)據(jù)類型相同時(shí),進(jìn)行“數(shù)值等值”比較。
值類型與引用類型比較 必然“不相同”。
所以“相同與否”的檢測(cè),僅對(duì)兩個(gè)相同數(shù)據(jù)類型、值類型的數(shù)據(jù)有意義
(其它情況下的比較值都是“不同”),這時(shí)所比較的方法,也是完全的“數(shù)據(jù)
等值”的比較。
換而言之,下面的代碼與上一個(gè)示例的所發(fā)生的運(yùn)算,以及運(yùn)
算效果是完全一致的:
var str1 ='abc' + 'def';
var str2 ='abcd' +'ef';
//運(yùn)算過程和結(jié)果完全等同于上一個(gè)示例
alert(str1 === str2);
通過上面的示例,我們成功地否定了一個(gè)慣例性的說法:“===”運(yùn)算是比
較引用的,而“==”是比較值。因?yàn)樵诶?中,我們看到str1與str2是兩個(gè)不
同引用的變量,但它們是完全相等的。
在對(duì)兩個(gè)引用類型的比較運(yùn)算過程中,“==”與“===”并沒有任何的不同。
引用類型的等值比較,將直接“比較引用(的地址)”。
但實(shí)際的意義不過是說:如果不是同一個(gè)變量或其引用,則兩個(gè)變量不相等,
也不相同。
我們看到,obj1與obj2是類型相同的,且值都是通過同一個(gè)直接量來(lái)創(chuàng)
建的,但是,由于String()對(duì)象是引用類型,所以它們既不“相等”,也不“相
同”。
1.3.4.2. 序列檢測(cè)
從數(shù)字概念來(lái)說,實(shí)數(shù)數(shù)軸上可比較的數(shù)字是無(wú)限的(正無(wú)窮到負(fù)無(wú)窮)。
該數(shù)軸上的有序類型,只是該無(wú)限區(qū)間上的一些點(diǎn)。但對(duì)于具體的語(yǔ)言來(lái)說,
由于數(shù)值的表達(dá)范圍有限,所以數(shù)值的比較也是有限的。
在JavaScript中 ,“字符串”是有序類型的一種特例。
JavaScript不存在“字符”類型,但它的字符串的每一個(gè)字符,都
被作為單一字符來(lái)參與序列檢測(cè)。
類型 運(yùn)算規(guī)則
兩個(gè)值類型進(jìn)行比較 直接比較數(shù)據(jù)在序列中的大小。
值類型與引用類型比較 將引用類型的數(shù)據(jù)轉(zhuǎn)換為與值類型數(shù)據(jù)相同的數(shù)據(jù),再進(jìn)
行“序列大小”比較。
兩個(gè)引用類型比較 無(wú)意義,總是返回false。(*注1)
*注1:其實(shí),對(duì)引用類型進(jìn)行序列檢測(cè)運(yùn)算仍然是可行的。但這與valueOf()運(yùn)算的效果有關(guān)。關(guān)
于這一點(diǎn),我們將在“有關(guān)章節(jié)”中詳細(xì)講述。
//例3:兩個(gè)對(duì)象(引用類型)比較時(shí)總是返回fal
alert(o1 >o2 ||o1 <o2 || o1== o2);
//例2:當(dāng)字符串與其它類型值比較時(shí),將字符串轉(zhuǎn)換為數(shù)值比較.所以顯示為true.
alert( s3 >i );
//例3:在將字符串轉(zhuǎn)換為數(shù)值時(shí)得到NaN,所以下面的三個(gè)比較都為false.
// (注:變量b中的布爾值true,轉(zhuǎn)換為數(shù)值1參與運(yùn)算)
alert( s1 >b ||s1 <b ||s1 == b);
//例4:兩個(gè)NaN的比較. NaN不等值也不大于或小于自身,所以下面的三個(gè)比較都為false.
alert( s1>NaN || s1< NaN ||s1 ==NaN );
JavaScript中,賦值是一個(gè)運(yùn)算,而不是一個(gè)語(yǔ)句(如何將它變成語(yǔ)句,是下一小節(jié)的話題)。
賦值的效果是修改存儲(chǔ)單元中的值”。而這,其實(shí)就是“賦值運(yùn)算”的本質(zhì)。
所以賦值運(yùn)算在值類型來(lái)講是復(fù)制數(shù)據(jù),而對(duì)于引用類型來(lái)講,則只是復(fù)制一個(gè)地址 。
這里存在一個(gè)特例:值類型的字符串是一個(gè)大的、不確定長(zhǎng)度的連續(xù)數(shù)據(jù)
塊,這導(dǎo)致復(fù)制數(shù)據(jù)的開銷很大,所以JavaScript中將字符串的賦值也變成了
復(fù)制(連續(xù)數(shù)據(jù)塊起始處的)地址。由此引入了三條字符串處理的限制:
不能直接修改字符串中的字符;
字符串連接運(yùn)算必然導(dǎo)致寫復(fù)制,這將產(chǎn)生新的字符串;
不能改變字符串的長(zhǎng)短,例如修改length 屬性是無(wú)意義的。
“函數(shù)”,包括普通的、類型值(即typeof 值)為“function”
的函數(shù),也包括類型值為“object”的、創(chuàng)建自Function類的函數(shù)(對(duì)象實(shí)例)。
語(yǔ)句表明執(zhí)行過程中的流程、限定與約定。可以是單行語(yǔ)句,或者由一對(duì)大括號(hào)“{}”括起來(lái)的復(fù)合語(yǔ)句——
在語(yǔ)法描述中,復(fù)合語(yǔ)句可以整體作為一個(gè)單行語(yǔ)句處理。
下面兩個(gè)原則,有助于你了解JavaScript的“語(yǔ)句”的定義:
語(yǔ)句由語(yǔ)法分隔符“;(分號(hào))”來(lái)分隔(注意,它不是運(yùn)算符)。
(除空語(yǔ)句和語(yǔ)句中的控制子句之外,)語(yǔ)句存在返回值,該值由執(zhí)行中的最
后一個(gè)子句/表達(dá)式的值決定。
當(dāng)語(yǔ)句位于以下幾種情況之一時(shí),也可以省略分號(hào)。
1、一個(gè)文本行或整個(gè)文本文件的未尾,或
2、在語(yǔ)法分隔符之前(如復(fù)合語(yǔ)句的大括號(hào)“}”), 或
3、在復(fù)合語(yǔ)句的大括號(hào)“}”之后。
賦值語(yǔ)句:使用“等號(hào)(=)”賦值運(yùn)算符
變量聲明語(yǔ)句:使用var關(guān)鍵字開始一個(gè)變量聲明
標(biāo)簽聲明語(yǔ)句:使用“identifier: statement”的語(yǔ)句開始一個(gè)標(biāo)簽聲明
表達(dá)式語(yǔ)句
“表達(dá)式”的約定是:由運(yùn)算數(shù)和操作構(gòu)成,并運(yùn)算產(chǎn)生結(jié)構(gòu)的語(yǔ)法結(jié)構(gòu)。
那么很顯然,下面的代碼就是一個(gè)表達(dá)式:
1+2+3
接下來(lái),另外的一項(xiàng)約定是:程序是由語(yǔ)句構(gòu)成的,語(yǔ)句是則由“;(分號(hào))”分隔的句子或命令。
那么如果在表達(dá)式后面加上一個(gè)“;”分隔符,JavaScript又如何理解呢?
1+2+3;
這就被稱為“表達(dá)式語(yǔ)句”。它表明“只有表達(dá)式,而沒有其它語(yǔ)法元素的語(yǔ)句”。
表達(dá)式語(yǔ)句
變量賦值語(yǔ)句 variable = value;
函數(shù)調(diào)用語(yǔ)句 foo();
屬性賦值語(yǔ)句 object.property = value;
方法調(diào)用語(yǔ)句 object.method();
表達(dá)式語(yǔ)句”的值,就是該表達(dá)式運(yùn)算的結(jié)果——即使不返回值(原格說來(lái)
并沒有“不返回值”,而是返回undefined值),也可以參與后續(xù)運(yùn)算。
可以用eval()函數(shù)來(lái)執(zhí)行一行字符串(如果它是表達(dá)式)
一個(gè)分號(hào)“;”在JavaScript中,它就被理解為空語(yǔ)句。我們前面說到過,語(yǔ)句也是有值
的,可以用來(lái)運(yùn)算。因此,空語(yǔ)句就返回值undefined——再次強(qiáng)調(diào)一下:完
全沒值的語(yǔ)句或表達(dá)式,在JavaScript中并不存在。
空語(yǔ)句的另一種應(yīng)用情況是寫空循環(huán),或者空分支。
eval()函數(shù)總是執(zhí)行語(yǔ)句。即使傳入下面這樣的代碼:
eval('1+2+3') //是試圖“運(yùn)算表達(dá)式”并返回表達(dá)式結(jié)果嗎?
在語(yǔ)法邏輯上,它也是被當(dāng)成語(yǔ)句處理的——因?yàn)檫€存在另一條規(guī)則:換行符
和文本結(jié)束符的前面可以沒有分號(hào)“;”。所以,上面這行代碼仍然是執(zhí)行語(yǔ)句
并返回語(yǔ)句的結(jié)果值。
變量聲明語(yǔ)句中的var是語(yǔ)句語(yǔ)法符號(hào),而不是運(yùn)算符。下面的代碼的效
果,是在語(yǔ)法解釋期和執(zhí)行期分兩次來(lái)實(shí)現(xiàn)的。
vari =100;
JavaScript中可以使用顯式聲明和隱式聲明。語(yǔ)法區(qū)別在于前者使用var
關(guān)鍵字,而后者不用;語(yǔ)義上的區(qū)別在于前者在函數(shù)中使用時(shí),表示聲明局部
變量,而后者聲明的結(jié)果總是全局變量。
變量在聲明后賦值前默認(rèn)值為undefined。
alert(str);//undefined
var str ='abcde';
“變量聲明語(yǔ)句”是一個(gè)有關(guān)鍵字的語(yǔ)句,并不是表達(dá)式語(yǔ)句;
“賦值語(yǔ)句”是一個(gè)表達(dá)式語(yǔ)句。
// 1.在for循環(huán)中聲明變量
for (var i=0; i<10; i++) {
// ...
}
// 2.在for ..in循環(huán)中聲明變量
for (var prop inObject.prototype) {
// ...
}
這兩種情況下它相當(dāng)于一個(gè)語(yǔ)句的子句。由于JavaScript的變量作用域只
能達(dá)到函數(shù)一級(jí)(而非語(yǔ)句塊一級(jí)),因此這里聲明變量,與在for 或for..in語(yǔ)
句之外聲明變量沒有什么區(qū)別。
強(qiáng)制運(yùn)算符()
() 表達(dá)式分組和調(diào)整運(yùn)算次序,
來(lái)改變執(zhí)行(優(yōu)先級(jí)別的)順序
把()內(nèi)的當(dāng)作一個(gè)整體并進(jìn)行求值。
除了for ,一個(gè)獨(dú)立的() 內(nèi)只能有運(yùn)算不能有變量聲明
JavaScript中,函數(shù)本身是一個(gè)變量/值,因此函數(shù)調(diào)用其實(shí)是一個(gè)表達(dá)式。
函數(shù)調(diào)用語(yǔ)句,它也是一個(gè)表達(dá)式語(yǔ)句:
//示例1.具名函數(shù)直接調(diào)用
function foo() {
}
foo();
//示例2.匿名函數(shù)通過引用來(lái)調(diào)用
fooRef = function() {
}
fooRef();
//示例3.沒有引用的匿名函數(shù)的調(diào)用方法(1)
(function() {
// ...
}());
//示例4.沒有引用的匿名函數(shù)的調(diào)用方法(2)
(function() {
// ...
})();
例3中是用強(qiáng)制運(yùn)算符使函數(shù)調(diào)用運(yùn)算得以執(zhí)行,例4中則用強(qiáng)制運(yùn)算符運(yùn)算
“函數(shù)直接量聲明”這個(gè)表達(dá)式,并返會(huì)一個(gè)函數(shù)自身(或引用),然后通過函
數(shù)調(diào)用運(yùn)算符“()”來(lái)操作函數(shù)引用。
換而言之,“函數(shù)調(diào)用運(yùn)算符()”在例3中的作用于匿名函數(shù)本身,在例4
中卻作于用于一個(gè)運(yùn)算的結(jié)果值。
//示例5.沒有引用的匿名函數(shù)的調(diào)用方法(3)
void function() {
// ...
}();
例5,則用于“調(diào)用函數(shù)并忽略返回值”。運(yùn)算符void用于使其
后的函數(shù)表達(dá)式執(zhí)行運(yùn)算。
那么如果不使用void與“()”這兩個(gè)運(yùn)算符,而直接使用下面的代碼,能
否使函數(shù)表達(dá)式語(yǔ)句得到執(zhí)行呢?
//示例6.直接使用函數(shù)調(diào)用運(yùn)算符"()"調(diào)用
function() {
// ...
}()
//示例7.使用語(yǔ)句結(jié)束符";"來(lái)執(zhí)行語(yǔ)句
function() {
// ...
}();
示例6、7看起來(lái)是正確的——起碼用我們以前提到的所有知識(shí)來(lái)看,這
兩個(gè)示例中的代碼均能被理解。但是事實(shí)上它們都不可執(zhí)行。究其原因,則是
因?yàn)樗鼈儫o(wú)法通過腳本引擎的語(yǔ)法檢測(cè)——在語(yǔ)法檢測(cè)階段,腳本引擎會(huì)認(rèn)為
下面的代碼:
function() {
}
//或
function foo() {
}
是函數(shù)聲明——因此事實(shí)上這里使用具名函數(shù)和匿名函數(shù)都是通不過語(yǔ)法檢
測(cè)的,而后面的討論也因此對(duì)這二者都有效——這就會(huì)使得在示例6和示例7
的代碼中,函數(shù)后面后面的一對(duì)括號(hào)沒有語(yǔ)法意義。這樣一來(lái),它們的代碼無(wú)
疑被語(yǔ)法解析成了:
//示意:對(duì)示例6的語(yǔ)法解釋
function() {
// ...
};
();
“function() {}”作為完整的語(yǔ)法結(jié)構(gòu)被解釋,因此相當(dāng)于其后已經(jīng)
存在語(yǔ)句結(jié)束符。而“();”則被獨(dú)立成一行進(jìn)行語(yǔ)法解釋,顯示這是錯(cuò)誤的語(yǔ)
法,因而會(huì)出現(xiàn)下面的錯(cuò)誤提示:
因此這個(gè)“語(yǔ)法錯(cuò)誤”事實(shí)上是針對(duì)于“();”,而不是針對(duì)于前面的函數(shù)
聲明的。為了證明這一點(diǎn),我們改寫代碼如下:
//改寫示例6的代碼以通過語(yǔ)法解釋
function() {
// ...
}(1,2)
這樣一來(lái)你會(huì)發(fā)現(xiàn)語(yǔ)法檢測(cè)通過了。因?yàn)檎Z(yǔ)句被語(yǔ)法解釋成了:
function() {
// ...
};
(1,2);
其中的“1”和“2”被解釋成了兩個(gè)單值表達(dá)式——當(dāng)然也可以是“(1)”這樣
的一個(gè)單值表達(dá)式。
如果你真的想在聲明的時(shí)候執(zhí)行一下該函數(shù),那么請(qǐng)參考前本小節(jié)開始的示例
3、4、5,用一個(gè)void或括號(hào)“()”運(yùn)算將函數(shù)聲明變成“(直接量的)單值表
達(dá)式”:
//示例:聲明時(shí)立即執(zhí)行該函數(shù)(也可以用于匿名函數(shù)聲明)
void function foo() {
// ...
}(1,2);
JavaScript也支持一種用于對(duì)象成員列舉的循環(huán)
語(yǔ)句for..in:
for ([var ]variable in <object | Array>)
statements;
需要注意的是,從語(yǔ)法上來(lái)看它能夠處理數(shù)組中的元素,但其實(shí)這不過是
JavaScript將數(shù)組作為對(duì)象處理時(shí)的一種表面現(xiàn)象。
在JavaScript中,標(biāo)簽只能被break語(yǔ)句與continue語(yǔ)句所引用。前者表
明停止語(yǔ)句執(zhí)行,并跳轉(zhuǎn)到break的所指示的范圍之外;后者表明停止當(dāng)前循
環(huán),并跳轉(zhuǎn)到break所指示的范圍的開始。
缺省情況下,break子句作用于循環(huán)語(yǔ)句的最內(nèi)層,或者整個(gè)switch語(yǔ)句,
因此它不必特別地指定中斷語(yǔ)句的范圍。但break子句也具有一種擴(kuò)展的語(yǔ)法,
以指示它所作用的范圍。該范圍用聲明過的標(biāo)簽來(lái)表示。例如:
break my_label;
這使得break子句不但可以使用在循環(huán)與條件分支內(nèi)部,也可使用在標(biāo)簽
化語(yǔ)句(labeled statement)的內(nèi)部。如下例:
/**
*顯示輸入字符串的末10個(gè)字符
*/
var str =prompt('please input a string','12345678910');
my_label:{
if (str &&str.length < 10) {
break my_label;
}
str =str.substr(str.length-10);
}
// other process...
alert(str);
這種情況下,break子句后的這個(gè)my_label不能省略——盡管break位于
my_label所表示的語(yǔ)句范圍之內(nèi)。因此,以下三種用法都將觸發(fā)語(yǔ)法編譯期的
觸發(fā)腳本異常:
7 my_label:{
8 if (str &&str.length < 10) {
99 //錯(cuò)誤一:在標(biāo)簽化語(yǔ)句中用使用break而不帶lable
10 break;
11 }
12 str =str.substr(str.length-10);
13 }
14
15 if (true) {
16 //錯(cuò)誤二:在標(biāo)簽化語(yǔ)句的范圍之外引用該標(biāo)簽
17 break my_label;
18 }
19 else {
20 //錯(cuò)誤三:在有效的范圍(標(biāo)簽化語(yǔ)句、循環(huán)和switch分支)之外使用break;
21 break;
22 }
錯(cuò)誤一與錯(cuò)誤三的異常信息是一樣的。由于語(yǔ)法上我們是可
以在“循環(huán)之外使用break子句的”,因此事實(shí)上這個(gè)提示信息所指的是“un-labeled break”——即在腳本引擎內(nèi)部,認(rèn)為“不帶標(biāo)簽的break”僅能用于循
環(huán)(和switch分支)的內(nèi)部。
當(dāng)return子句沒有指定返回值時(shí),該函數(shù)返回undefined。
當(dāng)使用void運(yùn)算調(diào)用函數(shù)時(shí),return子句指定的返回值將被忽略。
觸發(fā)異常:使用throw 語(yǔ)句可以在任意位置觸發(fā)異常,或由引擎在執(zhí)行過程中
內(nèi)部地觸發(fā)異常;
捕獲異常:使用try...catch 語(yǔ)句可以(在形式上表明)捕獲一個(gè)代碼塊中可能
發(fā)生的異常;
忽略異常:使用try...finally 語(yǔ)句可以忽略指定代碼塊中發(fā)生的異常,并保證
finally 語(yǔ)句塊中的代碼總是被執(zhí)行。
在上述語(yǔ)法中,finally{...}塊是可選的,但如果存在同一級(jí)別的catch(){...}
塊,則它finally 塊必須位于catch塊之后。在執(zhí)行上,catch塊是先于finally
塊的。但如果在finally 執(zhí)行中存在一個(gè)未被處理的異?!缭趂inally 之
前沒有catch處理塊,或在catch、finally 塊處理中又觸發(fā)了異常,那么這個(gè)異
常會(huì)被拋出到更外一層的try...catch/finally 中處理。
finally{...}語(yǔ)句塊的一個(gè)重要之處在于它“總是在try/catch 塊退出之前被
執(zhí)行”。這一過程中常常被忽略的情況包括
①
:
//在(函數(shù)內(nèi)部的)try塊中使用return時(shí),finally塊中的代碼仍是在return子語(yǔ)之前執(zhí)行的
try {
// ...
return;
}
finally {
...
}
//在(標(biāo)簽化語(yǔ)句的)try塊中使用break時(shí), finally塊中的代碼仍是在break子句之前執(zhí)行的
aLable:
try {
// ...
break aLabel;
}
finally {
...
}
①
類似情況還包括continue子句。在《JavaScript權(quán)威指南》中包含了一個(gè)這樣的例子,讀者請(qǐng)自行參考。
如果throw 語(yǔ)句位于一個(gè)finally{...}語(yǔ)句塊中,那么在它之后
的語(yǔ)句也不能被執(zhí)行——這意味著finally{...}語(yǔ)句中的代碼“不一定”能被完
整地執(zhí)行。同樣的道理,即使不是使用throw語(yǔ)句顯式地觸發(fā)異常,在finally{...}
塊中出現(xiàn)的任何執(zhí)行期異常也可能中止其后的代碼執(zhí)行。
因此,對(duì)于開發(fā)者來(lái)說,應(yīng)盡可能保證finally{...}語(yǔ)句塊中的代碼都能安
全的、無(wú)異常的執(zhí)行。如果不能確信這一點(diǎn),那么應(yīng)當(dāng)將那些不安全的代碼移
入到try{...}塊中。
實(shí)例創(chuàng)建時(shí)需要使用一個(gè)函數(shù)作為“構(gòu)造器(constructor)”。構(gòu)造器其實(shí)就
是一個(gè)普通的函數(shù),不過在該函數(shù)執(zhí)行過程中,
JavaScript將傳入new運(yùn)算所產(chǎn)生的實(shí)例,以該實(shí)例作為函數(shù)上下文環(huán)境中的
this 對(duì)象引用。這樣一來(lái),在構(gòu)造器函數(shù)內(nèi)部,就可以通過“修改或添加this
對(duì)象引用的成員”來(lái)完成對(duì)象構(gòu)造階段的“初始化對(duì)象實(shí)例”的工作
當(dāng)參數(shù)表為空時(shí),與沒有參數(shù)表是一致。因此下面兩行代碼是等義的:
//示例1:下面兩行代碼等義
obj =new constructor;
obj =new constructor();
但是,我們不能認(rèn)為constructor后面的括號(hào)是函數(shù)調(diào)用的括號(hào)。因?yàn)槿绻?br>這是函數(shù)調(diào)用的括號(hào),那么下面的代碼就應(yīng)該是合理的了(但事實(shí)上,這行代
碼對(duì)于構(gòu)造器來(lái)說是錯(cuò)誤的用法):
//示例2:錯(cuò)誤的代碼
obj =new (constructor());
所以,不能錯(cuò)誤地認(rèn)為:new運(yùn)算是“產(chǎn)生對(duì)象實(shí)例,并調(diào)用constructor函
數(shù)”——盡管看起來(lái)很象是這樣。
構(gòu)造器可通過return返回一個(gè)引用類型的直接量或?qū)ο髮?shí)例,但不能是值類型的直接
量(例如不能是true、'abc'之類)。當(dāng)用戶試圖返回這種值類型數(shù)據(jù)時(shí),腳本引
擎會(huì)忽略掉它們,則仍然使用原來(lái)的this 引用.
function getClass(name) {
switch (name) {
case 'string': return String;
case 'array': return Array;
default: return Object;
}
}
var aObject = {
'name': 'Object1',
value: 123,
getName: function() {
return this.name;
}
getValue: getValue
}
一個(gè)不合法的標(biāo)識(shí)符;也可以用數(shù)字“1”來(lái)做屬性名,同樣它也不是合法的標(biāo)識(shí)符。
空字符串和點(diǎn)號(hào)也可以作為屬性名——盡管可能沒有什么實(shí)用性。此外,
JavaScript對(duì)數(shù)字屬性名還有一些特殊使用的情況。在內(nèi)部轉(zhuǎn)化為字符串作為屬性名.
//示例:列舉成員的顯式成員
for (var nin obj) {
//在變量n中保存有成員名
alert('name: '+n +', value: '+ obj[n]);
}
“.”和“[]”都是對(duì)象成員存取運(yùn)算符,所不同的是:前者右邊的運(yùn)算元
必須是一個(gè)標(biāo)識(shí)符,后者中間的運(yùn)算元可以是變量、直接量或表達(dá)式?!?”是
JavaScript中唯一一個(gè)用“標(biāo)識(shí)符”作為運(yùn)算元的運(yùn)算符。
由于“.”號(hào)要求運(yùn)算元是標(biāo)識(shí)符,因此對(duì)一些不滿足標(biāo)識(shí)符命名規(guī)則的
屬性,就不可以使用“.”號(hào)。例如我們前面提到過的“abcd.def”、“1”和“.”
這些屬性名。這種情況下就只能使用“[]”運(yùn)算符.
刪除一個(gè)對(duì)象的屬性,可以使用delete運(yùn)算符。
delete可以刪除很多東西(包括對(duì)象成員和數(shù)組元素),但它不能刪除:
用var聲明的變量;
直接繼承自原型的成員。
delete obj.method;
delete obj['prop'];
delete array_value[2];
delete golbal_value;
delete不能刪除繼承自原型的成員。但如果你修改了這個(gè)成員的值,你仍
然可以刪除它,這將使它恢復(fù)到原型的值。關(guān)于這一點(diǎn)的真相,是delete運(yùn)算
事實(shí)上是刪除了實(shí)例的成員表中的值
如果真的需要?jiǎng)h除該屬性,你
只能對(duì)原型實(shí)例進(jìn)行操作——當(dāng)然,由于這是原型,所以它會(huì)直接影響到這個(gè)
類構(gòu)造的所有實(shí)例。
delete僅在刪除一個(gè)不能刪除的成員時(shí),才會(huì)返回false。
用delete操作來(lái)刪除宿主對(duì)象的成員時(shí),也可能存在其它的問題。例如
下面這個(gè)例子中,屬性aValue就刪除不掉,而在取值時(shí)又觸發(fā)異常
①
:
// code inInternet Explorer 5~7.x
aValue = 3;
//顯示true
alert('aValue'in window);
delete aValue;
//條件仍然為真,然而用alert()顯示值時(shí)卻出現(xiàn)異常
if ('aValue' inwindow) {
alert(aValue);
}
盡管并沒有資料提及delete運(yùn)算會(huì)導(dǎo)致異常(上例中是取值而非刪除操作
導(dǎo)致異常),但這種情況的確會(huì)發(fā)現(xiàn)。如果你試圖刪除宿主(例如瀏覽器中的
windows)的成員,那么這種可怕的災(zāi)難就會(huì)發(fā)生了:
// code inInternet Explorer 5~7.x
window.prop = 'my custom property';
delete window.prop;
這時(shí)發(fā)生的異常可能是“對(duì)象不支持該操作”,表明宿主不提供刪除成員的能
力——不過不同的宿主的處理方案也不一致,例如firefox瀏覽器就可以正常
刪除。
//方法1:基于運(yùn)算符“.”
obj.method();
//方法2:基于運(yùn)算符“[]”
obj['method']();
所以,事實(shí)上JavaScript中的方法調(diào)用,就是指“取得對(duì)象的成員,并執(zhí)
行函數(shù)調(diào)用運(yùn)算”。具體到語(yǔ)法的實(shí)現(xiàn)方法上,只需要使得“.”和“[]”運(yùn)算
的優(yōu)先級(jí)高于“()”即可。
// 'in'運(yùn)算的表達(dá)式返回值true
if ('XMLHttpRequest'in window) { ... }
if (window.XMLHttpRequest){ ... }
“window.XMLHttpRequest”這個(gè)運(yùn)算用于取
XMLHttpRequest成員。如果該屬性存在,則返回該屬性的值。接下來(lái),這個(gè)
值被類型轉(zhuǎn)換為if 語(yǔ)句所需的布爾值“true”。所以當(dāng)對(duì)這些屬性做取值運(yùn)算時(shí) ,
在if 語(yǔ)句中的效果正好“相當(dāng)于”使用in 運(yùn)算:
同樣,按照J(rèn)avaScript語(yǔ)言的約定,取一個(gè)“不存在的屬性”的值并不會(huì)
導(dǎo)致異常,而是返回“undefined”。而“undefined”可以被類型轉(zhuǎn)換為if 語(yǔ)句
所需的布爾值“false”。所以當(dāng)對(duì)一個(gè)“不存在的屬性”做取值運(yùn)算時(shí),在if
語(yǔ)句中的效果也正好相當(dāng)于使用in 運(yùn)算。
應(yīng)該注意到這里有一個(gè)“隱含的類型強(qiáng)制轉(zhuǎn)換”。正是因?yàn)檫@個(gè)強(qiáng)
制轉(zhuǎn)換,所以下面所有情況都可能導(dǎo)致表達(dá)式結(jié)果為false:
var obj ={};
function _in(obj, prop) {
if (obj[prop]) return true;
return false;
}
//檢測(cè)不存在的屬性
alert( _in(obj, 'myProp') );
//檢測(cè)某些有值的屬性,仍會(huì)返回false
var propertyNames =[0, '', [],false, undefined, null];
for (var i=0; i<propertyNames.length; i++) {
alert( _in(obj, propertyNames[i]) );
一個(gè)屬性即使存在,如果他的值為propertyNames中任一的一
個(gè),都將導(dǎo)致_in()檢測(cè)返回false。
if (typeof(window.XMLHttpRequest) !='undefined')
var obj ={
'aValue': undefined
};
//示例:使用typeof運(yùn)算存在的問題
if (typeof(obj.aValue) !='undefined') {
...
}
這種情況下,aValue屬性是存在的,但我不能通過typeof運(yùn)算來(lái)檢測(cè)它是
否存在。正是由于這個(gè)緣故,在WEB瀏覽器中,DOM的約定是“如果一個(gè)屬
性沒有初值,則應(yīng)該置為null”,這就是因?yàn)樵缙诘腏avaScript不能有效地通過
undefined來(lái)檢測(cè)屬性是否存在。同樣的道理,JavaScript規(guī)范在較后期的版本
中,便要求引擎應(yīng)該實(shí)現(xiàn)in 運(yùn)算,用以更有效地檢測(cè)屬性。
instanceof 將會(huì)檢測(cè)類的繼承關(guān)系。因此一個(gè)子類的實(shí)例,在對(duì)父類做
instanceof 運(yùn)算時(shí),仍會(huì)得到true。
JavaScript中可以看到propertyIsEnumerable()方法,而不會(huì)有(也不必
有)檢測(cè)方法是否可列舉的操作。
當(dāng)某 個(gè) 對(duì) 象 成 員 不 存 在 , 或 它 不 可 列 舉 時(shí) , 則 對(duì) 該 成 員 調(diào) 用
propertyIsEnumerable()方法將返回false——比較常見的情況是,JavaScript對(duì)
象的內(nèi)置成員是不能被列舉的。
可以用“in”運(yùn)算檢測(cè)到該成員,但不能用“for..in”語(yǔ)句來(lái)列舉它。
ECMAScript對(duì)propertyIsEnumerable()的一
個(gè)規(guī)范錯(cuò)誤。按照規(guī)范,該方法是不檢測(cè)對(duì)象的原型鏈的——但如果規(guī)范更合
理一些,那么該方法應(yīng)該檢測(cè)原型鏈的。為什么呢?因?yàn)槭聦?shí)上原型鏈上的(父
代類的)成員也是可以被“for..in”語(yǔ)句列舉的,但它的propertyIsEnumerable()
卻是false。
JavaScript提供with語(yǔ)句,以使得開發(fā)人員可以在一個(gè)代碼塊中顯式地指
定缺省對(duì)象。如果不指定缺省對(duì)象,則默認(rèn)使用宿主程序在全局指定的缺省對(duì)
象(在瀏覽器環(huán)境中的缺省對(duì)象是window)。
二義性
字符串 連接與數(shù) 字求和 是容易出 現(xiàn)二義性 的。因?yàn)?br>JavaScript中對(duì)這兩種運(yùn)算的處理將依賴于數(shù)據(jù)類型,而無(wú)法從運(yùn)算符上進(jìn)行
判讀。我們單獨(dú)地看一個(gè)表達(dá)式:
a= a+ b;
是根本無(wú)法知道它真實(shí)的含義是在求和,亦或是在做字符串連接——JavaScript
引擎做語(yǔ)法分析時(shí),也無(wú)法確知。
加號(hào)“+”帶來(lái)的主要問題與另一條規(guī)則有關(guān)。這條規(guī)則是“如果表達(dá)式
中存在字符串,則優(yōu)先按字符串連接進(jìn)行運(yùn)算”。
這會(huì)在一些宿主中出現(xiàn)問題。例如瀏覽器中,由于DOM模型的許多值看
起來(lái)是數(shù)字,但實(shí)際上卻是字符串。因此試圖做“和”運(yùn)算,卻變成了“字符
串連接”運(yùn)算。下面的例子說明這個(gè)問題:
<img id="testPic" style="border: 1solid red">
我們看到這個(gè)id 為testPic的IMG 元素(element)有一個(gè)寬度為1的邊框——
我們省略了默認(rèn)的單位px(pixel,像素點(diǎn))。但是如果你試圖用下面的代碼來(lái)加
寬它的邊框,你會(huì)得到錯(cuò)誤(一些瀏覽器忽略這個(gè)值,另一些則彈出異常,還
有一些瀏覽器則可能會(huì)崩潰):
var el= document.getElementById('testPic');
el.style.borderWidth += 10;
因?yàn)槭聦?shí)上在DOM模型里,borderWidth是一個(gè)包括了單位的字符串值,
因此這里的值會(huì)是"1px"。JavaScript本身并不會(huì)出錯(cuò),它會(huì)完成類似下面的運(yùn)
算,并使得值賦給borderWidth:
el.style.borderWidth = '1px' +10; //值為'1px10'
這時(shí),瀏覽器的DOM模型無(wú)法解釋“1px10”的含義,因此出錯(cuò)了。當(dāng)
你再次讀borderWidth值時(shí),它將仍是值1px——那么,我們?cè)趺醋C明上述的
運(yùn)算過程呢?下面的代碼將表明JavaScript運(yùn)算的結(jié)果是1px10,但賦值到
borderWidth時(shí),由于DOM忽略掉這個(gè)錯(cuò)誤的值,因此borderWidth沒有發(fā)生
實(shí)際的修改:
alert( el.style.borderWidth ='1px' + 10); //值為'1px10'
這個(gè)問題追其根源,一方面在于我們?cè)试S了省略單位的樣式表寫法,另一
方面也在于腳本引擎不能根據(jù)運(yùn)算符來(lái)確定這里的操作是數(shù)值運(yùn)算還是字符
串連接。后來(lái)W3C推動(dòng)XHTML規(guī)范,試圖從第一方面來(lái)避免這個(gè)問題,但
對(duì)開發(fā)界的影響仍舊有限。因此,瀏覽器的開發(fā)商提供的手冊(cè)中,都會(huì)盡可能
地寫明每一個(gè)屬性的數(shù)據(jù)類型,以避免開發(fā)人員寫出上面這樣的代碼。
最正確的寫法是:
var el= document.getElementById('testPic');
// 1.取原有的單位
var value =parseInt(el.style.borderWidth);
var unit =el.style.borderWidth.substr(value.toString().length);
// 2.運(yùn)算結(jié)果并附加單位
el.style.borderWidth = value +10 +unit;
//如果你確知屬性采用了缺省單位px,并試圖仍然省略單位值,
//那么你可以用下面這種方法(我并不推薦這樣):
// el.style.borderWidth =parseInt(el.style.borderWidth) + 10;
括號(hào)首先可以作為語(yǔ)句中的詞法元素。例如函數(shù)聲明中的“虛擬參數(shù)表”。
括號(hào)只作為“傳值參數(shù)表(這有別于函數(shù)聲明中的'虛
擬參數(shù)表’)”——注意這里它并不表達(dá)函數(shù)調(diào)用的含義。目前為止,這只出現(xiàn)
在new關(guān)鍵字的使用中:new關(guān)鍵字用于創(chuàng)建一個(gè)對(duì)象實(shí)例并負(fù)責(zé)調(diào)用該構(gòu)造
器函數(shù),如果存在一對(duì)括號(hào)“()”指示的參數(shù)表,則在調(diào)用構(gòu)造器函數(shù)時(shí)傳入
該參數(shù)表。例如:
//構(gòu)造對(duì)象時(shí),用于傳入初始化參數(shù)
var myArray = new Array('abc', 1, true);
也可以在for、if、while和do..while等語(yǔ)句中用來(lái)作為限定表
達(dá)式的詞法元素:
在充當(dāng)if、while和do..while的語(yǔ)句中的詞法元素時(shí),括號(hào)會(huì)有“將表達(dá)式結(jié)
果轉(zhuǎn)換為布爾值”的副作用(參見“”)。除此之外,在語(yǔ)句中的括號(hào),并不產(chǎn)
生類似于“運(yùn)算”這樣的附加效果。
第四種情況,是括號(hào)“()”用于強(qiáng)制表達(dá)式運(yùn)算。這種情況下,基本含義
是我們通常說的強(qiáng)制運(yùn)算優(yōu)先級(jí)。但事實(shí)上,不管有沒有優(yōu)先級(jí)的問題,括號(hào)
總是會(huì)強(qiáng)制其內(nèi)部的代碼作為表達(dá)式運(yùn)算。
var str1 =typeof(123);
var str2 =('please input a string', 1000);
在第一行代碼中,“()”強(qiáng)制123作為單值表達(dá)式運(yùn)算,當(dāng)然運(yùn)算結(jié)果還
是123,于是再進(jìn)行typeof 運(yùn)算。所以這里的一對(duì)括號(hào)起到了強(qiáng)制運(yùn)算的作用 。
同樣的道理,第二行代碼里的一對(duì)括號(hào)也起到相同的作用,它強(qiáng)制兩個(gè)單值表
達(dá)式做連續(xù)運(yùn)算,由于連續(xù)運(yùn)算符“,”的返回值是最后一個(gè)表達(dá)式的值,因
此這個(gè)結(jié)果返回了1000。
最后的這種情況最為常見:作為函數(shù)/方法調(diào)用運(yùn)算符。例如:
//有(),表明函數(shù)調(diào)用。
foo();
//沒有(),則該語(yǔ)句只是一個(gè)變量的返回。
foo;
在函數(shù)調(diào)用過程中,我們需要一再?gòu)?qiáng)調(diào):括號(hào)“( )”的作用是運(yùn)算符。我
們也因此得出推論,當(dāng)“( )”作為運(yùn)算符時(shí),它只是作用于表達(dá)式的運(yùn)算,而
不可能作用于語(yǔ)句。所以,你只能將位于:
function foo() {
return( 1+2);
}
這個(gè)函數(shù)內(nèi)的、return之后的括號(hào)理解成“強(qiáng)制表達(dá)式優(yōu)先級(jí)”,而不是理解成
“把return當(dāng)成函數(shù)或運(yùn)算符使用”。
基于同樣的理由,無(wú)論“break(my_label)”看起來(lái)如何合理,也會(huì)被引擎
識(shí)別為語(yǔ)法錯(cuò)誤。因?yàn)閙y_label只是一個(gè)標(biāo)簽,而不是(可以交“()”運(yùn)符處
理的)運(yùn)算元。標(biāo)簽與運(yùn)算元屬于兩個(gè)各自獨(dú)立的、可重復(fù)(而不發(fā)生覆蓋)的
標(biāo)識(shí)符系統(tǒng)。
大括號(hào)有四種作用,四種都是語(yǔ)言的詞法符號(hào)。第一種比較常見,表示“復(fù)
合語(yǔ)句”。例如:
//例1:表示標(biāo)簽后的復(fù)合語(yǔ)句
myLabel :{
//...
}
//例2:在其它語(yǔ)句中表示復(fù)合語(yǔ)句
if (continon_is_true ) {
// ...
}
else {
// ...
}
我們?cè)谇懊嬖?jīng)說過,在復(fù)合語(yǔ)句末尾的大括號(hào)之前,語(yǔ)句的“;”號(hào)可
以省略。我們又說過,有一類“表達(dá)式語(yǔ)句”。因此,下面這行代碼,就值得
回味了:
//例3:復(fù)合語(yǔ)句中的表達(dá)式語(yǔ)句
{
1,2,3
}
這其實(shí)是一個(gè)只有一條語(yǔ)句的復(fù)合語(yǔ)句。這條語(yǔ)句是:
1,2,3;
語(yǔ)句中的逗號(hào)“,”是連續(xù)運(yùn)算符。由于外面有一對(duì)大括號(hào),所以我們省略了
語(yǔ)句未尾的一個(gè)分號(hào)。
對(duì)象直接量聲
明。例如:
//例4:聲明對(duì)象直接量
var obj ={
v1: 1,
v2: 2,
v3: 3
}
由于上面是一條賦值語(yǔ)句,所以其中的直接量聲明部分其實(shí)是一個(gè)表達(dá)
式:
{
v1: 1,
v2: 2,
v3: 3
}
注意這幾行代碼,它們的整體才是一個(gè)表達(dá)式——一個(gè)直接量的單值表達(dá)式。
//方法2:使用復(fù)合語(yǔ)句的表示法
{
{
v1: 1,
v2: 2,
v3: 3
}
}
在第二種方法里,兩對(duì)大括號(hào)的含義就完全不同。從語(yǔ)法解析的角度上講 ,
二者的區(qū)別在于,對(duì)象立即值聲明時(shí),大括號(hào)的內(nèi)部會(huì)有一個(gè):
propertyName: expression
的語(yǔ)法格式,而大括號(hào)用做復(fù)合語(yǔ)句時(shí)沒有這項(xiàng)限制。
var code ='if (true) { entry: 1 }';
var value =eval(code);
alert( value );//顯示值1
1, { entry: 1 }是一個(gè)復(fù)合語(yǔ)句entry是標(biāo)號(hào),1是單值表達(dá)式省略了;
2.{ entry: 1 }是對(duì)象聲明.
JavaScript對(duì)此作出的解釋是“語(yǔ)句優(yōu)先”——因?yàn)檎Z(yǔ)句的語(yǔ)法分析在時(shí)
序上先于代碼執(zhí)行。所以表達(dá)式被作為次級(jí)元素來(lái)理解。因此我們輸出語(yǔ)句的
值,會(huì)是按左邊的方式解析執(zhí)行的結(jié)果值1:
如果用戶代碼需要用右邊的方式來(lái)解析執(zhí)行,那么應(yīng)該用一對(duì)括號(hào)“()”
來(lái)強(qiáng)制表明“表達(dá)式運(yùn)算”。如下:
if (true) ({
entry: 1
});
這樣一來(lái),返回的結(jié)果值就是一個(gè)對(duì)象直接量。下面的代碼用于驗(yàn)證這一
結(jié)果:
//使用括號(hào)“( )”強(qiáng)制表達(dá)式運(yùn)算的結(jié)果
var code ='if (true) ({ entry: 1 })';
var value =eval(code);
alert( value );//顯示值"[object Object]"
大括號(hào)的第三種用法,是被用于函數(shù)直接量聲明時(shí)的語(yǔ)法符號(hào):
大括號(hào)也是結(jié)構(gòu)化異常處理的語(yǔ)法符號(hào)
下面的語(yǔ)句會(huì)出現(xiàn)語(yǔ)法分析錯(cuò)
誤:
//示例:遺漏了try后面的語(yǔ)法符號(hào),因此下面的代碼導(dǎo)致語(yǔ)法分析錯(cuò)
try
i=100;
catch (e){/*略*/}
逗號(hào)“,”的二義性
我們先看看下面兩行語(yǔ)句的不同:
//例1
a= (1, 2,3);
//例2
a= 1,2, 3;
接下來(lái),你能預(yù)想下面這個(gè)語(yǔ)句的結(jié)果嗎?
//例3
a= [1, 2,(3,4,5),6]
這種用法產(chǎn)生的混亂,是因?yàn)槎禾?hào)“,”既可以是語(yǔ)法分隔符,又可以是
運(yùn)算符所導(dǎo)致的。
在上面的例1、例2中,逗號(hào)都被作為“連續(xù)運(yùn)算符”在使用。
例1的效果是“變量a賦值為3”。
例2則因?yàn)闆]有括號(hào)來(lái)強(qiáng)制優(yōu)先級(jí),因此按默認(rèn)優(yōu)先級(jí)會(huì)先完成賦值運(yùn)算,因此效果是“變量a賦值為1”。
在完成賦值運(yùn)算“a=1”之后,還仍將繼續(xù)執(zhí)行連續(xù)運(yùn)算,而最后一個(gè)表達(dá)式就是直接量3。
對(duì)于例1、2來(lái)說,盡管例1、2在變量賦值的效果上并不一樣,但語(yǔ)句的值卻都是3。
//例3
a= [1, 2,(3,4,5),6]
例3的結(jié)果是使變量a賦值為:[1, 2,5, 6]
//例4
var a= 1,2, 3;
然而這個(gè)代碼卻并不會(huì)象例2一樣“正常地執(zhí)行”。因?yàn)槔?中,逗號(hào)被
解釋成了語(yǔ)句var聲明時(shí)用來(lái)分隔多個(gè)變量的語(yǔ)法分隔符,而不是連續(xù)運(yùn)算符 。
而語(yǔ)句var要求用“,”號(hào)來(lái)分隔的是多個(gè)標(biāo)識(shí)符,而數(shù)值2和數(shù)值3顯然都不
是合法的標(biāo)識(shí)符,因此例4的代碼會(huì)在語(yǔ)法解釋期就提示出錯(cuò)。
var聲明會(huì)使連續(xù)運(yùn)算表達(dá)式變?yōu)檫B續(xù)聲明語(yǔ)句。
//顯示最后表達(dá)式的值"value: 240"
var i= 100;
alert( (i+=20, i*=2, 'value: '+i) );
在這個(gè)示例的alert()函數(shù)調(diào)用中,還存在一對(duì)括號(hào):用來(lái)表示強(qiáng)制運(yùn)算。
因?yàn)閍lert()是函數(shù),所以它會(huì)把下面的代碼:
alert( i+=20, i*=2, 'value: '+i);
理解為三個(gè)參數(shù)傳入。又由于函數(shù)參數(shù)表的傳值順序是從左至右的,因此這行
代碼被理解為:
alert( 120, 240, 'value: 240' );
但alert()這個(gè)函 數(shù)自 身 只會(huì) 處理 一 個(gè)參 數(shù), 因 此最 終顯 示 的結(jié) 果會(huì) 是 值
“120”——需要補(bǔ)充的是,這行代碼執(zhí)行完畢之后,變量"i"的值已經(jīng)變成240
了。
為了讓JavaScript理解這里的逗號(hào)","是連續(xù)運(yùn)算符,而不是函數(shù)參數(shù)表的
語(yǔ)法分隔符,我們?cè)谶@里加入了一對(duì)強(qiáng)制(表達(dá)式)運(yùn)算的括號(hào):
alert( (i+=20, i*=2, 'value: '+i) );
a= [[1] [1] ];
是的,很奇怪,這個(gè)語(yǔ)句并沒有語(yǔ)法錯(cuò)誤。盡管我們幾乎不理解這行代碼
的含義,但JavaScript解釋器可以理解,它會(huì)使得a被賦值為[undefined].
方括號(hào)既可以用于聲明數(shù)組的直接量,又
可以是存取數(shù)組下標(biāo)的運(yùn)算符。對(duì)于最開始的例子:
a= [[1] [1] ];
來(lái)說,它相當(dāng)于在執(zhí)行下面的代碼:
arr =[1];
a= [arr[1] ];
由于JavaScript中直接量可以參與運(yùn)算,因此第一個(gè)“[1]”被理解成了一
個(gè)數(shù)組的直接量,它只有一個(gè)元素,即“arr[0]=1”。接下來(lái),由于它是對(duì)象,
所以arr[1]就被理解為取下標(biāo)為1的元素——很顯然,這個(gè)元素還沒有聲明。
因此“[1][1]”的運(yùn)算結(jié)果就是undefined,而a=[[1][1] ]就變成了:
a= [undefined ];
array_properties = [
['pop'],
['push']
['length']
// more ...
];
無(wú)論出于什么原因,你可能忘掉了“['push']”后面的那個(gè)逗號(hào),然而你將
得到如下的一個(gè)數(shù)組(問題是,看起來(lái)它還能繼續(xù)參與運(yùn)算):
arr_properties =[
['pop'],
0//此處應(yīng)為1,原書錯(cuò)誤的寫作0
];
“示例(二)”之復(fù)雜,就在于它集中呈現(xiàn)
了下面三個(gè)語(yǔ)法二義性帶來(lái)的惡果:
方括號(hào)可以被理解為數(shù)組聲明,或下標(biāo)存?。?br> 方括號(hào)還可以被理解為對(duì)象成員存??;
逗號(hào)可以被理解為語(yǔ)法分隔符,或連續(xù)運(yùn)算符。
我們?cè)賮?lái)看這個(gè)例子:
/**
*方括號(hào)的二義性示例(二)
*/
var table =[
['A', 1, 2,3] //<--這里漏掉了一個(gè)逗號(hào)
['B', 3, 4,5],
['C', 5, 6,7]
];
它的第二行并沒有被理解成一個(gè)數(shù)組,也沒有直接地被理解成數(shù)組元素的
存取。相反,它理解成了四個(gè)表達(dá)式連續(xù)運(yùn)算。因?yàn)閺恼Z(yǔ)法上來(lái)說,由于['A', 1,2,
3]是一個(gè)數(shù)組對(duì)象,因此后面的方括號(hào)“[]”會(huì)被理解為“對(duì)象屬性存取運(yùn)算
符”。那么規(guī)則就變成了這樣:
如果其中運(yùn)算的結(jié)果是整數(shù),則用于做下標(biāo)存??;
如果其中運(yùn)算的結(jié)果是字符串,則用于對(duì)象成員存取。
由于在這里['B', 3,4,5]的作用是運(yùn)算取值,因此'B', 3,4,5被當(dāng)成了四個(gè)“各
由一個(gè)直接量構(gòu)成的”表達(dá)式。而這里的“,”號(hào),就不再是數(shù)組聲明時(shí)的語(yǔ)
法分隔符,而是連續(xù)運(yùn)算符。
在上一小節(jié)中,我們說過“,”號(hào)作為連續(xù)運(yùn)算符時(shí),是返回最后一個(gè)表
達(dá)式的值。于是,'B', 3,4,5作為表達(dá)式,就得到了5這個(gè)值。然后,JavaScript
會(huì)據(jù)此把下面的代碼:
var table =[
['A', 1, 2,3] //<--這里漏掉了一個(gè)逗號(hào)
['B', 3, 4,5],
['C', 5, 6,7]
];
理解成:
var table =[
['A', 1, 2,3][5],
['C', 5, 6,7]
];
而['A', 1,2,3]這個(gè)數(shù)組沒有第五個(gè)元素,于是這里的聲明結(jié)果變成了:
var table =[
undefined,
['C', 5, 6,7]
];
例一 var table =[
['A', 1, 2,3] //<--這里漏掉了一個(gè)逗號(hào)
['B', 3, 4,0], //理解為取下標(biāo)0
['C', 5, 6,7]
];
var table =[
'A',
['C', 5, 6,7]
];
例二 var table =[
['A', 1, 2,3] //<--這里漏掉了一個(gè)逗號(hào)
['B','length'], //理解為取屬性'length'
['C', 5, 6,7]
];
var table =[
4,
['C', 5, 6,7]
];