JavaScript程序

第2章 基本語法

2.1 概述

基本句法和變量

語句

JavaScript程序的執(zhí)行單位為行(line),也就是一行一行地執(zhí)行徊件。一般情況下,每一行就是一個(gè)語句窖梁。

語句(statement)是為了完成某種任務(wù)而進(jìn)行的操作,比如下面就是一行賦值語句:

vara =1+3;

這條語句先用var命令,聲明了變量a惧蛹,然后將1 + 3的運(yùn)算結(jié)果賦值給變量a。

1 + 3叫做表達(dá)式(expression)刑枝,指一個(gè)為了得到返回值的計(jì)算式苫费。語句和表達(dá)式的區(qū)別在于,前者主要為了進(jìn)行某種操作包警,一般情況下不需要返回值澄者;后者則是為了得到返回值,一定會(huì)返回一個(gè)值。

凡是JavaScript語言中預(yù)期為值的地方,都可以使用表達(dá)式。比如,賦值語句的等號(hào)右邊梅忌,預(yù)期是一個(gè)值,因此可以放置各種表達(dá)式板鬓。一條語句可以包含多個(gè)表達(dá)式悟耘。

語句以分號(hào)結(jié)尾,一個(gè)分號(hào)就表示一個(gè)語句結(jié)束悲酷。多個(gè)語句可以寫在一行內(nèi)讼昆。

vara =1+3;varb ='abc';

分號(hào)前面可以沒有任何內(nèi)容,JavaScript引擎將其視為空語句吗跋。

;;;

上面的代碼就表示3個(gè)空語句蹬屹。(關(guān)于分號(hào)的更多介紹蒂胞,請(qǐng)看后文《代碼風(fēng)格》一節(jié)经宏。)

表達(dá)式不需要分號(hào)結(jié)尾。一旦在表達(dá)式后面添加分號(hào)问欠,則JavaScript引擎就將表達(dá)式視為語句肖油,這樣會(huì)產(chǎn)生一些沒有任何意義的語句。

1+3;'abc';

上面兩行語句有返回值臂港,但是沒有任何意義森枪,因?yàn)橹皇欠祷匾粋€(gè)單純的值,沒有任何其他操作审孽。

變量

變量是對(duì)“值”的引用县袱,使用變量等同于引用一個(gè)值。每一個(gè)變量都有一個(gè)變量名佑力。

vara =1;

上面的代碼先聲明變量a式散,然后在變量a與數(shù)值1之間建立引用關(guān)系,也稱為將數(shù)值1“賦值”給變量a打颤。以后暴拄,引用變量a就會(huì)得到數(shù)值1。最前面的var编饺,是變量聲明命令乖篷。它表示通知解釋引擎,要?jiǎng)?chuàng)建一個(gè)變量a透且。

變量的聲明和賦值撕蔼,是分開的兩個(gè)步驟,上面的代碼將它們合在了一起秽誊,實(shí)際的步驟是下面這樣鲸沮。

vara;a =1;

如果只是聲明變量而沒有賦值,則該變量的值是不存在的锅论,JavaScript使用undefined表示這種情況讼溺。

vara;a// undefined

JavaScript允許在變量賦值的同時(shí),省略var命令聲明變量棍厌。也就是說肾胯,var a = 1與a = 1,這兩條語句的效果相同耘纱。但是由于這樣的做法很容易不知不覺地創(chuàng)建全局變量(尤其是在函數(shù)內(nèi)部)敬肚,所以建議總是使用var命令聲明變量。

嚴(yán)格地說束析,var a = 1與a = 1艳馒,這兩條語句的效果不完全一樣,主要體現(xiàn)在delete命令無法刪除前者。不過弄慰,絕大多數(shù)情況下第美,這種差異是可以忽略的双吆。

如果一個(gè)變量沒有聲明就直接使用涨享,JavaScript會(huì)報(bào)錯(cuò),告訴你變量未定義迎捺。

x// ReferenceError: x is not defined

上面代碼直接使用變量x慌闭,系統(tǒng)就報(bào)錯(cuò)别威,告訴你變量x沒有聲明。

可以在同一條var命令中聲明多個(gè)變量驴剔。

vara, b;

JavaScirpt是一種動(dòng)態(tài)類型語言省古,也就是說,變量的類型沒有限制丧失,可以賦予各種類型的值豺妓。

vara =1;a ='hello';

上面代碼中,變量a起先被賦值為一個(gè)數(shù)值布讹,后來又被重新賦值為一個(gè)字符串琳拭。第二次賦值的時(shí)候,因?yàn)樽兞縜已經(jīng)存在炒事,所以不需要使用var命令臀栈。

如果使用var重新聲明一個(gè)已經(jīng)存在的變量蔫慧,是無效的挠乳。

varx =1;varx;x// 1

上面代碼中,變量x聲明了兩次姑躲,第二次聲明是無效的睡扬。

但是,如果第二次聲明的同時(shí)還賦值了黍析,則會(huì)覆蓋掉前面的值卖怜。

varx =1;varx =2;// 等同于varx =1;varx;x =2;

變量提升

JavaScript引擎的工作方式是,先解析代碼阐枣,獲取所有被聲明的變量马靠,然后再一行一行地運(yùn)行。這造成的結(jié)果蔼两,就是所有的變量的聲明語句甩鳄,都會(huì)被提升到代碼的頭部,這就叫做變量提升(hoisting)额划。

console.log(a);vara =1;

上面代碼首先使用console.log方法妙啃,在控制臺(tái)(console)顯示變量a的值。這時(shí)變量a還沒有聲明和賦值,所以這是一種錯(cuò)誤的做法揖赴,但是實(shí)際上不會(huì)報(bào)錯(cuò)馆匿。因?yàn)榇嬖谧兞刻嵘嬲\(yùn)行的是下面的代碼燥滑。

vara;console.log(a);a =1;

最后的結(jié)果是顯示undefined渐北,表示變量a已聲明,但還未賦值铭拧。

請(qǐng)注意腔稀,變量提升只對(duì)var命令聲明的變量有效,如果一個(gè)變量不是用var命令聲明的羽历,就不會(huì)發(fā)生變量提升焊虏。

console.log(b);b =1;

上面的語句將會(huì)報(bào)錯(cuò),提示“ReferenceError: b is not defined”秕磷,即變量b未聲明诵闭,這是因?yàn)閎不是用var命令聲明的,JavaScript引擎不會(huì)將其提升澎嚣,而只是視為對(duì)頂層對(duì)象的b屬性的賦值疏尿。

標(biāo)識(shí)符

標(biāo)識(shí)符(identifier)是用來識(shí)別具體對(duì)象的一個(gè)名稱。最常見的標(biāo)識(shí)符就是變量名易桃,以及后面要提到的函數(shù)名褥琐。JavaScript語言的標(biāo)識(shí)符對(duì)大小寫敏感,所以a和A是兩個(gè)不同的標(biāo)識(shí)符晤郑。

標(biāo)識(shí)符有一套命名規(guī)則敌呈,不符合規(guī)則的就是非法標(biāo)識(shí)符。JavaScript引擎遇到非法標(biāo)識(shí)符造寝,就會(huì)報(bào)錯(cuò)磕洪。

簡(jiǎn)單說,標(biāo)識(shí)符命名規(guī)則如下:

第一個(gè)字符诫龙,可以是任意Unicode字母(包括英文字母和其他語言的字母)析显,以及美元符號(hào)($)和下劃線(_)。

第二個(gè)字符及后面的字符签赃,除了Unicode字母谷异、美元符號(hào)和下劃線,還可以用數(shù)字0-9锦聊。

下面這些都是合法的標(biāo)識(shí)符歹嘹。

arg0

_tmp

$elem

π

下面這些則是不合法的標(biāo)識(shí)符。

1a// 第一個(gè)字符不能是數(shù)字23// 同上***// 標(biāo)識(shí)符不能包含星號(hào)a+b// 標(biāo)識(shí)符不能包含加號(hào)-d// 標(biāo)識(shí)符不能包含減號(hào)或連詞線

中文是合法的標(biāo)識(shí)符括丁,可以用作變量名荞下。

var臨時(shí)變量 =1;

JavaScript有一些保留字,不能用作標(biāo)識(shí)符:arguments、break尖昏、case仰税、catch、class抽诉、const陨簇、continue、debugger迹淌、default河绽、delete、do唉窃、else耙饰、enum、eval纹份、export苟跪、extends、false蔓涧、finally件已、for、function元暴、if篷扩、implements、import茉盏、in鉴未、instanceof、interface援岩、let歼狼、new掏导、null享怀、package、private趟咆、protected添瓷、public、return值纱、static鳞贷、super、switch虐唠、this搀愧、throw、true、try咱筛、typeof搓幌、var、void迅箩、while溉愁、with、yield饲趋。

另外拐揭,還有三個(gè)詞雖然不是保留字,但是因?yàn)榫哂刑貏e含義奕塑,也不應(yīng)該用作標(biāo)識(shí)符:Infinity堂污、NaN、undefined龄砰。

注釋

源碼中被JavaScript引擎忽略的部分就叫做注釋敷鸦,它的作用是對(duì)代碼進(jìn)行解釋。Javascript提供兩種注釋:一種是單行注釋寝贡,用//起頭扒披;另一種是多行注釋,放在/* 和 */之間圃泡。

// 這是單行注釋/*

這是

多行

注釋

*/

此外碟案,由于歷史上JavaScript兼容HTML代碼的注釋,所以也被視為單行注釋颇蜡。

x =1;

-->x = 3;

上面代碼中价说,只有x = 1會(huì)執(zhí)行,其他的部分都被注釋掉了风秤。

需要注意的是鳖目,-->只有在行首,才會(huì)被當(dāng)成單行注釋缤弦,否則就是一個(gè)運(yùn)算符领迈。

functioncountdown(n){while(n -->0)console.log(n);}countdown(3)// 2// 1// 0

上面代碼中,n --> 0實(shí)際上會(huì)當(dāng)作n-- > 0碍沐,因此輸出2稼稿、1鲤脏、0。

區(qū)塊

JavaScript使用大括號(hào),將多個(gè)相關(guān)的語句組合在一起啸蜜,稱為“區(qū)塊”(block)隐孽。

與大多數(shù)編程語言不一樣瘤运,JavaScript的區(qū)塊不構(gòu)成單獨(dú)的作用域(scope)辱挥。也就是說置吓,區(qū)塊中的變量與區(qū)塊外的變量,屬于同一個(gè)作用域缔赠。

{vara =1;}a// 1

上面代碼在區(qū)塊內(nèi)部交洗,聲明并賦值了變量a,然后在區(qū)塊外部橡淑,變量a依然有效构拳,這說明區(qū)塊不構(gòu)成單獨(dú)的作用域,與不使用區(qū)塊的情況沒有任何區(qū)別梁棠。所以置森,單獨(dú)使用的區(qū)塊在JavaScript中意義不大,很少出現(xiàn)符糊。區(qū)塊往往用來構(gòu)成其他更復(fù)雜的語法結(jié)構(gòu)凫海,比如for、if男娄、while行贪、function等。

條件語句

條件語句提供一種語法構(gòu)造模闲,只有滿足某個(gè)條件建瘫,才會(huì)執(zhí)行相應(yīng)的語句。JavaScript提供if結(jié)構(gòu)和switch結(jié)構(gòu)尸折,完成條件判斷啰脚。

if 結(jié)構(gòu)

if結(jié)構(gòu)先判斷一個(gè)表達(dá)式的布爾值,然后根據(jù)布爾值的真?zhèn)问导校瑘?zhí)行不同的語句橄浓。

if(expression)? statement;// 或者if(expression) statement;

上面是if結(jié)構(gòu)的基本形式。需要注意的是亮航,expression(表達(dá)式)必須放在圓括號(hào)中荸实,表示對(duì)表達(dá)式求值。如果結(jié)果為true缴淋,就執(zhí)行緊跟在后面的語句(statement)准给;如果結(jié)果為false,則跳過statement的部分宴猾。

if(m ===3)? m +=1;

上面代碼表示圆存,只有在m等于3時(shí),才會(huì)將其值加上1仇哆。

這種寫法要求條件表達(dá)式后面只能有一個(gè)語句。如果想執(zhí)行多個(gè)語句夫植,必須在if的條件判斷之后讹剔,加上大括號(hào)油讯,表示代碼塊。

if(m ===3) {? m +=1;}

建議總是在if語句中使用大括號(hào)延欠,因?yàn)檫@樣方便插入語句陌兑。

注意,if后面的表達(dá)式由捎,不要混淆“賦值表達(dá)式”(=)與“嚴(yán)格相等運(yùn)算符”(===)或“相等運(yùn)算符”(==)兔综。因?yàn)椋百x值表達(dá)式”不具有比較作用狞玛。

varx =1;vary =2;if(x = y) {console.log(x);}// "2"

上面代碼的原意是软驰,當(dāng)x等于y的時(shí)候,才執(zhí)行相關(guān)語句心肪。但是锭亏,不小心將“嚴(yán)格相等運(yùn)算符”寫成“賦值表達(dá)式”,結(jié)果變成了將y賦值給x硬鞍,然后條件就變成了慧瘤,變量x的值(等于2)自動(dòng)轉(zhuǎn)為布爾值以后,判斷其是否為true固该。

這種錯(cuò)誤可以正常生成一個(gè)布爾值锅减,因而不會(huì)報(bào)錯(cuò)。為了避免這種情況伐坏,有些開發(fā)者習(xí)慣將常量寫在運(yùn)算符的左邊上煤,這樣的話,一旦不小心將相等運(yùn)算符寫成賦值運(yùn)算符著淆,就會(huì)報(bào)錯(cuò)劫狠,因?yàn)槌A坎荒鼙毁x值。

if(x =2) {// 不報(bào)錯(cuò)if(2= x) {// 報(bào)錯(cuò)

至于為什么優(yōu)先采用“嚴(yán)格相等運(yùn)算符”(===)永部,而不是“相等運(yùn)算符”(==)独泞,請(qǐng)參考《運(yùn)算符》一節(jié)。

if...else結(jié)構(gòu)

if代碼塊后面苔埋,還可以跟一個(gè)else代碼塊懦砂,表示不滿足條件時(shí),所要執(zhí)行的代碼组橄。

if(m ===3) {// then}else{// else}

上面代碼判斷變量m是否等于3荞膘,如果等于就執(zhí)行if代碼塊,否則執(zhí)行else代碼塊玉工。

對(duì)同一個(gè)變量進(jìn)行多次判斷時(shí)羽资,多個(gè)if...else語句可以連寫在一起。

if(m ===0) {// ...}elseif(m ===1) {// ...}elseif(m ===2) {// ...}else{// ...}

else代碼塊總是跟隨離自己最近的那個(gè)if語句遵班。

varm =1;varn =2;if(m !==1)if(n ===2)console.log('hello');elseconsole.log('world');

上面代碼不會(huì)有任何輸出屠升,else代碼塊不會(huì)得到執(zhí)行潮改,因?yàn)樗氖亲罱哪莻€(gè)if語句,相當(dāng)于下面這樣腹暖。

if(m !==1) {if(n ===2) {console.log('hello');? ? }else{console.log('world');? }}

如果想讓else代碼塊跟隨最上面的那個(gè)if語句汇在,就要改變大括號(hào)的位置。

if(m !==1) {if(n ===2) {console.log('hello');? ? }}else{console.log('world');}// world

switch結(jié)構(gòu)

多個(gè)if...else連在一起使用的時(shí)候脏答,可以轉(zhuǎn)為使用更方便的switch結(jié)構(gòu)糕殉。

switch(fruit) {case"banana":// ...break;case"apple":// ...break;default:// ...}

上面代碼根據(jù)變量fruit的值,選擇執(zhí)行相應(yīng)的case殖告。如果所有case都不符合阿蝶,則執(zhí)行最后的default部分。需要注意的是丛肮,每個(gè)case代碼塊內(nèi)部的break語句不能少赡磅,否則會(huì)接下去執(zhí)行下一個(gè)case代碼塊,而不是跳出switch結(jié)構(gòu)宝与。

varx =1;switch(x) {case1:console.log('x 于1');case2:console.log('x 等于2');default:console.log('x 等于其他值');}// x等于1// x等于2// x等于其他值

上面代碼中焚廊,case代碼塊之中沒有break語句,導(dǎo)致不會(huì)跳出switch結(jié)構(gòu)习劫,而會(huì)一直執(zhí)行下去咆瘟。

switch語句部分和case語句部分,都可以使用表達(dá)式诽里。

switch(1+3) {case2+2:? ? f();break;default:? ? neverhappens();}

上面代碼的default部分袒餐,是永遠(yuǎn)不會(huì)執(zhí)行到的。

需要注意的是谤狡,switch語句后面的表達(dá)式與case語句后面的表示式灸眼,在比較運(yùn)行結(jié)果時(shí),采用的是嚴(yán)格相等運(yùn)算符(===)墓懂,而不是相等運(yùn)算符(==)焰宣,這意味著比較時(shí)不會(huì)發(fā)生類型轉(zhuǎn)換。

varx =1;switch(x) {casetrue:console.log('x發(fā)生類型轉(zhuǎn)換');default:console.log('x沒有發(fā)生類型轉(zhuǎn)換');}// x沒有發(fā)生類型轉(zhuǎn)換

上面代碼中捕仔,由于變量x沒有發(fā)生類型轉(zhuǎn)換匕积,所以不會(huì)執(zhí)行case true的情況。這表明榜跌,switch語句內(nèi)部采用的是“嚴(yán)格相等運(yùn)算符”闪唆,詳細(xì)解釋請(qǐng)參考《運(yùn)算符》一節(jié)。

switch結(jié)構(gòu)不利于代碼重用钓葫,往往可以用對(duì)象形式重寫悄蕾。

functiongetItemPricing(customer, item){switch(customer.type) {case'VIP':returnitem.price * item.quantity *0.50;case'Preferred':returnitem.price * item.quantity *0.75;case'Regular':casedefault:returnitem.price * item.quantity;? }}

上面代碼根據(jù)不同用戶,返回不同的價(jià)格瓤逼。你可以發(fā)現(xiàn)笼吟,switch語句包含的三種情況库物,內(nèi)部邏輯都是相同的霸旗,不同只是折扣率贷帮。這啟發(fā)我們可以用對(duì)象屬性,重寫這個(gè)判斷诱告。

varpricing = {'VIP':0.50,'Preferred':0.75,'Regular':1.0};functiongetItemPricing(customer, item){if(pricing[customer.type])returnitem.price * item.quantity * pricing[customer.type];elsereturnitem.price * item.quantity * pricing.Regular;}

如果價(jià)格檔次再多一些撵枢,對(duì)象屬性寫法的簡(jiǎn)潔優(yōu)勢(shì)就更明顯了。

三元運(yùn)算符 ?:

JavaScript還有一個(gè)三元運(yùn)算符(即該運(yùn)算符需要三個(gè)運(yùn)算子)?:精居,也可以用于邏輯判斷锄禽。

(contidion) ? expression1 : expression2

上面代碼中,如果contidion為true靴姿,則返回expression1的值沃但,否則返回expression2的值。

vareven = (n %2===0) ?true:false;

上面代碼中佛吓,如果n可以被2整除宵晚,則even等于true,否則等于false维雇。它等同于下面的形式淤刃。

vareven;if(n %2===0) {? even =true;}else{? even =false;}

這個(gè)三元運(yùn)算符可以被視為if...else...的簡(jiǎn)寫形式,因此可以用于多種場(chǎng)合吱型。

varmyVar;console.log( myVar? ?'myVar has a value':'myVar do not has a value')// myVar do not has a value

上面代碼利用三元運(yùn)算符逸贾,輸出相應(yīng)的提示勇垛。

varmsg ='The number '+ n? +' is '+ ((n %2===0) ?'even':'odd');

上面代碼利用三元運(yùn)算符擎厢,在字符串之中插入不同的值。

循環(huán)語句

循環(huán)語句用于重復(fù)執(zhí)行某個(gè)操作锄码,它有多種形式触徐。

while循環(huán)

While語句包括一個(gè)循環(huán)條件和一段代碼塊咪鲜,只要條件為真,就不斷循環(huán)執(zhí)行代碼塊锌介。

while(expression)? statement;// 或者while(expression) statement;

while語句的循環(huán)條件是一個(gè)表達(dá)式(express)嗜诀,必須放在圓括號(hào)中。代碼塊部分孔祸,如果只有一條語句(statement)隆敢,可以省略大括號(hào),否則就必須加上大括號(hào)崔慧。

while(expression) {? statement;}

下面是while語句的一個(gè)例子拂蝎。

vari =0;while(i <100) {console.log('i當(dāng)前為:'+ i);? i +=1;}

上面的代碼將循環(huán)100次,直到i等于100為止惶室。

下面的例子是一個(gè)無限循環(huán)温自,因?yàn)闂l件總是為真玄货。

while(true) {console.log("Hello, world");}

for循環(huán)

for語句是循環(huán)命令的另一種形式。

for(initialize; test; increment)? statement// 或者for(initialize; test; increment) {? statement}

for語句后面的括號(hào)里面悼泌,有三個(gè)表達(dá)式松捉。

初始化表達(dá)式(initialize):確定循環(huán)的初始值,只在循環(huán)開始時(shí)執(zhí)行一次馆里。

測(cè)試表達(dá)式(test):檢查循環(huán)條件隘世,只要為真就進(jìn)行后續(xù)操作。

遞增表達(dá)式(increment):完成后續(xù)操作鸠踪,然后返回上一步丙者,再一次檢查循環(huán)條件。

下面是一個(gè)例子营密。

varx =3;for(vari =0; i < x; i++) {console.log(i);}// 0// 1// 2

上面代碼中械媒,初始化表達(dá)式是var i = 0,即初始化一個(gè)變量i评汰;測(cè)試表達(dá)式是i < x纷捞,即只要i小于x,就會(huì)執(zhí)行循環(huán)键俱;遞增表達(dá)式是i++兰绣,即每次循環(huán)結(jié)束后,i增大1编振。

所有for循環(huán)缀辩,都可以改寫成while循環(huán)。上面的例子改為while循環(huán)踪央,代碼如下臀玄。

varx =3;vari =0;while(i < x) {console.log(i);? i++;}

for語句的三個(gè)部分(initialize,test畅蹂,increment)健无,可以省略任何一個(gè),也可以全部省略液斜。

for( ; ; ){console.log('Hello World');}

上面代碼省略了for語句表達(dá)式的三個(gè)部分累贤,結(jié)果就導(dǎo)致了一個(gè)無限循環(huán)。

do...while循環(huán)

do...while循環(huán)與while循環(huán)類似少漆,唯一的區(qū)別就是先運(yùn)行一次循環(huán)體臼膏,然后判斷循環(huán)條件。

dostatementwhile(expression);// 或者do{? statement}while(expression);

不管條件是否為真示损,do..while循環(huán)至少運(yùn)行一次渗磅,這是這種結(jié)構(gòu)最大的特點(diǎn)。另外,while語句后面的分號(hào)不能省略始鱼。

下面是一個(gè)例子仔掸。

varx =3;vari =0;do{console.log(i);? i++;}while(i < x);

break語句和continue語句

break語句和continue語句都具有跳轉(zhuǎn)作用,可以讓代碼不按既有的順序執(zhí)行医清。

break語句用于跳出代碼塊或循環(huán)起暮。

vari =0;while(i <100) {console.log('i當(dāng)前為:'+ i);? i++;if(i ===10)break;}

上面代碼只會(huì)執(zhí)行10次循環(huán),一旦i等于10状勤,就會(huì)跳出循環(huán)鞋怀。

for循環(huán)也可以使用break語句跳出循環(huán)双泪。

for(vari =0; i <5; i++) {console.log(i);if(i ===3)break;}// 0// 1// 2// 3

上面代碼執(zhí)行到i等于3持搜,就會(huì)跳出循環(huán)。

continue語句用于立即終止本輪循環(huán)焙矛,返回循環(huán)結(jié)構(gòu)的頭部葫盼,開始下一輪循環(huán)。

vari =0;while(i <100){? i++;if(i%2===0)continue;console.log('i當(dāng)前為:'+ i);}

上面代碼只有在i為奇數(shù)時(shí)村斟,才會(huì)輸出i的值贫导。如果i為偶數(shù),則直接進(jìn)入下一輪循環(huán)蟆盹。

如果存在多重循環(huán)孩灯,不帶參數(shù)的break語句和continue語句都只針對(duì)最內(nèi)層循環(huán)。

標(biāo)簽(label)

JavaScript語言允許逾滥,語句的前面有標(biāo)簽(label)峰档,相當(dāng)于定位符,用于跳轉(zhuǎn)到程序的任意位置寨昙,標(biāo)簽的格式如下讥巡。

label:

? statement

標(biāo)簽可以是任意的標(biāo)識(shí)符,但是不能是保留字舔哪,語句部分可以是任意語句欢顷。

標(biāo)簽通常與break語句和continue語句配合使用,跳出特定的循環(huán)捉蚤。

top:for(vari =0; i <3; i++){for(varj =0; j <3; j++){if(i ===1&& j ===1)breaktop;console.log('i='+ i +', j='+ j);? ? }? }// i=0, j=0// i=0, j=1// i=0, j=2// i=1, j=0

上面代碼為一個(gè)雙重循環(huán)區(qū)塊抬驴,break命令后面加上了top標(biāo)簽(注意,top不用加引號(hào))缆巧,滿足條件時(shí)布持,直接跳出雙層循環(huán)。如果break語句后面不使用標(biāo)簽盅蝗,則只能跳出內(nèi)層循環(huán)鳖链,進(jìn)入下一次的外層循環(huán)。

continue語句也可以與標(biāo)簽配合使用。

top:for(vari =0; i <3; i++){for(varj =0; j <3; j++){if(i ===1&& j ===1)continuetop;console.log('i='+ i +', j='+ j);? ? }? }// i=0, j=0// i=0, j=1// i=0, j=2// i=1, j=0// i=2, j=0// i=2, j=1// i=2, j=2

上面代碼中芙委,continue命令后面有一個(gè)標(biāo)簽名逞敷,滿足條件時(shí),會(huì)跳過當(dāng)前循環(huán)灌侣,直接進(jìn)入下一輪外層循環(huán)推捐。如果continue語句后面不使用標(biāo)簽,則只能進(jìn)入下一輪的內(nèi)層循環(huán)侧啼。

數(shù)據(jù)類型

概述

JavaScript語言的每一個(gè)值牛柒,都屬于某一種數(shù)據(jù)類型。JavaScript的數(shù)據(jù)類型痊乾,共有六種皮壁。(ES6又新增了第七種Symbol類型的值,本教程不涉及哪审。)

數(shù)值(number):整數(shù)和小數(shù)(比如1和3.14)

字符串(string):字符組成的文本(比如"Hello World")

布爾值(boolean):true(真)和false(假)兩個(gè)特定值

undefined:表示“未定義”或不存在蛾魄,即此處目前沒有任何值

null:表示空缺,即此處應(yīng)該有一個(gè)值湿滓,但目前為空

對(duì)象(object):各種值組成的集合

通常滴须,我們將數(shù)值、字符串叽奥、布爾值稱為原始類型(primitive type)的值扔水,即它們是最基本的數(shù)據(jù)類型,不能再細(xì)分了朝氓。而將對(duì)象稱為合成類型(complex type)的值魔市,因?yàn)橐粋€(gè)對(duì)象往往是多個(gè)原始類型的值的合成,可以看作是一個(gè)存放各種值的容器膀篮。至于undefined和null嘹狞,一般將它們看成兩個(gè)特殊值。

對(duì)象又可以分成三個(gè)子類型誓竿。

狹義的對(duì)象(object)

數(shù)組(array)

函數(shù)(function)

狹義的對(duì)象和數(shù)組是兩種不同的數(shù)據(jù)組合方式磅网,而函數(shù)其實(shí)是處理數(shù)據(jù)的方法。JavaScript把函數(shù)當(dāng)成一種數(shù)據(jù)類型筷屡,可以像其他類型的數(shù)據(jù)一樣涧偷,進(jìn)行賦值和傳遞,這為編程帶來了很大的靈活性毙死,體現(xiàn)了JavaScript作為“函數(shù)式語言”的本質(zhì)燎潮。

這里需要明確的是,JavaScript的所有數(shù)據(jù)扼倘,都可以視為廣義的對(duì)象确封。不僅數(shù)組和函數(shù)屬于對(duì)象除呵,就連原始類型的數(shù)據(jù)(數(shù)值、字符串爪喘、布爾值)也可以用對(duì)象方式調(diào)用颜曾。為了避免混淆,此后除非特別聲明秉剑,本教程的”對(duì)象“都特指狹義的對(duì)象泛豪。

本教程將詳細(xì)介紹所有的數(shù)據(jù)類型。undefined和null兩個(gè)特殊值和布爾類型Boolean比較簡(jiǎn)單侦鹏,將在本節(jié)介紹诡曙,其他類型將各自有單獨(dú)的一節(jié)。

typeof運(yùn)算符

JavaScript有三種方法略水,可以確定一個(gè)值到底是什么類型价卤。

typeof運(yùn)算符

instanceof運(yùn)算符

Object.prototype.toString方法

instanceof運(yùn)算符和Object.prototype.toString方法,將在后文相關(guān)章節(jié)介紹聚请。這里著重介紹typeof運(yùn)算符荠雕。

typeof運(yùn)算符可以返回一個(gè)值的數(shù)據(jù)類型,可能有以下結(jié)果驶赏。

(1)原始類型

數(shù)值湿故、字符串絮姆、布爾值分別返回number炼邀、string舟扎、boolean餐禁。

typeof123// "number"typeof'123'// "string"typeoffalse// "boolean"

(2)函數(shù)

函數(shù)返回function修陡。

functionf(){}typeoff// "function"

(3)undefined

undefined返回undefined硫麻。

typeofundefined// "undefined"

利用這一點(diǎn)弟断,typeof可以用來檢查一個(gè)沒有聲明的變量洒敏,而不報(bào)錯(cuò)龄恋。

v// ReferenceError: v is not definedtypeofv// "undefined"

上面代碼中,變量v沒有用var命令聲明凶伙,直接使用就會(huì)報(bào)錯(cuò)郭毕。但是,放在typeof后面函荣,就不報(bào)錯(cuò)了显押,而是返回undefined。

實(shí)際編程中傻挂,這個(gè)特點(diǎn)通常用在判斷語句乘碑。

// 錯(cuò)誤的寫法if(v) {// ...}// ReferenceError: v is not defined// 正確的寫法if(typeofv ==="undefined") {// ...}

(4)其他

除此以外,其他情況都返回object金拒。

typeofwindow// "object"typeof{}// "object"typeof[]// "object"typeofnull// "object"

從上面代碼可以看到兽肤,空數(shù)組([])的類型也是object,這表示在JavaScript內(nèi)部,數(shù)組本質(zhì)上只是一種特殊的對(duì)象资铡。

另外沉迹,null的類型也是object,這是由于歷史原因造成的害驹。1995年JavaScript語言的第一版鞭呕,所有值都設(shè)計(jì)成32位,其中最低的3位用來表述數(shù)據(jù)類型宛官,object對(duì)應(yīng)的值是000葫松。當(dāng)時(shí),只設(shè)計(jì)了五種數(shù)據(jù)類型(對(duì)象底洗、整數(shù)腋么、浮點(diǎn)數(shù)、字符串和布爾值)亥揖,完全沒考慮null珊擂,只把它當(dāng)作object的一種特殊值,32位全部為0费变。這是typeof null返回object的根本原因摧扇。

為了兼容以前的代碼,后來就沒法修改了挚歧。這并不是說null就屬于對(duì)象扛稽,本質(zhì)上null是一個(gè)類似于undefined的特殊值。

既然typeof對(duì)數(shù)組(array)和對(duì)象(object)的顯示結(jié)果都是object滑负,那么怎么區(qū)分它們呢在张?instanceof運(yùn)算符可以做到。

varo = {};vara = [];oinstanceofArray// falseainstanceofArray// true

instanceof運(yùn)算符的詳細(xì)解釋矮慕,請(qǐng)見《面向?qū)ο缶幊獭芬徽隆?/p>

null和undefined

概述

null與undefined都可以表示“沒有”帮匾,含義非常相似。將一個(gè)變量賦值為undefined或null痴鳄,老實(shí)說瘟斜,語法效果幾乎沒區(qū)別。

vara =undefined;// 或者vara =null;

上面代碼中夏跷,a變量分別被賦值為undefined和null哼转,這兩種寫法的效果幾乎等價(jià)。

在if語句中槽华,它們都會(huì)被自動(dòng)轉(zhuǎn)為false壹蔓,相等運(yùn)算符(==)甚至直接報(bào)告兩者相等。

if(!undefined) {console.log('undefined is false');}// undefined is falseif(!null) {console.log('null is false');}// null is falseundefined==null// true

上面代碼說明猫态,兩者的行為是何等相似佣蓉!Google公司開發(fā)的JavaScript語言的替代品Dart語言披摄,就明確規(guī)定只有null,沒有undefined勇凭!

既然含義與用法都差不多疚膊,為什么要同時(shí)設(shè)置兩個(gè)這樣的值,這不是無端增加復(fù)雜度虾标,令初學(xué)者困擾嗎寓盗?這與歷史原因有關(guān)。

1995年JavaScript誕生時(shí)璧函,最初像Java一樣傀蚌,只設(shè)置了null作為表示"無"的值。根據(jù)C語言的傳統(tǒng)蘸吓,null被設(shè)計(jì)成可以自動(dòng)轉(zhuǎn)為0善炫。

Number(null)// 05+null// 5

但是,JavaScript的設(shè)計(jì)者Brendan Eich库继,覺得這樣做還不夠箩艺,有兩個(gè)原因。首先宪萄,null像在Java里一樣艺谆,被當(dāng)成一個(gè)對(duì)象。但是雨膨,JavaScript的值分成原始類型和合成類型兩大類擂涛,Brendan Eich覺得表示"無"的值最好不是對(duì)象。其次聊记,JavaScript的最初版本沒有包括錯(cuò)誤處理機(jī)制,發(fā)生數(shù)據(jù)類型不匹配時(shí)恢暖,往往是自動(dòng)轉(zhuǎn)換類型或者默默地失敗排监。Brendan Eich覺得,如果null自動(dòng)轉(zhuǎn)為0杰捂,很不容易發(fā)現(xiàn)錯(cuò)誤舆床。

因此,Brendan Eich又設(shè)計(jì)了一個(gè)undefined嫁佳。他是這樣區(qū)分的:null是一個(gè)表示"無"的對(duì)象挨队,轉(zhuǎn)為數(shù)值時(shí)為0;undefined是一個(gè)表示"無"的原始值蒿往,轉(zhuǎn)為數(shù)值時(shí)為NaN盛垦。

Number(undefined)// NaN5+undefined// NaN

但是,這樣的區(qū)分在實(shí)踐中很快就被證明不可行瓤漏。目前null和undefined基本是同義的腾夯,只有一些細(xì)微的差別颊埃。

null的特殊之處在于,JavaScript把它包含在對(duì)象類型(object)之中蝶俱。

typeofnull// "object"

上面代碼表示班利,查詢null的類型,JavaScript返回object(對(duì)象)榨呆。

這并不是說null的數(shù)據(jù)類型就是對(duì)象罗标,而是JavaScript早期部署中的一個(gè)約定俗成,其實(shí)不完全正確积蜻,后來再想改已經(jīng)太晚了闯割,會(huì)破壞現(xiàn)存代碼,所以一直保留至今浅侨。

注意纽谒,JavaScript的標(biāo)識(shí)名區(qū)分大小寫,所以u(píng)ndefined和null不同于Undefined和Null(或者其他僅僅大小寫不同的詞形)如输,后者只是普通的變量名鼓黔。

用法和含義

對(duì)于null和undefined,可以大致可以像下面這樣理解不见。

null表示空值澳化,即該處的值現(xiàn)在為空。比如稳吮,調(diào)用函數(shù)時(shí)缎谷,不需要傳入某個(gè)參數(shù),這時(shí)就可以傳入null灶似。

undefined表示“未定義”列林,下面是返回undefined的典型場(chǎng)景。

// 變量聲明了酪惭,但沒有賦值vari;i// undefined// 調(diào)用函數(shù)時(shí)希痴,應(yīng)該提供的參數(shù)沒有提供,該參數(shù)等于undefinedfunctionf(x){returnx;}f()// undefined// 對(duì)象沒有賦值的屬性varo =newObject();o.p// undefined// 函數(shù)沒有返回值時(shí)春感,默認(rèn)返回undefinedfunctionf(){}f()// undefined

布爾值

布爾值代表“真”和“假”兩個(gè)狀態(tài)砌创。“真”用關(guān)鍵字true表示鲫懒,“假”用關(guān)鍵字false表示嫩实。布爾值只有這兩個(gè)值。

下列運(yùn)算符會(huì)返回布爾值:

兩元邏輯運(yùn)算符:&&(And)窥岩,||(Or)

前置邏輯運(yùn)算符:!(Not)

相等運(yùn)算符:===甲献,!==,==谦秧,!=

比較運(yùn)算符:>竟纳,>=撵溃,<,<=

如果JavaScript預(yù)期某個(gè)位置應(yīng)該是布爾值锥累,會(huì)將該位置上現(xiàn)有的值自動(dòng)轉(zhuǎn)為布爾值氛赐。轉(zhuǎn)換規(guī)則是除了下面六個(gè)值被轉(zhuǎn)為false败去,其他值都視為true赦役。

undefined

null

false

0

NaN

""或''(空字符串)

布爾值往往用于程序流程的控制挨摸,請(qǐng)看一個(gè)例子。

if('') {console.log(true);}// 沒有任何輸出

上面代碼的if命令后面的判斷條件际歼,預(yù)期應(yīng)該是一個(gè)布爾值惶翻,所以JavaScript自動(dòng)將空字符串,轉(zhuǎn)為布爾值false鹅心,導(dǎo)致程序不會(huì)進(jìn)入代碼塊吕粗,所以沒有任何輸出。

需要特別注意的是旭愧,空數(shù)組([])和空對(duì)象({})對(duì)應(yīng)的布爾值颅筋,都是true。

if([]) {console.log(true);}// trueif({}) {console.log(true);}// true

更多關(guān)于數(shù)據(jù)類型轉(zhuǎn)換的介紹输枯,參見《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)议泵。

2.2 數(shù)值

概述

整數(shù)和浮點(diǎn)數(shù)

JavaScript內(nèi)部,所有數(shù)字都是以64位浮點(diǎn)數(shù)形式儲(chǔ)存桃熄,即使整數(shù)也是如此先口。所以,1與1.0是相同的瞳收,是同一個(gè)數(shù)碉京。

1===1.0// true

這就是說,在JavaScript語言的底層螟深,根本沒有整數(shù)收夸,所有數(shù)字都是小數(shù)(64位浮點(diǎn)數(shù))。容易造成混淆的是血崭,某些運(yùn)算只有整數(shù)才能完成,此時(shí)JavaScript會(huì)自動(dòng)把64位浮點(diǎn)數(shù)厘灼,轉(zhuǎn)成32位整數(shù)夹纫,然后再進(jìn)行運(yùn)算,參見《運(yùn)算符》一節(jié)的”位運(yùn)算“部分设凹。

由于浮點(diǎn)數(shù)不是精確的值舰讹,所以涉及小數(shù)的比較和運(yùn)算要特別小心。

0.1+0.2===0.3// false0.3/0.1// 2.9999999999999996(0.3-0.2) === (0.2-0.1)// false

數(shù)值精度

根據(jù)國(guó)際標(biāo)準(zhǔn)IEEE 754闪朱,JavaScript浮點(diǎn)數(shù)的64個(gè)二進(jìn)制位月匣,從最左邊開始钻洒,是這樣組成的。

第1位:符號(hào)位锄开,0表示正數(shù)素标,1表示負(fù)數(shù)

第2位到第12位:儲(chǔ)存指數(shù)部分

第13位到第64位:儲(chǔ)存小數(shù)部分(即有效數(shù)字)

符號(hào)位決定了一個(gè)數(shù)的正負(fù),指數(shù)部分決定了數(shù)值的大小萍悴,小數(shù)部分決定了數(shù)值的精度头遭。

IEEE 754規(guī)定,有效數(shù)字第一位默認(rèn)總是1癣诱,不保存在64位浮點(diǎn)數(shù)之中计维。也就是說,有效數(shù)字總是1.xx...xx的形式撕予,其中xx..xx的部分保存在64位浮點(diǎn)數(shù)之中鲫惶,最長(zhǎng)可能為52位。因此实抡,JavaScript提供的有效數(shù)字最長(zhǎng)為53個(gè)二進(jìn)制位欠母。

(-1)^符號(hào)位 * 1.xx...xx* 2^指數(shù)位

上面公式是一個(gè)數(shù)在JavaScript內(nèi)部實(shí)際的表現(xiàn)形式。

精度最多只能到53個(gè)二進(jìn)制位澜术,這意味著艺蝴,絕對(duì)值小于2的53次方的整數(shù),即-(253-1)到253-1鸟废,都可以精確表示猜敢。

Math.pow(2,53)// 9007199254740992Math.pow(2,53) +1// 9007199254740992Math.pow(2,53) +2// 9007199254740994Math.pow(2,53) +3// 9007199254740996Math.pow(2,53) +4// 9007199254740996

從上面示例可以看到,大于2的53次方以后盒延,整數(shù)運(yùn)算的結(jié)果開始出現(xiàn)錯(cuò)誤缩擂。所以,大于等于2的53次方的數(shù)值添寺,都無法保持精度胯盯。

Math.pow(2,53)// 9007199254740992// 多出的三個(gè)有效數(shù)字,將無法保存9007199254740992111// 9007199254740992000

上面示例表明计露,大于2的53次方以后博脑,多出來的有效數(shù)字(最后三位的111)都會(huì)無法保存,變成0票罐。

數(shù)值范圍

根據(jù)標(biāo)準(zhǔn)叉趣,64位浮點(diǎn)數(shù)的指數(shù)部分的長(zhǎng)度是11個(gè)二進(jìn)制位,意味著指數(shù)部分的最大值是2047(2的11次方減1)该押。也就是說疗杉,64位浮點(diǎn)數(shù)的指數(shù)部分的值最大為2047,分出一半表示負(fù)數(shù)蚕礼,則JavaScript能夠表示的數(shù)值范圍為21024到2-1023(開區(qū)間)烟具,超出這個(gè)范圍的數(shù)無法表示梢什。

如果指數(shù)部分等于或超過最大正值1024,JavaScript會(huì)返回Infinity(關(guān)于Infinity的介紹參見下文)朝聋,這稱為“正向溢出”嗡午;如果等于或超過最小負(fù)值-1023(即非常接近0),JavaScript會(huì)直接把這個(gè)數(shù)轉(zhuǎn)為0玖翅,這稱為“負(fù)向溢出”翼馆。

varx =0.5;for(vari =0; i <25; i++) {? x = x * x;}x// 0

上面代碼對(duì)0.5連續(xù)做25次平方,由于最后結(jié)果太接近0金度,超出了可表示的范圍应媚,JavaScript就直接將其轉(zhuǎn)為0。

至于具體的最大值和最小值猜极,JavaScript提供Number對(duì)象的MAX_VALUE和MIN_VALUE屬性表示(參見《Number對(duì)象》一節(jié))中姜。

Number.MAX_VALUE// 1.7976931348623157e+308Number.MIN_VALUE// 5e-324

數(shù)值的表示法

JavaScript的數(shù)值有多種表示方法,可以用字面形式直接表示跟伏,比如35(十進(jìn)制)和0xFF(十六進(jìn)制)丢胚。

數(shù)值也可以采用科學(xué)計(jì)數(shù)法表示,下面是幾個(gè)科學(xué)計(jì)數(shù)法的例子受扳。

123e3// 123000123e-3// 0.123-3.1E+12.1e-23

科學(xué)計(jì)數(shù)法允許字母e或E的后面携龟,跟著一個(gè)整數(shù),表示這個(gè)數(shù)值的指數(shù)部分勘高。

以下兩種情況峡蟋,JavaScript會(huì)自動(dòng)將數(shù)值轉(zhuǎn)為科學(xué)計(jì)數(shù)法表示,其他情況都采用字面形式直接表示华望。

(1)小數(shù)點(diǎn)前的數(shù)字多于21位蕊蝗。

1234567890123456789012// 1.2345678901234568e+21123456789012345678901// 123456789012345680000

(2)小數(shù)點(diǎn)后的零多于5個(gè)。

// 小數(shù)點(diǎn)后緊跟5個(gè)以上的零赖舟,// 就自動(dòng)轉(zhuǎn)為科學(xué)計(jì)數(shù)法0.0000003// 3e-7// 否則蓬戚,就保持原來的字面形式0.000003// 0.000003

數(shù)值的進(jìn)制

使用字面量(literal)時(shí),JavaScript對(duì)整數(shù)提供四種進(jìn)制的表示方法:十進(jìn)制宾抓、十六進(jìn)制子漩、八進(jìn)制、2進(jìn)制石洗。

十進(jìn)制:沒有前導(dǎo)0的數(shù)值痛单。

八進(jìn)制:有前綴0o或0O的數(shù)值,或者有前導(dǎo)0劲腿、且只用到0-7的七個(gè)阿拉伯?dāng)?shù)字的數(shù)值。

十六進(jìn)制:有前綴0x或0X的數(shù)值鸟妙。

二進(jìn)制:有前綴0b或0B的數(shù)值焦人。

默認(rèn)情況下挥吵,JavaScript內(nèi)部會(huì)自動(dòng)將八進(jìn)制、十六進(jìn)制花椭、二進(jìn)制轉(zhuǎn)為十進(jìn)制忽匈。下面是一些例子。

0xff// 2550o377// 2550b11// 3

如果八進(jìn)制矿辽、十六進(jìn)制丹允、二進(jìn)制的數(shù)值里面,出現(xiàn)不屬于該進(jìn)制的數(shù)字袋倔,就會(huì)報(bào)錯(cuò)雕蔽。

0xzz// 報(bào)錯(cuò)0o88// 報(bào)錯(cuò)0b22// 報(bào)錯(cuò)

上面代碼中,十六進(jìn)制出現(xiàn)了字母z宾娜、八進(jìn)制出現(xiàn)數(shù)字8批狐、二進(jìn)制出現(xiàn)數(shù)字2,因此報(bào)錯(cuò)前塔。

通常來說嚣艇,有前導(dǎo)0的數(shù)值會(huì)被視為八進(jìn)制,但是如果前導(dǎo)0后面有數(shù)字8和9华弓,則該數(shù)值被視為十進(jìn)制食零。

0888// 8880777// 511

用前導(dǎo)0表示八進(jìn)制,處理時(shí)很容易造成混亂寂屏。ES5的嚴(yán)格模式和ES6贰谣,已經(jīng)廢除了這種表示法,但是瀏覽器目前還支持凑保。

特殊數(shù)值

JavaScript提供幾個(gè)特殊的數(shù)值冈爹。

正零和負(fù)零

前面說過,JavaScript的64位浮點(diǎn)數(shù)之中欧引,有一個(gè)二進(jìn)制位是符號(hào)位频伤。這意味著,任何一個(gè)數(shù)都有一個(gè)對(duì)應(yīng)的負(fù)值芝此,就連0也不例外憋肖。

在JavaScript內(nèi)部,實(shí)際上存在2個(gè)0:一個(gè)是+0婚苹,一個(gè)是-0岸更。它們是等價(jià)的。

-0=== +0// true0===-0// true0=== +0// true

幾乎所有場(chǎng)合膊升,正零和負(fù)零都會(huì)被當(dāng)作正常的0怎炊。

+0// 0-0// 0(-0).toString()// '0'(+0).toString()// '0'

唯一有區(qū)別的場(chǎng)合是,+0或-0當(dāng)作分母,返回的值是不相等的评肆。

(1/ +0) === (1/-0)// false

上面代碼之所以出現(xiàn)這樣結(jié)果债查,是因?yàn)槌哉愕玫?Infinity州弟,除以負(fù)零得到-Infinity岭粤,這兩者是不相等的(關(guān)于Infinity詳見后文)。

NaN

(1)含義

NaN是JavaScript的特殊值傻丝,表示“非數(shù)字”(Not a Number)久橙,主要出現(xiàn)在將字符串解析成數(shù)字出錯(cuò)的場(chǎng)合俄占。

5-'x'// NaN

上面代碼運(yùn)行時(shí),會(huì)自動(dòng)將字符串x轉(zhuǎn)為數(shù)值淆衷,但是由于x不是數(shù)值缸榄,所以最后得到結(jié)果為NaN,表示它是“非數(shù)字”(NaN)吭敢。

另外碰凶,一些數(shù)學(xué)函數(shù)的運(yùn)算結(jié)果會(huì)出現(xiàn)NaN。

Math.acos(2)// NaNMath.log(-1)// NaNMath.sqrt(-1)// NaN

0除以0也會(huì)得到NaN鹿驼。

0/0// NaN

需要注意的是欲低,NaN不是一種獨(dú)立的數(shù)據(jù)類型,而是一種特殊數(shù)值畜晰,它的數(shù)據(jù)類型依然屬于Number砾莱,使用typeof運(yùn)算符可以看得很清楚。

typeofNaN// 'number'

(2)運(yùn)算規(guī)則

NaN不等于任何值凄鼻,包括它本身腊瑟。

NaN===NaN// false

由于數(shù)組的indexOf方法,內(nèi)部使用的是嚴(yán)格相等運(yùn)算符块蚌,所以該方法對(duì)NaN不成立闰非。

[NaN].indexOf(NaN)// -1

NaN在布爾運(yùn)算時(shí)被當(dāng)作false。

Boolean(NaN)// false

NaN與任何數(shù)(包括它自己)的運(yùn)算峭范,得到的都是NaN财松。

NaN+32// NaNNaN-32// NaNNaN*32// NaNNaN/32// NaN

(3)判斷NaN的方法

isNaN方法可以用來判斷一個(gè)值是否為NaN。

isNaN(NaN)// trueisNaN(123)// false

但是纱控,isNaN只對(duì)數(shù)值有效辆毡,如果傳入其他值,會(huì)被先轉(zhuǎn)成數(shù)值甜害。比如舶掖,傳入字符串的時(shí)候,字符串會(huì)被先轉(zhuǎn)成NaN尔店,所以最后返回true眨攘,這一點(diǎn)要特別引起注意主慰。也就是說,isNaN為true的值期犬,有可能不是NaN河哑,而是一個(gè)字符串。

isNaN('Hello')// true// 相當(dāng)于isNaN(Number('Hello'))// true

出于同樣的原因龟虎,對(duì)于對(duì)象和數(shù)組,isNaN也返回true沙庐。

isNaN({})// true// 等同于isNaN(Number({}))// trueisNaN(['xzy'])// true// 等同于isNaN(Number(['xzy']))// true

但是鲤妥,對(duì)于空數(shù)組和只有一個(gè)數(shù)值成員的數(shù)組,isNaN返回false拱雏。

isNaN([])// falseisNaN([123])// falseisNaN(['123'])// false

上面代碼之所以返回false棉安,原因是這些數(shù)組能被Number函數(shù)轉(zhuǎn)成數(shù)值,請(qǐng)參見《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)铸抑。

因此贡耽,使用isNaN之前,最好判斷一下數(shù)據(jù)類型鹊汛。

functionmyIsNaN(value){returntypeofvalue ==='number'&&isNaN(value);}

判斷NaN更可靠的方法是蒲赂,利用NaN是JavaScript之中唯一不等于自身的值這個(gè)特點(diǎn),進(jìn)行判斷刁憋。

functionmyIsNaN(value){returnvalue !== value;}

Infinity

(1)定義

Infinity表示“無窮”滥嘴,用來表示兩種場(chǎng)景。一種是一個(gè)正的數(shù)值太大至耻,或一個(gè)負(fù)的數(shù)值太小若皱,無法表示;另一種是非0數(shù)值除以0尘颓,得到Infinity走触。

// 場(chǎng)景一Math.pow(2,Math.pow(2,100))// Infinity// 場(chǎng)景二0/0// NaN1/0// Infinity

上面代碼中,第一個(gè)場(chǎng)景是一個(gè)表達(dá)式的計(jì)算結(jié)果太大疤苹,超出了JavaScript能夠表示的范圍互广,因此返回Infinity。第二個(gè)場(chǎng)景是0除以0會(huì)得到NaN痰催,而非0數(shù)值除以0兜辞,會(huì)返回Infinity。

Infinity有正負(fù)之分夸溶,Infinity表示正的無窮逸吵,-Infinity表示負(fù)的無窮。

Infinity=== -Infinity// false1/-0// -Infinity-1/-0// Infinity

上面代碼中缝裁,非零正數(shù)除以-0扫皱,會(huì)得到-Infinity足绅,負(fù)數(shù)除以-0,會(huì)得到Infinity韩脑。

由于數(shù)值正向溢出(overflow)氢妈、負(fù)向溢出(underflow)和被0除,JavaScript都不報(bào)錯(cuò)段多,而是返回Infinity首量,所以單純的數(shù)學(xué)運(yùn)算幾乎沒有可能拋出錯(cuò)誤。

Infinity大于一切數(shù)值(除了NaN)进苍,-Infinity小于一切數(shù)值(除了NaN)加缘。

Infinity>1000// true-Infinity<-1000// true

Infinity與NaN比較,總是返回false觉啊。

Infinity>NaN// falseInfinity

(2)運(yùn)算規(guī)則

Infinity的四則運(yùn)算拣宏,符合無窮的數(shù)學(xué)計(jì)算規(guī)則。

5*Infinity// Infinity5-Infinity// -InfinityInfinity/5// Infinity5/Infinity// 0

Infinity加上或乘以Infinity杠人,返回的還是Infinity勋乾。

Infinity+Infinity// InfinityInfinity*Infinity// Infinity

Infinity減去或除以Infinity,得到NaN嗡善。

Infinity-Infinity// NaNInfinity/Infinity// NaN

(3)isFinite函數(shù)

isFinite函數(shù)返回一個(gè)布爾值辑莫,檢查某個(gè)值是不是正常數(shù)值,而不是Infinity滤奈。

isFinite(Infinity)// falseisFinite(-1)// trueisFinite(true)// trueisFinite(NaN)// false

上面代碼表示摆昧,如果對(duì)NaN使用isFinite函數(shù),也返回false蜒程,表示NaN不是一個(gè)正常值绅你。

與數(shù)值相關(guān)的全局方法

parseInt()

(1)基本用法

parseInt方法用于將字符串轉(zhuǎn)為整數(shù)。

parseInt('123')// 123

如果字符串頭部有空格昭躺,空格會(huì)被自動(dòng)去除忌锯。

parseInt('? 81')// 81

如果parseInt的參數(shù)不是字符串,則會(huì)先轉(zhuǎn)為字符串再轉(zhuǎn)換领炫。

parseInt(1.23)// 1// 等同于parseInt('1.23')// 1

字符串轉(zhuǎn)為整數(shù)的時(shí)候偶垮,是一個(gè)個(gè)字符依次轉(zhuǎn)換,如果遇到不能轉(zhuǎn)為數(shù)字的字符帝洪,就不再進(jìn)行下去似舵,返回已經(jīng)轉(zhuǎn)好的部分。

parseInt('8a')// 8parseInt('12**')// 12parseInt('12.34')// 12parseInt('15e2')// 15parseInt('15px')// 15

上面代碼中葱峡,parseInt的參數(shù)都是字符串砚哗,結(jié)果只返回字符串頭部可以轉(zhuǎn)為數(shù)字的部分。

如果字符串的第一個(gè)字符不能轉(zhuǎn)化為數(shù)字(后面跟著數(shù)字的正負(fù)號(hào)除外)砰奕,返回NaN蛛芥。

parseInt('abc')// NaNparseInt('.3')// NaNparseInt('')// NaNparseInt('+')// NaNparseInt('+1')// 1

parseInt的返回值只有兩種可能提鸟,不是一個(gè)十進(jìn)制整數(shù),就是NaN仅淑。

如果字符串以0x或0X開頭称勋,parseInt會(huì)將其按照十六進(jìn)制數(shù)解析。

parseInt('0x10')// 16

如果字符串以0開頭涯竟,將其按照10進(jìn)制解析赡鲜。

parseInt('011')// 11

對(duì)于那些會(huì)自動(dòng)轉(zhuǎn)為科學(xué)計(jì)數(shù)法的數(shù)字,parseInt會(huì)將科學(xué)計(jì)數(shù)法的表示方法視為字符串庐船,因此導(dǎo)致一些奇怪的結(jié)果蝗蛙。

parseInt(1000000000000000000000.5)// 1// 等同于parseInt('1e+21')// 1parseInt(0.0000008)// 8// 等同于parseInt('8e-7')// 8

(2)進(jìn)制轉(zhuǎn)換

parseInt方法還可以接受第二個(gè)參數(shù)(2到36之間),表示被解析的值的進(jìn)制醉鳖,返回該值對(duì)應(yīng)的十進(jìn)制數(shù)。默認(rèn)情況下哮内,parseInt的第二個(gè)參數(shù)為10盗棵,即默認(rèn)是十進(jìn)制轉(zhuǎn)十進(jìn)制。

parseInt('1000')// 1000// 等同于parseInt('1000',10)// 1000

下面是轉(zhuǎn)換指定進(jìn)制的數(shù)的例子北发。

parseInt('1000',2)// 8parseInt('1000',6)// 216parseInt('1000',8)// 512

上面代碼中纹因,二進(jìn)制、六進(jìn)制琳拨、八進(jìn)制的1000瞭恰,分別等于十進(jìn)制的8、216和512狱庇。這意味著惊畏,可以用parseInt方法進(jìn)行進(jìn)制的轉(zhuǎn)換。

如果第二個(gè)參數(shù)不是數(shù)值密任,會(huì)被自動(dòng)轉(zhuǎn)為一個(gè)整數(shù)颜启。這個(gè)整數(shù)只有在2到36之間,才能得到有意義的結(jié)果浪讳,超出這個(gè)范圍缰盏,則返回NaN。如果第二個(gè)參數(shù)是0淹遵、undefined和null口猜,則直接忽略。

parseInt('10',37)// NaNparseInt('10',1)// NaNparseInt('10',0)// 10parseInt('10',null)// 10parseInt('10',undefined)// 10

如果字符串包含對(duì)于指定進(jìn)制無意義的字符透揣,則從最高位開始济炎,只返回可以轉(zhuǎn)換的數(shù)值。如果最高位無法轉(zhuǎn)換淌实,則直接返回NaN冻辩。

parseInt('1546',2)// 1parseInt('546',2)// NaN

上面代碼中猖腕,對(duì)于二進(jìn)制來說,1是有意義的字符恨闪,5倘感、4、6都是無意義的字符咙咽,所以第一行返回1钠乏,第二行返回NaN明刷。

前面說過,如果parseInt的第一個(gè)參數(shù)不是字符串,會(huì)被先轉(zhuǎn)為字符串抗愁。這會(huì)導(dǎo)致一些令人意外的結(jié)果签餐。

parseInt(0x11,36)// 43// 等同于parseInt(String(0x11),36)parseInt('17',36)

上面代碼中,十六進(jìn)制的0x11會(huì)被先轉(zhuǎn)為十進(jìn)制的17愚战,再轉(zhuǎn)為字符串娇唯。然后,再用36進(jìn)制解讀字符串17寂玲,最后返回結(jié)果43塔插。

這種處理方式,對(duì)于八進(jìn)制的前綴0拓哟,尤其需要注意。

parseInt(011,2)// NaN// 等同于parseInt(String(011),2)parseInt('011',2)// 3

上面代碼中捧颅,第一行的011會(huì)被先轉(zhuǎn)為字符串9亮蒋,因?yàn)?不是二進(jìn)制的有效字符,所以返回NaN湿硝。第二行的字符串011,會(huì)被當(dāng)作二進(jìn)制處理垛膝,返回3线衫。

ES5不再允許將帶有前綴0的數(shù)字視為八進(jìn)制數(shù),而是要求忽略這個(gè)0。但是,為了保證兼容性,大部分瀏覽器并沒有部署這一條規(guī)定茵烈。

parseFloat()

parseFloat方法用于將一個(gè)字符串轉(zhuǎn)為浮點(diǎn)數(shù)存璃。

parseFloat('3.14')// 3.14

如果字符串符合科學(xué)計(jì)數(shù)法,則會(huì)進(jìn)行相應(yīng)的轉(zhuǎn)換辑甜。

parseFloat('314e-2')// 3.14parseFloat('0.0314E+2')// 3.14

如果字符串包含不能轉(zhuǎn)為浮點(diǎn)數(shù)的字符,則不再進(jìn)行往后轉(zhuǎn)換子檀,返回已經(jīng)轉(zhuǎn)好的部分。

parseFloat('3.14more non-digit characters')// 3.14

parseFloat方法會(huì)自動(dòng)過濾字符串前導(dǎo)的空格缩歪。

parseFloat('\t\v\r12.34\n ')// 12.34

如果參數(shù)不是字符串逛球,或者字符串的第一個(gè)字符不能轉(zhuǎn)化為浮點(diǎn)數(shù),則返回NaN。

parseFloat([])// NaNparseFloat('FF2')// NaNparseFloat('')// NaN

上面代碼中,尤其值得注意,parseFloat會(huì)將空字符串轉(zhuǎn)為NaN世杀。

這些特點(diǎn)使得parseFloat的轉(zhuǎn)換結(jié)果不同于Number函數(shù)。

parseFloat(true)// NaNNumber(true)// 1parseFloat(null)// NaNNumber(null)// 0parseFloat('')// NaNNumber('')// 0parseFloat('123.45#')// 123.45Number('123.45#')// NaN

2.3 字符串

概述

定義

字符串就是零個(gè)或多個(gè)排在一起的字符,放在單引號(hào)或雙引號(hào)之中。

'abc'"abc"

單引號(hào)字符串的內(nèi)部,可以使用雙引號(hào)煌抒。雙引號(hào)字符串的內(nèi)部况既,可以使用單引號(hào)臭胜。

'key = "value"'"It's a long journey"

上面兩個(gè)都是合法的字符串。

如果要在單引號(hào)字符串的內(nèi)部吕晌,使用單引號(hào)(或者在雙引號(hào)字符串的內(nèi)部乏沸,使用雙引號(hào))铆铆,就必須在內(nèi)部的單引號(hào)(或者雙引號(hào))前面加上反斜杠,用來轉(zhuǎn)義鳍悠。

'Did she say \'Hello\'?'// "Did she say 'Hello'?""Did she say \"Hello\"?"http:// "Did she say "Hello"?"

字符串默認(rèn)只能寫在一行內(nèi)薯蝎,分成多行將會(huì)報(bào)錯(cuò)。

'a

b

c'// SyntaxError: Unexpected token ILLEGAL

上面代碼將一個(gè)字符串分成三行,JavaScript就會(huì)報(bào)錯(cuò)。

如果長(zhǎng)字符串必須分成多行,可以在每一行的尾部使用反斜杠。

varlongString ="Long \

long \

long \

string";longString// "Long long long string"

上面代碼表示,加了反斜杠以后嘹承,原來寫在一行的字符串豪娜,可以分成多行,效果與寫在同一行完全一樣。注意,反斜杠的后面必須是換行符恐锣,而不能有其他字符(比如空格)玷禽,否則會(huì)報(bào)錯(cuò)。

連接運(yùn)算符(+)可以連接多個(gè)單行字符串,用來模擬多行字符串咪啡。

varlongString ='Long '+'long '+'long '+'string';

另外钥飞,有一種利用多行注釋结闸,生成多行字符串的變通方法结耀。

(function(){/*

line 1

line 2

line 3

*/}).toString().split('\n').slice(1,-1).join('\n')// "line 1 line 2 line 3"

轉(zhuǎn)義

反斜杠(\\)在字符串內(nèi)有特殊含義慰枕,用來表示一些特殊字符蜂厅,所以又稱為轉(zhuǎn)義符稠通。

需要用反斜杠轉(zhuǎn)義的特殊字符狮惜,主要有下面這些:

\0代表沒有內(nèi)容的字符(\u0000)

\b后退鍵(\u0008)

\f換頁符(\u000C)

\n換行符(\u000A)

\r回車鍵(\u000D)

\t制表符(\u0009)

\v垂直制表符(\u000B)

\'單引號(hào)(\u0027)

\"雙引號(hào)(\u0022)

\\\\反斜杠(\u005C)

\XXX用三個(gè)八進(jìn)制數(shù)(000到377)表示字符,XXX對(duì)應(yīng)該字符的Unicode,比如\251表示版權(quán)符號(hào)。

\xXX用兩個(gè)十六進(jìn)制數(shù)(00到FF)表示字符贡歧,XX對(duì)應(yīng)該字符的Unicode绍弟,比如\xA9表示版權(quán)符號(hào)豹悬。

\uXXXX用四位十六進(jìn)制的Unicode編號(hào)代表某個(gè)字符,比如\u00A9表示版權(quán)符號(hào)瞻佛。

下面是最后三種字符的特殊寫法的例子脱篙。

'\251'// "?"'\xA9'// "?"'\u00A9'// "?"'\172'==='z'// true'\x7A'==='z'// true'\u007A'==='z'// true

如果非特殊字符前面使用反斜杠川梅,則反斜杠會(huì)被省略怨酝。

'\a'// "a"

上面代碼表示a是一個(gè)正常字符揍堕,前面加反斜杠沒有特殊含義抖部,則反斜杠會(huì)被自動(dòng)省略夫啊。

如果字符串的正常內(nèi)容之中玄坦,需要包含反斜杠沸枯,則反斜杠前需要再加一個(gè)反斜杠赤套,用來對(duì)自身轉(zhuǎn)義。

"Prev \\ Next"http:// "Prev \ Next"

字符串與數(shù)組

字符串可以被視為字符數(shù)組阻桅,因此可以使用數(shù)組的方括號(hào)運(yùn)算符,用來返回某個(gè)位置的字符(從0開始)北戏。

vars ='hello';s[0]// "h"s[1]// "e"s[4]// "o"http:// 直接對(duì)字符串使用方括號(hào)運(yùn)算符'hello'[1]// "e"

如果方括號(hào)中的數(shù)字超過字符串的范圍赘阀,或者方括號(hào)中根本不是數(shù)字秒咨,則返回undefined。

'abc'[3]// undefined'abc'[-1]// undefined'abc'['x']// undefined

但是,字符串與數(shù)組的相似性僅此而已屋谭。實(shí)際上扶踊,無法改變字符串之中的單個(gè)字符霉猛。

vars ='hello';deletes[0];s// "hello"s[1] ='a';s// "hello"s[5] ='!';s// "hello"

上面代碼表示荡澎,字符串內(nèi)部的單個(gè)字符無法改變和增刪椒涯,這些操作會(huì)默默地失敗镐作。

字符串之所以類似于字符數(shù)組,實(shí)際是由于對(duì)字符串進(jìn)行方括號(hào)運(yùn)算時(shí)刃泌,字符串會(huì)自動(dòng)轉(zhuǎn)換為一個(gè)字符串對(duì)象(詳見《標(biāo)準(zhǔn)庫(kù)》一章的《包裝對(duì)象》一節(jié))。

length屬性

length屬性返回字符串的長(zhǎng)度绽族,該屬性也是無法改變的。

vars ='hello';s.length// 5s.length =3;s.length// 5s.length =7;s.length// 5

上面代碼表示字符串的length屬性無法改變,但是不會(huì)報(bào)錯(cuò)泥技。

字符集

JavaScript使用Unicode字符集浆兰,也就是說在JavaScript內(nèi)部,所有字符都用Unicode表示。

不僅JavaScript內(nèi)部使用Unicode儲(chǔ)存字符簸呈,而且還可以直接在程序中使用Unicode榕订,所有字符都可以寫成"\uxxxx"的形式,其中xxxx代表該字符的Unicode編碼蜕便。比如劫恒,\u00A9代表版權(quán)符號(hào)。

vars ='\u00A9';s// "?"

每個(gè)字符在JavaScript內(nèi)部都是以16位(即2個(gè)字節(jié))的UTF-16格式儲(chǔ)存玩裙。也就是說兼贸,JavaScript的單位字符長(zhǎng)度固定為16位長(zhǎng)度,即2個(gè)字節(jié)吃溅。

但是溶诞,UTF-16有兩種長(zhǎng)度:對(duì)于U+0000到U+FFFF之間的字符,長(zhǎng)度為16位(即2個(gè)字節(jié))决侈;對(duì)于U+10000到U+10FFFF之間的字符螺垢,長(zhǎng)度為32位(即4個(gè)字節(jié)),而且前兩個(gè)字節(jié)在0xD800到0xDBFF之間赖歌,后兩個(gè)字節(jié)在0xDC00到0xDFFF之間枉圃。舉例來說,U+1D306對(duì)應(yīng)的字符為??庐冯,它寫成UTF-16就是0xD834 0xDF06孽亲。瀏覽器會(huì)正確將這四個(gè)字節(jié)識(shí)別為一個(gè)字符,但是JavaScript內(nèi)部的字符長(zhǎng)度總是固定為16位展父,會(huì)把這四個(gè)字節(jié)視為兩個(gè)字符返劲。

vars ='\uD834\uDF06';s// "??"s.length// 2/^.$/.test(s)// falses.charAt(0)// ""s.charAt(1)// ""s.charCodeAt(0)// 55348s.charCodeAt(1)// 57094

上面代碼說明,對(duì)于于U+10000到U+10FFFF之間的字符栖茉,JavaScript總是視為兩個(gè)字符(字符的length屬性為2)篮绿,用來匹配單個(gè)字符的正則表達(dá)式會(huì)失敗(JavaScript認(rèn)為這里不止一個(gè)字符)吕漂,charAt方法無法返回單個(gè)字符亲配,charCodeAt方法返回每個(gè)字節(jié)對(duì)應(yīng)的十進(jìn)制值。

所以處理的時(shí)候惶凝,必須把這一點(diǎn)考慮在內(nèi)吼虎。對(duì)于4個(gè)字節(jié)的Unicode字符,假定C是字符的Unicode編號(hào)苍鲜,H是前兩個(gè)字節(jié)思灰,L是后兩個(gè)字節(jié),則它們之間的換算關(guān)系如下坡贺。

// 將大于U+FFFF的字符官辈,從Unicode轉(zhuǎn)為UTF-16H =Math.floor((C -0x10000) /0x400) +0xD800L = (C -0x10000) %0x400+0xDC00// 將大于U+FFFF的字符箱舞,從UTF-16轉(zhuǎn)為UnicodeC = (H -0xD800) *0x400+ L -0xDC00+0x10000

下面的正則表達(dá)式可以識(shí)別所有UTF-16字符。

([\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF])

由于JavaScript引擎(嚴(yán)格說是ES5規(guī)格)不能自動(dòng)識(shí)別輔助平面(編號(hào)大于0xFFFF)的Unicode字符拳亿,導(dǎo)致所有字符串處理函數(shù)遇到這類字符晴股,都會(huì)產(chǎn)生錯(cuò)誤的結(jié)果(詳見《標(biāo)準(zhǔn)庫(kù)》一章的String對(duì)象章節(jié))。如果要完成字符串相關(guān)操作肺魁,就必須判斷字符是否落在0xD800到0xDFFF這個(gè)區(qū)間电湘。

下面是能夠正確處理字符串遍歷的函數(shù)。

functiongetSymbols(string){varlength = string.length;varindex =-1;varoutput = [];varcharacter;varcharCode;while(++index < length) {? ? character = string.charAt(index);? ? charCode = character.charCodeAt(0);if(charCode >=0xD800&& charCode <=0xDBFF) {? ? ? output.push(character + string.charAt(++index));? ? }else{? ? ? output.push(character);? ? }? }returnoutput;}varsymbols = getSymbols('??');symbols.forEach(function(symbol){// ...});

替換(String.prototype.replace)鹅经、截取子字符串(String.prototype.substring,String.prototype.slice)等其他字符串操作寂呛,都必須做類似的處理。

Base64轉(zhuǎn)碼

Base64是一種編碼方法瘾晃,可以將任意字符轉(zhuǎn)成可打印字符贷痪。使用這種編碼方法愿卸,主要不是為了加密宠叼,而是為了不出現(xiàn)特殊字符,簡(jiǎn)化程序的處理作儿。

JavaScript原生提供兩個(gè)Base64相關(guān)方法强胰。

btoa():字符串或二進(jìn)制值轉(zhuǎn)為Base64編碼

atob():Base64編碼轉(zhuǎn)為原來的編碼

varstring ='Hello World!';btoa(string)// "SGVsbG8gV29ybGQh"atob('SGVsbG8gV29ybGQh')// "Hello World!"

這兩個(gè)方法不適合非ASCII碼的字符舱沧,會(huì)報(bào)錯(cuò)。

btoa('你好')// Uncaught DOMException: The string to be encoded contains characters outside of the Latin1 range.

要將非ASCII碼字符轉(zhuǎn)為Base64編碼偶洋,必須中間插入一個(gè)轉(zhuǎn)碼環(huán)節(jié)熟吏,再使用這兩個(gè)方法。

functionb64Encode(str){returnbtoa(encodeURIComponent(str));}functionb64Decode(str){returndecodeURIComponent(atob(str));}b64Encode('你好')// "JUU0JUJEJUEwJUU1JUE1JUJE"b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE')// "你好"

對(duì)象

概述

生成方法

對(duì)象(object)是JavaScript的核心概念玄窝,也是最重要的數(shù)據(jù)類型牵寺。JavaScript的所有數(shù)據(jù)都可以被視為對(duì)象。

簡(jiǎn)單說哆料,所謂對(duì)象缸剪,就是一種無序的數(shù)據(jù)集合吗铐,由若干個(gè)“鍵值對(duì)”(key-value)構(gòu)成东亦。

varo = {p:'Hello World'};

上面代碼中,大括號(hào)就定義了一個(gè)對(duì)象唬渗,它被賦值給變量o典阵。這個(gè)對(duì)象內(nèi)部包含一個(gè)鍵值對(duì)(又稱為“成員”),p是“鍵名”(成員的名稱)镊逝,字符串Hello World是“鍵值”(成員的值)壮啊。鍵名與鍵值之間用冒號(hào)分隔。如果對(duì)象內(nèi)部包含多個(gè)鍵值對(duì)撑蒜,每個(gè)鍵值對(duì)之間用逗號(hào)分隔歹啼。

varo = {p1:'Hello',p2:'World'};

對(duì)象的生成方法玄渗,通常有三種方法。除了像上面那樣直接使用大括號(hào)生成({})狸眼,還可以用new命令生成一個(gè)Object對(duì)象的實(shí)例藤树,或者使用Object.create方法生成。

varo1 = {};varo2 =newObject();varo3 =Object.create(null);

上面三行語句是等價(jià)的拓萌。一般來說岁钓,第一種采用大括號(hào)的寫法比較簡(jiǎn)潔,第二種采用構(gòu)造函數(shù)的寫法清晰地表示了意圖微王,第三種寫法一般用在需要對(duì)象繼承的場(chǎng)合屡限。關(guān)于第二種寫法,詳見《標(biāo)準(zhǔn)庫(kù)》一章的Object對(duì)象一節(jié)炕倘,第三種寫法詳見《面向?qū)ο缶幊獭芬徽隆?/p>

鍵名

對(duì)象的所有鍵名都是字符串钧大,所以加不加引號(hào)都可以。上面的代碼也可以寫成下面這樣罩旋。

varo = {'p':'Hello World'};

如果鍵名是數(shù)值拓型,會(huì)被自動(dòng)轉(zhuǎn)為字符串。

varo ={1:'a',3.2:'b',1e2:true,1e-2:true,.234:true,0xFF:true,};o// Object {//? 1: "a",//? 100: true,//? 255: true,//? 3.2: "b",//? 0.01: true,//? 0.234: true// }

但是瘸恼,如果鍵名不符合標(biāo)識(shí)名的條件(比如第一個(gè)字符為數(shù)字劣挫,或者含有空格或運(yùn)算符),也不是數(shù)字东帅,則必須加上引號(hào)压固,否則會(huì)報(bào)錯(cuò)。

varo = {'1p':"Hello World",'h w':"Hello World",'p+q':"Hello World"};

上面對(duì)象的三個(gè)鍵名靠闭,都不符合標(biāo)識(shí)名的條件帐我,所以必須加上引號(hào)。

注意愧膀,JavaScript的保留字可以不加引號(hào)當(dāng)作鍵名拦键。

varobj = {for:1,class:2};

屬性

對(duì)象的每一個(gè)“鍵名”又稱為“屬性”(property),它的“鍵值”可以是任何數(shù)據(jù)類型檩淋。如果一個(gè)屬性的值為函數(shù)芬为,通常把這個(gè)屬性稱為“方法”,它可以像函數(shù)那樣調(diào)用蟀悦。

varo = {p:function(x){return2* x;? }};o.p(1)// 2

上面的對(duì)象就有一個(gè)方法p媚朦,它就是一個(gè)函數(shù)。

對(duì)象的屬性之間用逗號(hào)分隔日戈,最后一個(gè)屬性后面可以加逗號(hào)(trailing comma)询张,也可以不加。

varo = {p:123,m:function(){ ... },}

上面的代碼中m屬性后面的那個(gè)逗號(hào)浙炼,有或沒有都不算錯(cuò)份氧。但是唯袄,ECMAScript 3不允許添加逗號(hào),所以如果要兼容老式瀏覽器(比如IE 8)蜗帜,那就不能加這個(gè)逗號(hào)越妈。

屬性可以動(dòng)態(tài)創(chuàng)建,不必在對(duì)象聲明時(shí)就指定钮糖。

varobj = {};obj.foo =123;obj.foo// 123

上面代碼中梅掠,直接對(duì)obj對(duì)象的foo屬性賦值,結(jié)果就在運(yùn)行時(shí)創(chuàng)建了foo屬性店归。

由于對(duì)象的方法就是函數(shù)阎抒,因此也有name屬性。

varobj = {m1:functionm1(){},m2:function(){}};obj.m1.name// m1obj.m2.name// undefined

對(duì)象的引用

如果不同的變量名指向同一個(gè)對(duì)象消痛,那么它們都是這個(gè)對(duì)象的引用且叁,也就是說指向同一個(gè)內(nèi)存地址。修改其中一個(gè)變量秩伞,會(huì)影響到其他所有變量逞带。

varo1 = {};varo2 = o1;o1.a =1;o2.a// 1o2.b =2;o1.b// 2

上面代碼中,o1和o2指向同一個(gè)對(duì)象纱新,因此為其中任何一個(gè)變量添加屬性展氓,另一個(gè)變量都可以讀寫該屬性。

此時(shí)脸爱,如果取消某一個(gè)變量對(duì)于原對(duì)象的引用遇汞,不會(huì)影響到另一個(gè)變量。

varo1 = {};varo2 = o1;o1 =1;o2// {}

上面代碼中簿废,o1和o2指向同一個(gè)對(duì)象空入,然后o1的值變?yōu)?,這時(shí)不會(huì)對(duì)o2產(chǎn)生影響族檬,o2還是指向原來的那個(gè)對(duì)象歪赢。

但是,這種引用只局限于對(duì)象单料,對(duì)于原始類型的數(shù)據(jù)則是傳值引用埋凯,也就是說,都是值的拷貝看尼。

varx =1;vary = x;x =2;y// 1

上面的代碼中递鹉,當(dāng)x的值發(fā)生變化后盟步,y的值并不變藏斩,這就表示y和x并不是指向同一個(gè)內(nèi)存地址。

表達(dá)式還是語句却盘?

對(duì)象采用大括號(hào)表示狰域,這導(dǎo)致了一個(gè)問題:如果行首是一個(gè)大括號(hào)媳拴,它到底是表達(dá)式還是語句?

{foo:1}

JavaScript引擎讀到上面這行代碼兆览,會(huì)發(fā)現(xiàn)可能有兩種含義屈溉。第一種可能是,這是一個(gè)表達(dá)式抬探,表示一個(gè)包含foo屬性的對(duì)象子巾;第二種可能是,這是一個(gè)語句小压,表示一個(gè)代碼區(qū)塊线梗,里面有一個(gè)標(biāo)簽foo,指向表達(dá)式123怠益。

為了避免這種歧義性仪搔,JavaScript規(guī)定,如果行首是大括號(hào)蜻牢,一律解釋為語句(即代碼塊)烤咧。如果要解釋為表達(dá)式(即對(duì)象),必須在大括號(hào)前加上圓括號(hào)抢呆。

({foo:1})

這種差異在eval語句中反映得最明顯煮嫌。

eval('{foo: 1}')// 123eval('({foo: 1})')// {foo: 123}

上面代碼中,如果沒有圓括號(hào)抱虐,eval將其理解為一個(gè)代碼塊立膛;加上圓括號(hào)以后,就理解成一個(gè)對(duì)象梯码。

屬性的操作

讀取屬性

讀取對(duì)象的屬性宝泵,有兩種方法,一種是使用點(diǎn)運(yùn)算符轩娶,還有一種是使用方括號(hào)運(yùn)算符儿奶。

varo = {p:'Hello World'};o.p// "Hello World"o['p']// "Hello World"

上面代碼分別采用點(diǎn)運(yùn)算符和方括號(hào)運(yùn)算符,讀取屬性p鳄抒。

請(qǐng)注意闯捎,如果使用方括號(hào)運(yùn)算符,鍵名必須放在引號(hào)里面许溅,否則會(huì)被當(dāng)作變量處理瓤鼻。但是,數(shù)字鍵可以不加引號(hào)贤重,因?yàn)闀?huì)被當(dāng)作字符串處理茬祷。

varo = {0.7:'Hello World'};o['0.7']// "Hello World"o[0.7]// "Hello World"

方括號(hào)運(yùn)算符內(nèi)部可以使用表達(dá)式。

o['hello'+' world']o[3+3]

數(shù)值鍵名不能使用點(diǎn)運(yùn)算符(因?yàn)闀?huì)被當(dāng)成小數(shù)點(diǎn))并蝗,只能使用方括號(hào)運(yùn)算符祭犯。

obj.0xFF// SyntaxError: Unexpected tokenobj[0xFF]// true

上面代碼的第一個(gè)表達(dá)式秸妥,對(duì)數(shù)值鍵名0xFF使用點(diǎn)運(yùn)算符,結(jié)果報(bào)錯(cuò)沃粗。第二個(gè)表達(dá)式使用方括號(hào)運(yùn)算符粥惧,結(jié)果就是正確的。

檢查變量是否聲明

如果讀取一個(gè)不存在的鍵最盅,會(huì)返回undefined突雪,而不是報(bào)錯(cuò)∥屑可以利用這一點(diǎn)挂签,來檢查一個(gè)全局變量是否被聲明。

// 檢查a變量是否被聲明if(a) {...}// 報(bào)錯(cuò)if(window.a) {...}// 不報(bào)錯(cuò)if(window['a']) {...}// 不報(bào)錯(cuò)

上面的后二種寫法之所以不報(bào)錯(cuò)盼产,是因?yàn)樵跒g覽器環(huán)境饵婆,所有全局變量都是window對(duì)象的屬性。window.a的含義就是讀取window對(duì)象的a屬性戏售,如果該屬性不存在侨核,就返回undefined,并不會(huì)報(bào)錯(cuò)灌灾。

需要注意的是搓译,后二種寫法有漏洞,如果a屬性是一個(gè)空字符串(或其他對(duì)應(yīng)的布爾值為false的情況)锋喜,則無法起到檢查變量是否聲明的作用融撞。正確的做法是可以采用下面的寫法迷郑。

// 寫法一if(window.a ===undefined) {// ...}// 寫法二if('a'inwindow) {// ...}

屬性的賦值

點(diǎn)運(yùn)算符和方括號(hào)運(yùn)算符磺平,不僅可以用來讀取值力麸,還可以用來賦值。

o.p ='abc';o['p'] ='abc';

上面代碼分別使用點(diǎn)運(yùn)算符和方括號(hào)運(yùn)算符炉奴,對(duì)屬性p賦值逼庞。

JavaScript允許屬性的“后綁定”,也就是說瞻赶,你可以在任意時(shí)刻新增屬性赛糟,沒必要在定義對(duì)象的時(shí)候,就定義好屬性砸逊。

varo = {p:1};// 等價(jià)于varo = {};o.p =1;

查看所有屬性

查看一個(gè)對(duì)象本身的所有屬性璧南,可以使用Object.keys方法。

varo = {key1:1,key2:2};Object.keys(o);// ['key1', 'key2']

屬性的刪除

刪除一個(gè)屬性师逸,需要使用delete命令司倚。

varo = {p:1};Object.keys(o)// ["p"]deleteo.p// trueo.p// undefinedObject.keys(o)// []

上面代碼表示,一旦使用delete命令刪除某個(gè)屬性,再讀取該屬性就會(huì)返回undefined对湃,而且Object.keys方法返回的該對(duì)象的所有屬性中崖叫,也將不再包括該屬性遗淳。

麻煩的是拍柒,如果刪除一個(gè)不存在的屬性,delete不報(bào)錯(cuò)屈暗,而且返回true拆讯。

varo = {};deleteo.p// true

上面代碼表示,delete命令只能用來保證某個(gè)屬性的值為undefined养叛,而無法保證該屬性是否真的存在种呐。

只有一種情況,delete命令會(huì)返回false弃甥,那就是該屬性存在爽室,且不得刪除。

varo =Object.defineProperty({},"p", {value:123,configurable:false});o.p// 123deleteo.p// false

上面代碼之中淆攻,o對(duì)象的p屬性是不能刪除的阔墩,所以delete命令返回false(關(guān)于Object.defineProperty方法的介紹,請(qǐng)看《標(biāo)準(zhǔn)庫(kù)》一章的Object對(duì)象章節(jié))瓶珊。

另外啸箫,需要注意的是,delete命令只能刪除對(duì)象本身的屬性伞芹,不能刪除繼承的屬性(關(guān)于繼承參見《面向?qū)ο缶幊獭芬还?jié))忘苛。delete命令也不能刪除var命令聲明的變量,只能用來刪除屬性唱较。

in運(yùn)算符

in運(yùn)算符用于檢查對(duì)象是否包含某個(gè)屬性(注意扎唾,檢查的是鍵名,不是鍵值)南缓,如果包含就返回true稽屏,否則返回false。

varo = {p:1};'p'ino// true

在JavaScript語言中西乖,所有全局變量都是頂層對(duì)象(瀏覽器的頂層對(duì)象就是window對(duì)象)的屬性狐榔,因此可以用in運(yùn)算符判斷,一個(gè)全局變量是否存在获雕。

// 假設(shè)變量x未定義// 寫法一:報(bào)錯(cuò)if(x) {return1; }// 寫法二:不正確if(window.x) {return1; }// 寫法三:正確if('x'inwindow) {return1; }

上面三種寫法之中薄腻,如果x不存在,第一種寫法會(huì)報(bào)錯(cuò)届案;如果x的值對(duì)應(yīng)布爾值false(比如x等于空字符串)庵楷,第二種寫法無法得到正確結(jié)果;只有第三種寫法,才能正確判斷變量x是否存在尽纽。

in運(yùn)算符的一個(gè)問題是咐蚯,它不能識(shí)別對(duì)象繼承的屬性。

varo =newObject();o.hasOwnProperty('toString')// false'toString'ino// true

上面代碼中弄贿,toString方法不是對(duì)象o自身的屬性春锋,而是繼承的屬性,hasOwnProperty方法可以說明這一點(diǎn)差凹。但是期奔,in運(yùn)算符不能識(shí)別,對(duì)繼承的屬性也返回true危尿。

for...in循環(huán)

for...in循環(huán)用來遍歷一個(gè)對(duì)象的全部屬性呐萌。

varo = {a:1,b:2,c:3};for(variino) {console.log(o[i]);}// 1// 2// 3

下面是一個(gè)使用for...in循環(huán),進(jìn)行數(shù)組賦值的例子谊娇。

varprops = [], i =0;for(props[i++]in{x:1,y:2});props// ['x', 'y']

注意肺孤,for...in循環(huán)遍歷的是對(duì)象所有可enumberable的屬性,其中不僅包括定義在對(duì)象本身的屬性济欢,還包括對(duì)象繼承的屬性赠堵。

// name 是 Person 本身的屬性functionPerson(name){this.name = name;}// describe是Person.prototype的屬性Person.prototype.describe =function(){return'Name: '+this.name;};varperson =newPerson('Jane');// for...in循環(huán)會(huì)遍歷實(shí)例自身的屬性(name),// 以及繼承的屬性(describe)for(varkeyinperson) {console.log(key);}// name// describe

上面代碼中船逮,name是對(duì)象本身的屬性顾腊,describe是對(duì)象繼承的屬性,for...in循環(huán)的遍歷會(huì)包括這兩者挖胃。

如果只想遍歷對(duì)象本身的屬性杂靶,可以使用hasOwnProperty方法,在循環(huán)內(nèi)部做一個(gè)判斷酱鸭。

for(varkeyinperson) {if(person.hasOwnProperty(key)) {console.log(key);? }}// name

為了避免這一點(diǎn)吗垮,可以新建一個(gè)繼承null的對(duì)象。由于null沒有任何屬性凹髓,所以新對(duì)象也就不會(huì)有繼承的屬性了烁登。

with語句

with語句的格式如下:

with(object) {? statements;}

它的作用是操作同一個(gè)對(duì)象的多個(gè)屬性時(shí),提供一些書寫的方便蔚舀。

// 例一with(o) {? p1 =1;? p2 =2;}// 等同于o.p1 =1;o.p2 =2;// 例二with(document.links[0]){console.log(href);console.log(title);console.log(style);}// 等同于console.log(document.links[0].href);console.log(document.links[0].title);console.log(document.links[0].style);

注意饵沧,with區(qū)塊內(nèi)部的變量,必須是當(dāng)前對(duì)象已經(jīng)存在的屬性赌躺,否則會(huì)創(chuàng)造一個(gè)當(dāng)前作用域的全局變量狼牺。這是因?yàn)閣ith區(qū)塊沒有改變作用域,它的內(nèi)部依然是當(dāng)前作用域礼患。

varo = {};with(o) {? x ="abc";}o.x// undefinedx// "abc"

上面代碼中是钥,對(duì)象o沒有屬性x掠归,所以with區(qū)塊內(nèi)部對(duì)x的操作,等于創(chuàng)造了一個(gè)全局變量x悄泥。正確的寫法應(yīng)該是虏冻,先定義對(duì)象o的屬性x,然后在with區(qū)塊內(nèi)操作它弹囚。

varo = {};o.x =1;with(o) {? x =2;}o.x// 2

這是with語句的一個(gè)很大的弊病厨相,就是綁定對(duì)象不明確。

with(o) {console.log(x);}

單純從上面的代碼塊余寥,根本無法判斷x到底是全局變量领铐,還是o對(duì)象的一個(gè)屬性悯森。這非常不利于代碼的除錯(cuò)和模塊化宋舷,編譯器也無法對(duì)這段代碼進(jìn)行優(yōu)化,只能留到運(yùn)行時(shí)判斷瓢姻,這就拖慢了運(yùn)行速度祝蝠。因此,建議不要使用with語句幻碱,可以考慮用一個(gè)臨時(shí)變量代替with绎狭。

with(o1.o2.o3) {console.log(p1 + p2);}// 可以寫成vartemp = o1.o2.o3;console.log(temp.p1 + temp.p2);

with語句少數(shù)有用場(chǎng)合之一,就是替換模板變量褥傍。

varstr ='Hello <%= name %>!';

上面代碼是一個(gè)模板字符串儡嘶。假定有一個(gè)parser函數(shù),可以將這個(gè)字符串解析成下面的樣子恍风。

parser(str)// '"Hello ", name, "!"'

那么蹦狂,就可以利用with語句,進(jìn)行模板變量替換朋贬。

varstr ='Hello <%= name %>!';varo = {name:'Alice'};functiontmpl(str, obj){? str ='var p = [];'+'with (obj) {p.push('+ parser(str) +')};'+'return p;'varr = (newFunction('obj', str))(obj);returnr.join('');}tmpl(str, o)// "Hello Alice!"

上面代碼的核心邏輯是下面的部分凯楔。

varo = {name:'Alice'};varp = [];with(o) {? p.push('Hello ', name,'!');};p.join('')// "Hello Alice!"

上面代碼中,with區(qū)塊內(nèi)部锦募,模板變量name可以被對(duì)象o的屬性替換摆屯,而p依然是全局變量。這就是很多模板引擎的實(shí)現(xiàn)原理糠亩。

數(shù)組

數(shù)組的定義

數(shù)組(array)是按次序排列的一組值虐骑。每個(gè)值的位置都有編號(hào)(從0開始),整個(gè)數(shù)組用方括號(hào)表示赎线。

vararr = ['a','b','c'];

上面代碼中的a廷没、b、c就構(gòu)成一個(gè)數(shù)組氛驮,兩端的方括號(hào)是數(shù)組的標(biāo)志腕柜。a是0號(hào)位置,b是1號(hào)位置,c是2號(hào)位置盏缤。

除了在定義時(shí)賦值砰蠢,數(shù)組也可以先定義后賦值。

vararr = [];arr[0] ='a';arr[1] ='b';arr[2] ='c';

任何類型的數(shù)據(jù)唉铜,都可以放入數(shù)組台舱。

vararr = [? {a:1},? [1,2,3],function(){returntrue;}];arr[0]// Object {a: 1}arr[1]// [1, 2, 3]arr[2]// function (){return true;}

上面數(shù)組arr的3個(gè)成員依次是對(duì)象、數(shù)組潭流、函數(shù)竞惋。

如果數(shù)組的元素還是數(shù)組,就形成了多維數(shù)組灰嫉。

vara = [[1,2], [3,4]];a[0][1]// 2a[1][1]// 4

數(shù)組的本質(zhì)

本質(zhì)上拆宛,數(shù)組屬于一種特殊的對(duì)象。typeof運(yùn)算符會(huì)返回?cái)?shù)組的類型是object讼撒。

typeof[1,2,3]// "object"

上面代碼表明浑厚,typeof運(yùn)算符認(rèn)為數(shù)組的類型就是對(duì)象。

數(shù)組的特殊性體現(xiàn)在根盒,它的鍵名是按次序排列的一組整數(shù)(0钳幅,1,2...)炎滞。

vararr = ['a','b','c'];Object.keys(arr)// ["0", "1", "2"]

上面代碼中敢艰,Object.keys方法返回?cái)?shù)組的所有鍵名〔崛可以看到數(shù)組的鍵名就是整數(shù)0钠导、1、2击奶。

由于數(shù)組成員的鍵名是固定的辈双,因此數(shù)組不用為每個(gè)元素指定鍵名克饶,而對(duì)象的每個(gè)成員都必須指定鍵名漩怎。

JavaScript語言規(guī)定炕檩,對(duì)象的鍵名一律為字符串侯谁,所以定硝,數(shù)組的鍵名其實(shí)也是字符串肩民。之所以可以用數(shù)值讀取煤傍,是因?yàn)榉亲址逆I名會(huì)被轉(zhuǎn)為字符串喷舀。

vararr = ['a','b','c'];arr['0']// 'a'arr[0]// 'a'

上面代碼分別用數(shù)值和字符串作為鍵名担映,結(jié)果都能讀取數(shù)組废士。原因是數(shù)值鍵名被自動(dòng)轉(zhuǎn)為了字符串。

需要注意的是蝇完,這一條在賦值時(shí)也成立官硝。如果一個(gè)值可以被轉(zhuǎn)換為整數(shù)矗蕊,則以該值為鍵名,等于以對(duì)應(yīng)的整數(shù)為鍵名氢架。

vara = [];a['1000'] ='abc';a[1000]// 'abc'a[1.00] =6;a[1]// 6

上面代碼表明傻咖,由于字符串“1000”和浮點(diǎn)數(shù)1.00都可以轉(zhuǎn)換為整數(shù),所以視同為整數(shù)鍵賦值岖研。

上一節(jié)說過卿操,對(duì)象有兩種讀取成員的方法:“點(diǎn)”結(jié)構(gòu)(object.key)和方括號(hào)結(jié)構(gòu)(object[key])。但是孙援,對(duì)于數(shù)值的鍵名害淤,不能使用點(diǎn)結(jié)構(gòu)。

vararr = [1,2,3];arr.0// SyntaxError

上面代碼中拓售,arr.0的寫法不合法窥摄,因?yàn)閱为?dú)的數(shù)值不能作為標(biāo)識(shí)符(identifier)。所以邻辉,數(shù)組成員只能用方括號(hào)arr[0]表示(方括號(hào)是運(yùn)算符溪王,可以接受數(shù)值)腮鞍。

length屬性

數(shù)組的length屬性值骇,返回?cái)?shù)組的成員數(shù)量。

['a','b','c'].length// 3

JavaScript使用一個(gè)32位整數(shù)移国,保存數(shù)組的元素個(gè)數(shù)吱瘩。這意味著,數(shù)組成員最多只有4294967295個(gè)(232-1)個(gè)迹缀,也就是說length屬性的最大值就是4294967295使碾。

數(shù)組的length屬性與對(duì)象的length屬性有區(qū)別,只要是數(shù)組祝懂,就一定有l(wèi)ength屬性票摇,而對(duì)象不一定有。而且砚蓬,數(shù)組的length屬性是一個(gè)動(dòng)態(tài)的值矢门,等于鍵名中的最大整數(shù)加上1。

vararr = ['a','b'];arr.length// 2arr[2] ='c';arr.length// 3arr[9] ='d';arr.length// 10arr[1000] ='e';arr.length// 1001

上面代碼表示灰蛙,數(shù)組的數(shù)字鍵不需要連續(xù)祟剔,length屬性的值總是比最大的那個(gè)整數(shù)鍵大1。另外摩梧,這也表明數(shù)組是一種動(dòng)態(tài)的數(shù)據(jù)結(jié)構(gòu)物延,可以隨時(shí)增減數(shù)組的成員。

length屬性是可寫的仅父。如果人為設(shè)置一個(gè)小于當(dāng)前成員個(gè)數(shù)的值叛薯,該數(shù)組的成員會(huì)自動(dòng)減少到length設(shè)置的值浑吟。

vararr = ['a','b','c'];arr.length// 3arr.length =2;arr// ["a", "b"]

上面代碼表示,當(dāng)數(shù)組的length屬性設(shè)為2(即最大的整數(shù)鍵只能是1)那么整數(shù)鍵2(值為c)就已經(jīng)不在數(shù)組中了耗溜,被自動(dòng)刪除了买置。

將數(shù)組清空的一個(gè)有效方法,就是將length屬性設(shè)為0强霎。

vararr = ['a','b','c'];arr.length =0;arr// []

如果人為設(shè)置length大于當(dāng)前元素個(gè)數(shù)忿项,則數(shù)組的成員數(shù)量會(huì)增加到這個(gè)值,新增的位置都是空位城舞。

vara = ['a'];a.length =3;a[1]// undefined

上面代碼表示轩触,當(dāng)length屬性設(shè)為大于數(shù)組個(gè)數(shù)時(shí),讀取新增的位置都會(huì)返回undefined家夺。

如果人為設(shè)置length為不合法的值脱柱,JavaScript會(huì)報(bào)錯(cuò)。

// 設(shè)置負(fù)值[].length =-1// RangeError: Invalid array length// 數(shù)組元素個(gè)數(shù)大于等于2的32次方[].length =Math.pow(2,32)// RangeError: Invalid array length// 設(shè)置字符串[].length ='abc'// RangeError: Invalid array length

值得注意的是拉馋,由于數(shù)組本質(zhì)上是對(duì)象的一種榨为,所以我們可以為數(shù)組添加屬性,但是這不影響length屬性的值煌茴。

vara = [];a['p'] ='abc';a.length// 0a[2.1] ='abc';a.length// 0

上面代碼將數(shù)組的鍵分別設(shè)為字符串和小數(shù)随闺,結(jié)果都不影響length屬性。因?yàn)槁琹ength屬性的值就是等于最大的數(shù)字鍵加1矩乐,而這個(gè)數(shù)組沒有整數(shù)鍵,所以length屬性保持為0回论。

類似數(shù)組的對(duì)象

在JavaScript中散罕,有些對(duì)象被稱為“類似數(shù)組的對(duì)象”(array-like object)。意思是傀蓉,它們看上去很像數(shù)組欧漱,可以使用length屬性,但是它們并不是數(shù)組葬燎,所以無法使用一些數(shù)組的方法误甚。

下面就是一個(gè)類似數(shù)組的對(duì)象。

varobj = {0:'a',1:'b',2:'c',length:3};obj[0]// 'a'obj[2]// 'c'obj.length// 3

上面代碼的變量obj是一個(gè)對(duì)象萨蚕,但是看上去跟數(shù)組很像靶草。所以只要有數(shù)字鍵和length屬性,就是一個(gè)類似數(shù)組的對(duì)象岳遥。當(dāng)然奕翔,變量obj無法使用數(shù)組特有的一些方法,比如pop和push方法浩蓉。而且派继,length屬性不是動(dòng)態(tài)值宾袜,不會(huì)隨著成員的變化而變化。

varobj = {length:0};obj[3] ='d';obj.length// 0

上面代碼為對(duì)象obj添加了一個(gè)數(shù)字鍵驾窟,但是length屬性沒變庆猫。這就說明了obj不是數(shù)組。

典型的類似數(shù)組的對(duì)象是函數(shù)的arguments對(duì)象绅络,以及大多數(shù)DOM元素集月培,還有字符串。

// arguments對(duì)象functionargs(){returnarguments}vararrayLike = args('a','b');arrayLike[0]// 'a'arrayLike.length// 2arrayLikeinstanceofArray// false// DOM元素集varelts =document.getElementsByTagName('h3');elts.length// 3eltsinstanceofArray// false// 字符串'abc'[1]// 'b''abc'.length// 3'abc'instanceofArray// false

數(shù)組的slice方法將類似數(shù)組的對(duì)象恩急,變成真正的數(shù)組杉畜。

vararr =Array.prototype.slice.call(arrayLike);

遍歷類似數(shù)組的對(duì)象,可以采用for循環(huán)衷恭,也可以采用數(shù)組的forEach方法此叠。

// for循環(huán)functionlogArgs(){for(vari =0; i

由于字符串也是類似數(shù)組的對(duì)象,所以也可以用Array.prototype.forEach.call遍歷随珠。

Array.prototype.forEach.call('abc',function(chr){console.log(chr);});// a// b// c

in運(yùn)算符

檢查某個(gè)鍵名是否存在的運(yùn)算符in灭袁,適用于對(duì)象掂骏,也適用于數(shù)組腌零。

2in['a','b','c']// true'2'in['a','b','c']// true

上面代碼表明,數(shù)組存在鍵名為2的鍵族跛。由于鍵名都是字符串烤芦,所以數(shù)值2會(huì)自動(dòng)轉(zhuǎn)成字符串举娩。

for...in循環(huán)和數(shù)組的遍歷

使用for...in循環(huán),可以遍歷數(shù)組的所有元素构罗。

vara = [1,2,3];for(variina) {console.log(a[i]);}// 1// 2// 3

需要注意的是,for...in會(huì)遍歷數(shù)組所有的鍵智玻,即使是非數(shù)字鍵遂唧。

vara = [1,2,3];a.foo =true;for(varkeyina) {console.log(key);}// 0// 1// 2// foo

上面代碼在遍歷數(shù)組時(shí),也遍歷到了非整數(shù)鍵foo吊奢。所以盖彭,使用for...in遍歷數(shù)組的時(shí)候,一定要小心页滚。

其他的數(shù)組遍歷方法召边,就是使用length屬性,結(jié)合for循環(huán)或者while循環(huán)裹驰。

// for循環(huán)vara = [1,2,3];for(vari =0; i < a.length; i++) {console.log(a[i]);}// while循環(huán)vari =0;while(i < a.length) {console.log(a[i]);? i++;}varl = a.length;while(l--) {console.log(a[l]);}

上面代碼是三種遍歷數(shù)組的寫法隧熙。最后一種寫法是逆向遍歷,即從最后一個(gè)元素向第一個(gè)元素遍歷幻林。

數(shù)組的forEach方法贞盯,也可以用來遍歷數(shù)組音念,詳見《標(biāo)準(zhǔn)庫(kù)》一章的Array對(duì)象部分。

varcolors = ['red','green','blue'];colors.forEach(function(color){console.log(color);});

數(shù)組的空位

當(dāng)數(shù)組的某個(gè)位置是空元素躏敢,即兩個(gè)逗號(hào)之間沒有任何值闷愤,我們稱該數(shù)組存在空位(hole)。

vara = [1, ,1];a.length// 3

上面代碼表明件余,數(shù)組的空位不影響length屬性讥脐。

需要注意的是,如果最后一個(gè)元素后面有逗號(hào)啼器,并不會(huì)產(chǎn)生空位攘烛。也就是說,有沒有這個(gè)逗號(hào)镀首,結(jié)果都是一樣的坟漱。

vara = [1,2,3,];a.length// 3a// [1, 2, 3]

上面代碼中,數(shù)組最后一個(gè)成員后面有一個(gè)逗號(hào)更哄,這不影響length屬性的值芋齿,與沒有這個(gè)逗號(hào)時(shí)效果一樣。

數(shù)組的空位是可以讀取的成翩,返回undefined觅捆。

vara = [, , ,];a[1]// undefined

使用delete命令刪除一個(gè)值,會(huì)形成空位麻敌。

vara = [1,2,3];deletea[1];a[1]// undefined

delete命令不影響length屬性栅炒。

vara = [1,2,3];deletea[1];deletea[2];a.length// 3

上面代碼用delete命令刪除了兩個(gè)鍵,對(duì)length屬性沒有影響术羔。也就是說赢赊,length屬性不過濾空位。所以级历,使用length屬性進(jìn)行數(shù)組遍歷释移,一定要非常小心。

數(shù)組的某個(gè)位置是空位寥殖,與某個(gè)位置是undefined玩讳,是不一樣的。如果是空位嚼贡,使用數(shù)組的forEach方法熏纯、for...in結(jié)構(gòu)、以及Object.keys方法進(jìn)行遍歷粤策,空位都會(huì)被跳過樟澜。

vara = [, , ,];a.forEach(function(x, i){console.log(i +'. '+ x);})// 不產(chǎn)生任何輸出for(variina) {console.log(i);}// 不產(chǎn)生任何輸出Object.keys(a)// []

如果某個(gè)位置是undefined,遍歷的時(shí)候就不會(huì)被跳過掐场。

vara = [undefined,undefined,undefined];a.forEach(function(x, i){console.log(i +'. '+ x);});// 0. undefined// 1. undefined// 2. undefinedfor(variina) {console.log(i);}// 0// 1// 2Object.keys(a)// ['0', '1', '2']

這就是說往扔,空位就是數(shù)組沒有這個(gè)元素贩猎,所以不會(huì)被遍歷到,而undefined則表示數(shù)組有這個(gè)元素萍膛,值是undefined吭服,所以遍歷不會(huì)跳過。

函數(shù)

概述

函數(shù)就是一段預(yù)先設(shè)置的代碼塊蝗罗,可以反復(fù)調(diào)用艇棕,根據(jù)輸入?yún)?shù)的不同,返回不同的值串塑。

JavaScript有三種方法沼琉,可以聲明一個(gè)函數(shù)。

函數(shù)的聲明

(1)function命令

function命令聲明的代碼區(qū)塊桩匪,就是一個(gè)函數(shù)打瘪。function命令后面是函數(shù)名,函數(shù)名后面是一對(duì)圓括號(hào)傻昙,里面是傳入函數(shù)的參數(shù)闺骚。函數(shù)體放在大括號(hào)里面。

functionprint(s){console.log(s);}

上面的代碼命名了一個(gè)print函數(shù)妆档,以后使用print()這種形式僻爽,就可以調(diào)用相應(yīng)的代碼。這叫做函數(shù)的聲明(Function Declaration)贾惦。

(2)函數(shù)表達(dá)式

除了用function命令聲明函數(shù)胸梆,還可以采用變量賦值的寫法。

varprint =function(s){console.log(s);};

這種寫法將一個(gè)匿名函數(shù)賦值給變量须板。這時(shí)碰镜,這個(gè)匿名函數(shù)又稱函數(shù)表達(dá)式(Function Expression),因?yàn)橘x值語句的等號(hào)右側(cè)只能放表達(dá)式逼纸。

采用函數(shù)表達(dá)式聲明函數(shù)時(shí)洋措,function命令后面不帶有函數(shù)名。如果加上函數(shù)名杰刽,該函數(shù)名只在函數(shù)體內(nèi)部有效,在函數(shù)體外部無效王滤。

varprint =functionx(){console.log(typeofx);};x// ReferenceError: x is not definedprint()// function

上面代碼在函數(shù)表達(dá)式中贺嫂,加入了函數(shù)名x。這個(gè)x只在函數(shù)體內(nèi)部可用雁乡,指代函數(shù)表達(dá)式本身第喳,其他地方都不可用。這種寫法的用處有兩個(gè)踱稍,一是可以在函數(shù)體內(nèi)部調(diào)用自身曲饱,二是方便除錯(cuò)(除錯(cuò)工具顯示函數(shù)調(diào)用棧時(shí)悠抹,將顯示函數(shù)名,而不再顯示這里是一個(gè)匿名函數(shù))扩淀。因此楔敌,下面的形式聲明函數(shù)也非常常見。

varf =functionf(){};

需要注意的是驻谆,函數(shù)的表達(dá)式需要在語句的結(jié)尾加上分號(hào)卵凑,表示語句結(jié)束紧唱。而函數(shù)的聲明在結(jié)尾的大括號(hào)后面不用加分號(hào)花竞「铺總的來說班套,這兩種聲明函數(shù)的方式癣猾,差別很細(xì)微(參閱后文《變量提升》一節(jié))削彬,這里可以近似認(rèn)為是等價(jià)的烟具。

(3)Function構(gòu)造函數(shù)

還有第三種聲明函數(shù)的方式:Function構(gòu)造函數(shù)袄膏。

varadd =newFunction('x','y','return (x + y)');// 等同于functionadd(x, y){return(x + y);}

在上面代碼中勒魔,F(xiàn)unction構(gòu)造函數(shù)接受三個(gè)參數(shù)甫煞,除了最后一個(gè)參數(shù)是add函數(shù)的“函數(shù)體”,其他參數(shù)都是add函數(shù)的參數(shù)沥邻。如果只有一個(gè)參數(shù)危虱,該參數(shù)就是函數(shù)體。

varfoo =newFunction('return "hello world"');// 等同于functionfoo(){return"hello world";}

Function構(gòu)造函數(shù)可以不使用new命令唐全,返回結(jié)果完全一樣埃跷。

總的來說,這種聲明函數(shù)的方式非常不直觀邮利,幾乎無人使用弥雹。

函數(shù)的重復(fù)聲明

如果同一個(gè)函數(shù)被多次聲明,后面的聲明就會(huì)覆蓋前面的聲明延届。

functionf(){console.log(1);}f()// 2functionf(){console.log(2);}f()// 2

上面代碼中剪勿,后一次的函數(shù)聲明覆蓋了前面一次。而且方庭,由于函數(shù)名的提升(參見下文)厕吉,前一次聲明在任何時(shí)候都是無效的,這一點(diǎn)要特別注意械念。

圓括號(hào)運(yùn)算符头朱,return語句和遞歸

調(diào)用函數(shù)時(shí),要使用圓括號(hào)運(yùn)算符龄减。圓括號(hào)之中项钮,可以加入函數(shù)的參數(shù)。

functionadd(x, y){returnx + y;}add(1,1)// 2

上面代碼中,函數(shù)名后面緊跟一對(duì)圓括號(hào)烁巫,就會(huì)調(diào)用這個(gè)函數(shù)署隘。

函數(shù)體內(nèi)部的return語句,表示返回亚隙。JavaScript引擎遇到return語句磁餐,就直接返回return后面的那個(gè)表達(dá)式的值,后面即使還有語句恃鞋,也不會(huì)得到執(zhí)行崖媚。也就是說,return語句所帶的那個(gè)表達(dá)式恤浪,就是函數(shù)的返回值畅哑。return語句不是必需的,如果沒有的話水由,該函數(shù)就不返回任何值荠呐,或者說返回undefined。

函數(shù)可以調(diào)用自身砂客,這就是遞歸(recursion)泥张。下面就是通過遞歸,計(jì)算斐波那契數(shù)列的代碼鞠值。

functionfib(num){if(num >2) {returnfib(num -2) + fib(num -1);? }else{return1;? }}fib(6)// 8

上面代碼中媚创,fib函數(shù)內(nèi)部又調(diào)用了fib,計(jì)算得到斐波那契數(shù)列的第6個(gè)元素是8彤恶。

第一等公民

JavaScript的函數(shù)與其他數(shù)據(jù)類型(數(shù)值钞钙、字符串、布爾值等等)處于同等地位声离,可以使用其他數(shù)據(jù)類型的地方芒炼,就能使用函數(shù)。比如术徊,可以把函數(shù)賦值給變量和對(duì)象的屬性本刽,也可以當(dāng)作參數(shù)傳入其他函數(shù),或者作為函數(shù)的結(jié)果返回赠涮。

這表明子寓,函數(shù)與其他數(shù)據(jù)類型完全是平等的,所以又稱函數(shù)為第一等公民笋除。

functionadd(x, y){returnx + y;}// 將函數(shù)賦值給一個(gè)變量varoperator = add;// 將函數(shù)作為參數(shù)和返回值functiona(op){returnop;}a(add)(1,1)// 2

函數(shù)名的提升

JavaScript引擎將函數(shù)名視同變量名别瞭,所以采用function命令聲明函數(shù)時(shí),整個(gè)函數(shù)會(huì)像變量聲明一樣株憾,被提升到代碼頭部。所以,下面的代碼不會(huì)報(bào)錯(cuò)嗤瞎。

f();functionf(){}

表面上墙歪,上面代碼好像在聲明之前就調(diào)用了函數(shù)f。但是實(shí)際上贝奇,由于“變量提升”虹菲,函數(shù)f被提升到了代碼頭部,也就是在調(diào)用之前已經(jīng)聲明了掉瞳。但是毕源,如果采用賦值語句定義函數(shù),JavaScript就會(huì)報(bào)錯(cuò)陕习。

f();varf =function(){};// TypeError: undefined is not a function

上面的代碼等同于下面的形式霎褐。

varf;f();f =function(){};

上面代碼第二行,調(diào)用f的時(shí)候该镣,f只是被聲明了冻璃,還沒有被賦值,等于undefined损合,所以會(huì)報(bào)錯(cuò)省艳。因此,如果同時(shí)采用function命令和賦值語句聲明同一個(gè)函數(shù)嫁审,最后總是采用賦值語句的定義跋炕。

varf =function(){console.log('1');}functionf(){console.log('2');}f()// 1

不能在條件語句中聲明函數(shù)

根據(jù)ECMAScript的規(guī)范,不得在非函數(shù)的代碼塊中聲明函數(shù)律适,最常見的情況就是if和try語句辐烂。

if(foo) {functionx(){}}try{functionx(){}}catch(e) {console.log(e);}

上面代碼分別在if代碼塊和try代碼塊中聲明了兩個(gè)函數(shù),按照語言規(guī)范擦耀,這是不合法的棉圈。但是,實(shí)際情況是各家瀏覽器往往并不報(bào)錯(cuò)眷蜓,能夠運(yùn)行分瘾。

但是由于存在函數(shù)名的提升,所以在條件語句中聲明函數(shù)吁系,可能是無效的德召,這是非常容易出錯(cuò)的地方。

if(false){functionf(){}}f()// 不報(bào)錯(cuò)

上面代碼的原始意圖是不聲明函數(shù)f汽纤,但是由于f的提升上岗,導(dǎo)致if語句無效,所以上面的代碼不會(huì)報(bào)錯(cuò)蕴坪。要達(dá)到在條件語句中定義函數(shù)的目的肴掷,只有使用函數(shù)表達(dá)式敬锐。

if(false) {varf =function(){};}f()// undefined

函數(shù)的屬性和方法

name屬性

name屬性返回緊跟在function關(guān)鍵字之后的那個(gè)函數(shù)名。

functionf1(){}f1.name// 'f1'varf2 =function(){};f2.name// ''varf3 =functionmyName(){};f3.name// 'myName'

上面代碼中呆瞻,函數(shù)的name屬性總是返回緊跟在function關(guān)鍵字之后的那個(gè)函數(shù)名台夺。對(duì)于f2來說,返回空字符串痴脾,匿名函數(shù)的name屬性總是為空字符串颤介;對(duì)于f3來說鹏倘,返回函數(shù)表達(dá)式的名字(真正的函數(shù)名還是f3聋伦,myName這個(gè)名字只在函數(shù)體內(nèi)部可用)。

length屬性

length屬性返回函數(shù)預(yù)期傳入的參數(shù)個(gè)數(shù)玛臂,即函數(shù)定義之中的參數(shù)個(gè)數(shù)前域。

functionf(a, b){}f.length// 2

上面代碼定義了空函數(shù)f辕近,它的length屬性就是定義時(shí)的參數(shù)個(gè)數(shù)。不管調(diào)用時(shí)輸入了多少個(gè)參數(shù)话侄,length屬性始終等于2亏推。

length屬性提供了一種機(jī)制,判斷定義時(shí)和調(diào)用時(shí)參數(shù)的差異年堆,以便實(shí)現(xiàn)面向?qū)ο缶幊痰摹狈椒ㄖ剌d“(overload)吞杭。

toString()

函數(shù)的toString方法返回函數(shù)的源碼。

functionf(){? a();? b();? c();}f.toString()// function f() {//? a();//? b();//? c();// }

函數(shù)內(nèi)部的注釋也可以返回变丧。

functionf(){/*

? 這是一個(gè)

? 多行注釋

*/}f.toString()// "function f(){/*//? 這是一個(gè)//? 多行注釋// */}"

利用這一點(diǎn)芽狗,可以變相實(shí)現(xiàn)多行字符串。

varmultiline =function(fn){vararr = fn.toString().split('\n');returnarr.slice(1, arr.length -1).join('\n');};functionf(){/*

? 這是一個(gè)

? 多行注釋

*/}multiline(f.toString())// " 這是一個(gè)//? 多行注釋"

函數(shù)作用域

定義

作用域(scope)指的是變量存在的范圍痒蓬。Javascript只有兩種作用域:一種是全局作用域童擎,變量在整個(gè)程序中一直存在,所有地方都可以讀裙ド埂顾复;另一種是函數(shù)作用域,變量只在函數(shù)內(nèi)部存在鲁捏。

在函數(shù)外部聲明的變量就是全局變量(global variable)芯砸,它可以在函數(shù)內(nèi)部讀取。

varv =1;functionf(){console.log(v);}f()// 1

上面的代碼表明给梅,函數(shù)f內(nèi)部可以讀取全局變量v假丧。

在函數(shù)內(nèi)部定義的變量,外部無法讀取动羽,稱為“局部變量”(local variable)包帚。

functionf(){varv =1;}v// ReferenceError: v is not defined

上面代碼中,變量v在函數(shù)內(nèi)部定義运吓,所以是一個(gè)局部變量渴邦,函數(shù)之外就無法讀取疯趟。

函數(shù)內(nèi)部定義的變量,會(huì)在該作用域內(nèi)覆蓋同名全局變量几莽。

varv =1;functionf(){varv =2;console.log(v);}f()// 2v// 1

上面代碼中迅办,變量v同時(shí)在函數(shù)的外部和內(nèi)部有定義。結(jié)果章蚣,在函數(shù)內(nèi)部定義,局部變量v覆蓋了全局變量v姨夹。

注意纤垂,對(duì)于var命令來說,局部變量只能在函數(shù)內(nèi)部聲明磷账,在其他區(qū)塊中聲明峭沦,一律都是全局變量。

if(true) {varx =5;}console.log(x);// 5

上面代碼中逃糟,變量x在條件判斷區(qū)塊之中聲明吼鱼,結(jié)果就是一個(gè)全局變量,可以在區(qū)塊之外讀取绰咽。

函數(shù)內(nèi)部的變量提升

與全局作用域一樣菇肃,函數(shù)作用域內(nèi)部也會(huì)產(chǎn)生“變量提升”現(xiàn)象。var命令聲明的變量取募,不管在什么位置琐谤,變量聲明都會(huì)被提升到函數(shù)體的頭部。

functionfoo(x){if(x >100) {vartmp = x -100;? }}

上面的代碼等同于

functionfoo(x){vartmp;if(x >100) {? ? tmp = x -100;? };}

函數(shù)本身的作用域

函數(shù)本身也是一個(gè)值玩敏,也有自己的作用域斗忌。它的作用域綁定其聲明時(shí)所在的作用域。

vara =1;varx =function(){console.log(a);};functionf(){vara =2;? x();}f()// 1

上面代碼中旺聚,函數(shù)x是在函數(shù)f的外部聲明的织阳,所以它的作用域綁定外層,內(nèi)部變量a不會(huì)到函數(shù)f體內(nèi)取值砰粹,所以輸出1唧躲,而不是2。

很容易犯錯(cuò)的一點(diǎn)是伸眶,如果函數(shù)A調(diào)用函數(shù)B惊窖,卻沒考慮到函數(shù)B不會(huì)引用函數(shù)A的內(nèi)部變量。

varx =function(){console.log(a);};functiony(f){vara =2;? f();}y(x)// ReferenceError: a is not defined

上面代碼將函數(shù)x作為參數(shù)厘贼,傳入函數(shù)y界酒。但是,函數(shù)x是在函數(shù)y體外聲明的嘴秸,作用域綁定外層毁欣,因此找不到函數(shù)y的內(nèi)部變量a庇谆,導(dǎo)致報(bào)錯(cuò)。

參數(shù)

概述

函數(shù)運(yùn)行的時(shí)候凭疮,有時(shí)需要提供外部數(shù)據(jù)饭耳,不同的外部數(shù)據(jù)會(huì)得到不同的結(jié)果,這種外部數(shù)據(jù)就叫參數(shù)执解。

functionsquare(x){returnx * x;}square(2)// 4square(3)// 9

上式的x就是square函數(shù)的參數(shù)寞肖。每次運(yùn)行的時(shí)候,需要提供這個(gè)值衰腌,否則得不到結(jié)果新蟆。

參數(shù)的省略

函數(shù)參數(shù)不是必需的,Javascript允許省略參數(shù)右蕊。

functionf(a, b){returna;}f(1,2,3)// 1f(1)// 1f()// undefinedf.length// 2

上面代碼的函數(shù)f定義了兩個(gè)參數(shù)琼稻,但是運(yùn)行時(shí)無論提供多少個(gè)參數(shù)(或者不提供參數(shù)),JavaScript都不會(huì)報(bào)錯(cuò)饶囚。

被省略的參數(shù)的值就變?yōu)閡ndefined帕翻。需要注意的是,函數(shù)的length屬性與實(shí)際傳入的參數(shù)個(gè)數(shù)無關(guān)萝风,只反映函數(shù)預(yù)期傳入的參數(shù)個(gè)數(shù)嘀掸。

但是,沒有辦法只省略靠前的參數(shù)闹丐,而保留靠后的參數(shù)横殴。如果一定要省略靠前的參數(shù),只有顯式傳入undefined卿拴。

functionf(a, b){returna;}f( ,1)// SyntaxError: Unexpected token ,(…)f(undefined,1)// undefined

上面代碼中衫仑,如果省略第一個(gè)參數(shù),就會(huì)報(bào)錯(cuò)堕花。

默認(rèn)值

通過下面的方法文狱,可以為函數(shù)的參數(shù)設(shè)置默認(rèn)值。

functionf(a){? a = a ||1;returna;}f('')// 1f(0)// 1

上面代碼的||表示“或運(yùn)算”缘挽,即如果a有值瞄崇,則返回a,否則返回事先設(shè)定的默認(rèn)值(上例為1)壕曼。

這種寫法會(huì)對(duì)a進(jìn)行一次布爾運(yùn)算苏研,只有為true時(shí),才會(huì)返回a腮郊∧∧ⅲ可是,除了undefined以外轧飞,0衅鹿、空字符撒踪、null等的布爾值也是false。也就是說大渤,在上面的函數(shù)中制妄,不能讓a等于0或空字符串,否則在明明有參數(shù)的情況下泵三,也會(huì)返回默認(rèn)值耕捞。

為了避免這個(gè)問題雹食,可以采用下面更精確的寫法怀骤。

functionf(a){? (a !==undefined&& a !==null) ? a = a : a =1;returna;}f()// 1f('')// ""f(0)// 0

上面代碼中,函數(shù)f的參數(shù)是空字符或0拳氢,都不會(huì)觸發(fā)參數(shù)的默認(rèn)值纬霞。

傳遞方式

函數(shù)參數(shù)如果是原始類型的值(數(shù)值、字符串驱显、布爾值)诗芜,傳遞方式是傳值傳遞(passes by value)。這意味著埃疫,在函數(shù)體內(nèi)修改參數(shù)值伏恐,不會(huì)影響到函數(shù)外部。

varp =2;functionf(p){? p =3;}f(p);p// 2

上面代碼中栓霜,變量p是一個(gè)原始類型的值翠桦,傳入函數(shù)f的方式是傳值傳遞。因此胳蛮,在函數(shù)內(nèi)部销凑,p的值是原始值的拷貝,無論怎么修改仅炊,都不會(huì)影響到原始值斗幼。

但是,如果函數(shù)參數(shù)是復(fù)合類型的值(數(shù)組抚垄、對(duì)象蜕窿、其他函數(shù)),傳遞方式是傳址傳遞(pass by reference)呆馁。也就是說桐经,傳入函數(shù)的原始值的地址,因此在函數(shù)內(nèi)部修改參數(shù)浙滤,將會(huì)影響到原始值阴挣。

varobj = {p:1};functionf(o){? o.p =2;}f(obj);obj.p// 2

上面代碼中,傳入函數(shù)f的是參數(shù)對(duì)象obj的地址瓷叫。因此屯吊,在函數(shù)內(nèi)部修改obj的屬性p送巡,會(huì)影響到原始值。

注意盒卸,如果函數(shù)內(nèi)部修改的骗爆,不是參數(shù)對(duì)象的某個(gè)屬性,而是替換掉整個(gè)參數(shù)蔽介,這時(shí)不會(huì)影響到原始值摘投。

varobj = [1,2,3];functionf(o){? o = [2,3,4];}f(obj);obj// [1, 2, 3]

上面代碼中,在函數(shù)f內(nèi)部虹蓄,參數(shù)對(duì)象obj被整個(gè)替換成另一個(gè)值犀呼。這時(shí)不會(huì)影響到原始值。這是因?yàn)檗弊椋问絽?shù)(o)與實(shí)際參數(shù)obj存在一個(gè)賦值關(guān)系外臂。

// 函數(shù)f內(nèi)部o = obj;

上面代碼中,對(duì)o的修改都會(huì)反映在obj身上律胀。但是宋光,如果對(duì)o賦予一個(gè)新的值,就等于切斷了o與obj的聯(lián)系炭菌,導(dǎo)致此后的修改都不會(huì)影響到obj了罪佳。

某些情況下,如果需要對(duì)某個(gè)原始類型的變量黑低,獲取傳址傳遞的效果赘艳,可以將它寫成全局對(duì)象的屬性。

vara =1;functionf(p){window[p] =2;}f('a');a// 2

上面代碼中克握,變量a本來是傳值傳遞蕾管,但是寫成window對(duì)象的屬性,就達(dá)到了傳址傳遞的效果玛荞。

同名參數(shù)

如果有同名的參數(shù)娇掏,則取最后出現(xiàn)的那個(gè)值。

functionf(a, a){console.log(a);}f(1,2)// 2

上面的函數(shù)f有兩個(gè)參數(shù)勋眯,且參數(shù)名都是a婴梧。取值的時(shí)候,以后面的a為準(zhǔn)客蹋。即使后面的a沒有值或被省略塞蹭,也是以其為準(zhǔn)。

functionf(a, a){console.log(a);}f(1)// undefined

調(diào)用函數(shù)f的時(shí)候讶坯,沒有提供第二個(gè)參數(shù)番电,a的取值就變成了undefined。這時(shí),如果要獲得第一個(gè)a的值漱办,可以使用arguments對(duì)象这刷。

functionf(a, a){console.log(arguments[0]);}f(1)// 1

arguments對(duì)象

(1)定義

由于JavaScript允許函數(shù)有不定數(shù)目的參數(shù),所以我們需要一種機(jī)制娩井,可以在函數(shù)體內(nèi)部讀取所有參數(shù)暇屋。這就是arguments對(duì)象的由來。

arguments對(duì)象包含了函數(shù)運(yùn)行時(shí)的所有參數(shù)洞辣,arguments[0]就是第一個(gè)參數(shù)咐刨,arguments[1]就是第二個(gè)參數(shù),以此類推扬霜。這個(gè)對(duì)象只有在函數(shù)體內(nèi)部定鸟,才可以使用。

varf =function(one){console.log(arguments[0]);console.log(arguments[1]);console.log(arguments[2]);}f(1,2,3)// 1// 2// 3

arguments對(duì)象除了可以讀取參數(shù)著瓶,還可以為參數(shù)賦值(嚴(yán)格模式不允許這種用法)联予。

varf =function(a, b){arguments[0] =3;arguments[1] =2;returna + b;}f(1,1)// 5

可以通過arguments對(duì)象的length屬性,判斷函數(shù)調(diào)用時(shí)到底帶幾個(gè)參數(shù)材原。

functionf(){returnarguments.length;}f(1,2,3)// 3f(1)// 1f()// 0

(2)與數(shù)組的關(guān)系

需要注意的是躯泰,雖然arguments很像數(shù)組,但它是一個(gè)對(duì)象华糖。數(shù)組專有的方法(比如slice和forEach),不能在arguments對(duì)象上直接使用瘟裸。

但是客叉,可以通過apply方法,把a(bǔ)rguments作為參數(shù)傳進(jìn)去话告,這樣就可以讓arguments使用數(shù)組方法了兼搏。

// 用于apply方法myfunction.apply(obj,arguments).// 使用與另一個(gè)數(shù)組合并Array.prototype.concat.apply([1,2,3],arguments)

要讓arguments對(duì)象使用數(shù)組方法,真正的解決方法是將arguments轉(zhuǎn)為真正的數(shù)組沙郭。下面是兩種常用的轉(zhuǎn)換方法:slice方法和逐一填入新數(shù)組佛呻。

varargs =Array.prototype.slice.call(arguments);// orvarargs = [];for(vari =0; i

(3)callee屬性

arguments對(duì)象帶有一個(gè)callee屬性,返回它所對(duì)應(yīng)的原函數(shù)病线。

varf =function(one){console.log(arguments.callee === f);}f()// true

可以通過arguments.callee吓著,達(dá)到調(diào)用函數(shù)自身的目的。

函數(shù)的其他知識(shí)點(diǎn)

閉包

閉包(closure)是Javascript語言的一個(gè)難點(diǎn)送挑,也是它的特色绑莺,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)。

要理解閉包惕耕,首先必須理解變量作用域纺裁。前面提到,JavaScript有兩種作用域:全局作用域和函數(shù)作用域。函數(shù)內(nèi)部可以直接讀取全局變量欺缘。

varn =999;functionf1(){console.log(n);}f1()// 999

上面代碼中栋豫,函數(shù)f1可以讀取全局變量n。

但是谚殊,在函數(shù)外部無法讀取函數(shù)內(nèi)部聲明的變量丧鸯。

functionf1(){varn =999;}console.log(n)// Uncaught ReferenceError: n is not defined(

上面代碼中,函數(shù)f1內(nèi)部聲明的變量n络凿,函數(shù)外是無法讀取的骡送。

如果出于種種原因,需要得到函數(shù)內(nèi)的局部變量絮记。正常情況下摔踱,這是辦不到的,只有通過變通方法才能實(shí)現(xiàn)。那就是在函數(shù)的內(nèi)部,再定義一個(gè)函數(shù)笋粟。

functionf1(){varn =999;functionf2(){console.log(n);// 999}}

上面代碼中畦徘,函數(shù)f2就在函數(shù)f1內(nèi)部,這時(shí)f1內(nèi)部的所有局部變量耻矮,對(duì)f2都是可見的。但是反過來就不行,f2內(nèi)部的局部變量试躏,對(duì)f1就是不可見的。這就是JavaScript語言特有的"鏈?zhǔn)阶饔糜?結(jié)構(gòu)(chain scope)设褐,子對(duì)象會(huì)一級(jí)一級(jí)地向上尋找所有父對(duì)象的變量颠蕴。所以,父對(duì)象的所有變量助析,對(duì)子對(duì)象都是可見的犀被,反之則不成立。

既然f2可以讀取f1的局部變量外冀,那么只要把f2作為返回值寡键,我們不就可以在f1外部讀取它的內(nèi)部變量了嗎!

functionf1(){varn =999;functionf2(){console.log(n);? }returnf2;}varresult = f1();result();// 999

上面代碼中雪隧,函數(shù)f1的返回值就是函數(shù)f2西轩,由于f2可以讀取f1的內(nèi)部變量,所以就可以在外部獲得f1的內(nèi)部變量了膀跌。

閉包就是函數(shù)f2遭商,即能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。由于在JavaScript語言中捅伤,只有函數(shù)內(nèi)部的子函數(shù)才能讀取內(nèi)部變量劫流,因此可以把閉包簡(jiǎn)單理解成“定義在一個(gè)函數(shù)內(nèi)部的函數(shù)”。閉包最大的特點(diǎn),就是它可以“記住”誕生的環(huán)境祠汇,比如f2記住了它誕生的環(huán)境f1仍秤,所以從f2可以得到f1的內(nèi)部變量。在本質(zhì)上可很,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁诗力。

閉包的最大用處有兩個(gè),一個(gè)是可以讀取函數(shù)內(nèi)部的變量我抠,另一個(gè)就是讓這些變量始終保持在內(nèi)存中苇本,即閉包可以使得它誕生環(huán)境一直存在。請(qǐng)看下面的例子菜拓,閉包使得內(nèi)部變量記住上一次調(diào)用時(shí)的運(yùn)算結(jié)果瓣窄。

functioncreateIncrementor(start){returnfunction(){returnstart++;? };}varinc = createIncrementor(5);inc()// 5inc()// 6inc()// 7

上面代碼中,start是函數(shù)createIncrementor的內(nèi)部變量纳鼎。通過閉包俺夕,start的狀態(tài)被保留了,每一次調(diào)用都是在上一次調(diào)用的基礎(chǔ)上進(jìn)行計(jì)算贱鄙。從中可以看到劝贸,閉包inc使得函數(shù)createIncrementor的內(nèi)部環(huán)境,一直存在逗宁。所以映九,閉包可以看作是函數(shù)內(nèi)部作用域的一個(gè)接口。

為什么會(huì)這樣呢瞎颗?原因就在于inc始終在內(nèi)存中氯迂,而inc的存在依賴于createIncrementor,因此也始終在內(nèi)存中言缤,不會(huì)在調(diào)用結(jié)束后,被垃圾回收機(jī)制回收禁灼。

閉包的另一個(gè)用處管挟,是封裝對(duì)象的私有屬性和私有方法。

functionPerson(name){var_age;functionsetAge(n){? ? _age = n;? }functiongetAge(){return_age;? }return{name: name,getAge: getAge,setAge: setAge? };}varp1 = person('張三');p1.setAge(25);p1.getAge()// 25

上面代碼中弄捕,函數(shù)Person的內(nèi)部變量_age僻孝,通過閉包getAge和setAge,變成了返回對(duì)象p1的私有變量守谓。

注意穿铆,外層函數(shù)每次運(yùn)行,都會(huì)生成一個(gè)新的閉包斋荞,而這個(gè)閉包又會(huì)保留外層函數(shù)的內(nèi)部變量荞雏,所以內(nèi)存消耗很大。因此不能濫用閉包,否則會(huì)造成網(wǎng)頁的性能問題凤优。

立即調(diào)用的函數(shù)表達(dá)式(IIFE)

在Javascript中悦陋,一對(duì)圓括號(hào)()是一種運(yùn)算符,跟在函數(shù)名之后筑辨,表示調(diào)用該函數(shù)俺驶。比如,print()就表示調(diào)用print函數(shù)棍辕。

有時(shí)暮现,我們需要在定義函數(shù)之后,立即調(diào)用該函數(shù)楚昭。這時(shí)栖袋,你不能在函數(shù)的定義之后加上圓括號(hào),這會(huì)產(chǎn)生語法錯(cuò)誤哪替。

function(){/* code */}();// SyntaxError: Unexpected token (

產(chǎn)生這個(gè)錯(cuò)誤的原因是栋荸,function這個(gè)關(guān)鍵字即可以當(dāng)作語句,也可以當(dāng)作表達(dá)式凭舶。

// 語句functionf(){}// 表達(dá)式varf =functionf(){}

為了避免解析上的歧義晌块,JavaScript引擎規(guī)定,如果function關(guān)鍵字出現(xiàn)在行首帅霜,一律解釋成語句匆背。因此,JavaScript引擎看到行首是function關(guān)鍵字之后身冀,認(rèn)為這一段都是函數(shù)的定義钝尸,不應(yīng)該以圓括號(hào)結(jié)尾,所以就報(bào)錯(cuò)了搂根。

解決方法就是不要讓function出現(xiàn)在行首珍促,讓引擎將其理解成一個(gè)表達(dá)式。最簡(jiǎn)單的處理剩愧,就是將其放在一個(gè)圓括號(hào)里面猪叙。

(function(){/* code */}());// 或者(function(){/* code */})();

上面兩種寫法都是以圓括號(hào)開頭,引擎就會(huì)認(rèn)為后面跟的是一個(gè)表示式仁卷,而不是函數(shù)定義語句穴翩,所以就避免了錯(cuò)誤。這就叫做“立即調(diào)用的函數(shù)表達(dá)式”(Immediately-Invoked Function Expression)锦积,簡(jiǎn)稱IIFE芒帕。

注意,上面兩種寫法最后的分號(hào)都是必須的丰介。如果省略分號(hào)背蟆,遇到連著兩個(gè)IIFE鉴分,可能就會(huì)報(bào)錯(cuò)。

// 報(bào)錯(cuò)(function(){/* code */}())(function(){/* code */}())

上面代碼的兩行之間沒有分號(hào)淆储,JavaScript會(huì)將它們連在一起解釋冠场,將第二行解釋為第一行的參數(shù)。

推而廣之本砰,任何讓解釋器以表達(dá)式來處理函數(shù)定義的方法碴裙,都能產(chǎn)生同樣的效果,比如下面三種寫法点额。

vari =function(){return10; }();true&&function(){/* code */}();0,function(){/* code */}();

甚至像下面這樣寫舔株,也是可以的。

!function(){/* code */}();~function(){/* code */}();-function(){/* code */}();+function(){/* code */}();

new關(guān)鍵字也能達(dá)到這個(gè)效果还棱。

newfunction(){/* code */}newfunction(){/* code */}()// 只有傳遞參數(shù)時(shí)载慈,才需要最后那個(gè)圓括號(hào)

通常情況下,只對(duì)匿名函數(shù)使用這種“立即執(zhí)行的函數(shù)表達(dá)式”珍手。它的目的有兩個(gè):一是不必為函數(shù)命名办铡,避免了污染全局變量;二是IIFE內(nèi)部形成了一個(gè)單獨(dú)的作用域琳要,可以封裝一些外部無法讀取的私有變量寡具。

// 寫法一vartmp = newData;processData(tmp);storeData(tmp);// 寫法二(function(){vartmp = newData;? processData(tmp);? storeData(tmp);}());

上面代碼中,寫法二比寫法一更好稚补,因?yàn)橥耆苊饬宋廴救肿兞俊?/p>

eval命令

eval命令的作用是夺欲,將字符串當(dāng)作語句執(zhí)行块茁。

eval('var a = 1;');a// 1

上面代碼將字符串當(dāng)作語句運(yùn)行坎弯,生成了變量a露该。

放在eval中的字符串,應(yīng)該有獨(dú)自存在的意義乍惊,不能用來與eval以外的命令配合使用杜秸。舉例來說,下面的代碼將會(huì)報(bào)錯(cuò)润绎。

eval('return;');

由于eval沒有自己的作用域亩歹,都在當(dāng)前作用域內(nèi)執(zhí)行,因此可能會(huì)修改其他外部變量的值凡橱,造成安全問題。

vara =1;eval('a = 2');a// 2

上面代碼中亭姥,eval命令修改了外部變量a的值稼钩。由于這個(gè)原因,所以eval有安全風(fēng)險(xiǎn)达罗,如果無法做到作用域隔離坝撑,最好不要使用静秆。此外,eval的命令字符串不會(huì)得到JavaScript引擎的優(yōu)化巡李,運(yùn)行速度較慢抚笔,也是另一個(gè)不應(yīng)該使用它的理由。通常情況下侨拦,eval最常見的場(chǎng)合是解析JSON數(shù)據(jù)字符串殊橙,正確的做法是這時(shí)應(yīng)該使用瀏覽器提供的JSON.parse方法。

ECMAScript 5將eval的使用分成兩種情況狱从,像上面這樣的調(diào)用膨蛮,就叫做“直接使用”,這種情況下eval的作用域就是當(dāng)前作用域(即全局作用域或函數(shù)作用域)季研。另一種情況是敞葛,eval不是直接調(diào)用,而是“間接調(diào)用”与涡,此時(shí)eval的作用域總是全局作用域惹谐。

vara =1;functionf(){vara =2;vare =eval;? e('console.log(a)');}f()// 1

上面代碼中,eval是間接調(diào)用驼卖,所以即使它是在函數(shù)中氨肌,它的作用域還是全局作用域,因此輸出的a為全局變量款慨。

eval的間接調(diào)用的形式五花八門儒飒,只要不是直接調(diào)用,幾乎都屬于間接調(diào)用檩奠。

eval.call(null,'...')window.eval('...')(1,eval)('...')(eval,eval)('...')(1?eval:0)('...')(__ =eval)('...')vare =eval; e('...')(function(e){ e('...') })(eval)(function(e){returne })(eval)('...')(function(){arguments[0]('...') })(eval)this.eval('...')this['eval']('...')[eval][0]('...')eval.call(this,'...')eval('eval')('...')

上面這些形式都是eval的間接調(diào)用桩了,因此它們的作用域都是全局作用域。

與eval作用類似的還有Function構(gòu)造函數(shù)埠戳。利用它生成一個(gè)函數(shù)井誉,然后調(diào)用該函數(shù),也能將字符串當(dāng)作命令執(zhí)行整胃。

varjsonp ='foo({"id":42})';varf =newFunction("foo", jsonp );// 相當(dāng)于定義了如下函數(shù)// function f(foo) {//? foo({"id":42});// }f(function(json){console.log( json.id );// 42})

上面代碼中颗圣,jsonp是一個(gè)字符串,F(xiàn)unction構(gòu)造函數(shù)將這個(gè)字符串屁使,變成了函數(shù)體在岂。調(diào)用該函數(shù)的時(shí)候,jsonp就會(huì)執(zhí)行蛮寂。這種寫法的實(shí)質(zhì)是將代碼放到函數(shù)作用域執(zhí)行蔽午,避免對(duì)全局作用域造成影響。

運(yùn)算符

運(yùn)算符是處理數(shù)據(jù)的基本方法酬蹋,用來從現(xiàn)有數(shù)據(jù)得到新的數(shù)據(jù)及老。JavaScript與其他編程語言一樣抽莱,提供了多種運(yùn)算符。本節(jié)逐一介紹這些運(yùn)算符骄恶。

加法運(yùn)算符

加法運(yùn)算符(+)是最常見的運(yùn)算符之一食铐,但是使用規(guī)則卻相對(duì)復(fù)雜。因?yàn)樵贘avaScript語言里面僧鲁,這個(gè)運(yùn)算符可以完成兩種運(yùn)算虐呻,既可以處理算術(shù)的加法,也可以用作字符串連接悔捶,它們都寫成+铃慷。

// 加法1+1// 2true+true// 21+true// 2// 字符串連接'1'+'1'// "11"'1.1'+'1.1'// "1.11.1"

它的算法步驟如下。

如果運(yùn)算子是對(duì)象蜕该,先自動(dòng)轉(zhuǎn)成原始類型的值(即先執(zhí)行該對(duì)象的valueOf方法犁柜,如果結(jié)果還不是原始類型的值,再執(zhí)行toString方法堂淡;如果對(duì)象是Date實(shí)例馋缅,則先執(zhí)行toString方法)。

兩個(gè)運(yùn)算子都是原始類型的值以后绢淀,只要有一個(gè)運(yùn)算子是字符串萤悴,則兩個(gè)運(yùn)算子都轉(zhuǎn)為字符串,執(zhí)行字符串連接運(yùn)算皆的。

否則覆履,兩個(gè)運(yùn)算子都轉(zhuǎn)為數(shù)值,執(zhí)行加法運(yùn)算费薄。

下面是一些例子硝全。

'1'+ {foo:'bar'}// "1[object Object]"'1'+1// "11"'1'+true// "1true"'1'+ [1]// "11"

上面代碼中,由于運(yùn)算符左邊是一個(gè)字符串楞抡,導(dǎo)致右邊的運(yùn)算子都會(huì)先轉(zhuǎn)為字符串伟众,然后執(zhí)行字符串連接運(yùn)算。

這種由于參數(shù)不同召廷,而改變自身行為的現(xiàn)象凳厢,叫做“重載”(overload)。由于加法運(yùn)算符是運(yùn)行時(shí)決定到底執(zhí)行那種運(yùn)算竞慢,使用的時(shí)候必須很小心先紫。

'3'+4+5// "345"3+4+'5'// "75"

上面代碼中,運(yùn)算結(jié)果由于字符串的位置不同而不同筹煮。

下面的寫法遮精,可以用來將一個(gè)值轉(zhuǎn)為字符串。

x +''

上面代碼中寺谤,一個(gè)值加上空字符串仑鸥,會(huì)使得該值轉(zhuǎn)為字符串形式。

加法運(yùn)算符會(huì)將其他類型的值变屁,自動(dòng)轉(zhuǎn)為字符串眼俊,然后再執(zhí)行連接運(yùn)算。

[1,2] + [3]// "1,23"http:// 等同于String([1,2]) +String([3])// '1,2' + '3'

上面代碼中粟关,兩個(gè)數(shù)組相加疮胖,會(huì)先轉(zhuǎn)成字符串,然后再連接闷板。這種數(shù)據(jù)類型的自動(dòng)轉(zhuǎn)換澎灸,參見《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)。

加法運(yùn)算符一定有左右兩個(gè)運(yùn)算子遮晚,如果只有右邊一個(gè)運(yùn)算子性昭,就是另一個(gè)運(yùn)算符,叫做“數(shù)值運(yùn)算符”县遣。

+ -3// 等同于 +(-3)+1+2// 等同于 +(1 + 2)+'1'// 1

上面代碼中糜颠,數(shù)值運(yùn)算符用于返回右邊運(yùn)算子的數(shù)值形式,詳細(xì)解釋見下文萧求。

你可能會(huì)問其兴,如果只有左邊一個(gè)運(yùn)算子,會(huì)出現(xiàn)什么情況夸政?答案是會(huì)報(bào)錯(cuò)元旬。

1+// SyntaxError: Unexpected end of input

加法運(yùn)算符以外的其他算術(shù)運(yùn)算符(比如減法、除法和乘法)守问,都不會(huì)發(fā)生重載匀归。它們的規(guī)則是:所有運(yùn)算子一律轉(zhuǎn)為數(shù)值,再進(jìn)行相應(yīng)的數(shù)學(xué)運(yùn)算酪碘。

1-'2'// -11*'2'// 21/'2'// 0.5

上面代碼中朋譬,減法、除法和乘法運(yùn)算符,都是將字符串自動(dòng)轉(zhuǎn)為數(shù)值哺窄,然后再運(yùn)算枢里。

由于加法運(yùn)算符與其他算術(shù)運(yùn)算符的這種差異,會(huì)導(dǎo)致一些意想不到的結(jié)果狡赐,計(jì)算時(shí)要小心。

varnow =newDate();typeof(now +1)// "string"typeof(now -1)// "number"

上面代碼中钦幔,now是一個(gè)Date對(duì)象的實(shí)例枕屉。加法運(yùn)算時(shí),得到的是一個(gè)字符串鲤氢;減法運(yùn)算時(shí)搀擂,得到卻是一個(gè)數(shù)值西潘。

算術(shù)運(yùn)算符

JavaScript提供9個(gè)算術(shù)運(yùn)算符,用來完成基本的算術(shù)運(yùn)算哨颂。

加法運(yùn)算符(Addition):x + y

減法運(yùn)算符(Subtraction):x - y

乘法運(yùn)算符(Multiplication):x * y

除法運(yùn)算符(Division):x / y

余數(shù)運(yùn)算符(Remainder):x % y

自增運(yùn)算符(Increment):++x或者x++

自減運(yùn)算符(Decrement):--x或者x--

數(shù)值運(yùn)算符(Convert to number):+x

負(fù)數(shù)值運(yùn)算符(Negate):-x

減法喷市、乘法、除法運(yùn)算法比較單純威恼,就是執(zhí)行相應(yīng)的數(shù)學(xué)運(yùn)算品姓。下面介紹其他幾個(gè)算術(shù)運(yùn)算符。

余數(shù)運(yùn)算符

余數(shù)運(yùn)算符(%)返回前一個(gè)運(yùn)算子被后一個(gè)運(yùn)算子除箫措,所得的余數(shù)腹备。

12%5// 2

需要注意的是,運(yùn)算結(jié)果的正負(fù)號(hào)由第一個(gè)運(yùn)算子的正負(fù)號(hào)決定斤蔓。

-1%2// -11%-2// 1

為了得到正確的負(fù)數(shù)的余數(shù)值植酥,需要先使用絕對(duì)值函數(shù)。

// 錯(cuò)誤的寫法functionisOdd(n){returnn %2===1;}isOdd(-5)// falseisOdd(-4)// false// 正確的寫法functionisOdd(n){returnMath.abs(n %2) ===1;}isOdd(-5)// trueisOdd(-4)// false

余數(shù)運(yùn)算符還可以用于浮點(diǎn)數(shù)的運(yùn)算附迷。但是惧互,由于浮點(diǎn)數(shù)不是精確的值,無法得到完全準(zhǔn)確的結(jié)果喇伯。

6.5%2.1// 0.19999999999999973

自增和自減運(yùn)算符

自增和自減運(yùn)算符喊儡,是一元運(yùn)算符,只需要一個(gè)運(yùn)算子稻据。它們的作用是將運(yùn)算子首先轉(zhuǎn)為數(shù)值艾猜,然后加上1或者減去1。它們會(huì)修改原始變量捻悯。

varx =1;++x// 2x// 2--x// 1x// 1

上面代碼的變量x自增后匆赃,返回2,再進(jìn)行自減今缚,返回1算柳。這兩種情況都會(huì)使得,原始變量x的值發(fā)生改變姓言。

自增和自減運(yùn)算符有一個(gè)需要注意的地方瞬项,就是放在變量之后,會(huì)先返回變量操作前的值何荚,再進(jìn)行自增/自減操作囱淋;放在變量之前,會(huì)先進(jìn)行自增/自減操作餐塘,再返回變量操作后的值妥衣。

varx =1;vary =1;x++// 1++y// 2

上面代碼中,x是先返回當(dāng)前值,然后自增税手,所以得到1蜂筹;y是先自增,然后返回新的值芦倒,所以得到2狂票。

數(shù)值運(yùn)算符,負(fù)數(shù)值運(yùn)算符

數(shù)值運(yùn)算符(+)同樣使用加號(hào)熙暴,但是加法運(yùn)算符是二元運(yùn)算符(需要兩個(gè)操作數(shù)),它是一元運(yùn)算符(只需要一個(gè)操作數(shù))慌盯。

數(shù)值運(yùn)算符的作用在于可以將任何值轉(zhuǎn)為數(shù)值(與Number函數(shù)的作用相同)周霉。

+true// 1+[]// 0+{}// NaN

上面代碼表示,非數(shù)值類型的值經(jīng)過數(shù)值運(yùn)算符以后亚皂,都變成了數(shù)值(最后一行NaN也是數(shù)值)俱箱。具體的類型轉(zhuǎn)換規(guī)則,參見《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)灭必。

負(fù)數(shù)值運(yùn)算符(-)狞谱,也同樣具有將一個(gè)值轉(zhuǎn)為數(shù)值的功能,只不過得到的值正負(fù)相反禁漓。連用兩個(gè)負(fù)數(shù)值運(yùn)算符跟衅,等同于數(shù)值運(yùn)算符。

varx =1;-x// -1-(-x)// 1

上面代碼最后一行的圓括號(hào)不可少播歼,否則會(huì)變成遞減運(yùn)算符伶跷。

數(shù)值運(yùn)算符號(hào)和負(fù)數(shù)值運(yùn)算符,都會(huì)返回一個(gè)新的值秘狞,而不會(huì)改變?cè)甲兞康闹怠?/p>

賦值運(yùn)算符

賦值運(yùn)算符(Assignment Operators)用于給變量賦值叭莫。

最常見的賦值運(yùn)算符,當(dāng)然就是等號(hào)(=)烁试,表達(dá)式x = y表示將y的值賦給x雇初。

除此之外,JavaScript還提供其他11個(gè)復(fù)合的賦值運(yùn)算符减响。

x += y// 等同于 x = x + yx -= y// 等同于 x = x - yx *= y// 等同于 x = x * yx /= y// 等同于 x = x / yx %= y// 等同于 x = x % yx >>= y// 等同于 x = x >> yx <<= y// 等同于 x = x << yx >>>= y// 等同于 x = x >>> yx &= y// 等同于 x = x & yx |= y// 等同于 x = x | yx ^= y// 等同于 x = x ^ y

這些復(fù)合的賦值運(yùn)算符靖诗,都是先進(jìn)行指定運(yùn)算,然后將得到值返回給左邊的變量辩蛋。

比較運(yùn)算符

比較運(yùn)算符用于比較兩個(gè)值呻畸,然后返回一個(gè)布爾值,表示是否滿足比較條件悼院。

2>1// true

上面代碼計(jì)算2是否大于1伤为,返回true。

JavaScript一共提供了8個(gè)比較運(yùn)算符。

==相等

===嚴(yán)格相等

!=不相等

!==嚴(yán)格不相等

<小于

<=小于或等于

>大于

>=大于或等于

比較運(yùn)算符的算法

比較運(yùn)算符可以比較各種類型的值绞愚,不僅僅是數(shù)值叙甸。

它的算法步驟如下。

如果運(yùn)算子是對(duì)象位衩,先自動(dòng)轉(zhuǎn)成原始類型的值(即先執(zhí)行該對(duì)象的valueOf方法裆蒸,如果結(jié)果還不是原始類型的值,再執(zhí)行toString方法)糖驴。

如果兩個(gè)運(yùn)算子都是字符串僚祷,則按照字典順序比較(實(shí)際上是比較Unicode碼點(diǎn))。

否則贮缕,將兩個(gè)運(yùn)算子都轉(zhuǎn)成數(shù)值辙谜,再進(jìn)行比較。

下面是一個(gè)例子感昼。

[2] > [1]// true// 等同于 '[2]' > '[1]'[2] > [11]// true// 等同于 '[2]' > '[11]'

上面代碼是兩個(gè)數(shù)組的比較装哆,它們會(huì)先轉(zhuǎn)成原始類型的值(這個(gè)例子是字符串),再進(jìn)行比較定嗓。

5>'4'// truetrue>false// true2>true// true

上面代碼中蜕琴,字符串和布爾值都會(huì)先轉(zhuǎn)成數(shù)值,再進(jìn)行比較宵溅。

字符串的比較

字符串按照字典順序進(jìn)行比較凌简。

'cat'>'dog'// false'cat'>'catalog'// false

JavaScript引擎內(nèi)部首先比較首字符的Unicode編號(hào),如果相等恃逻,再比較第二個(gè)字符的Unicode編號(hào)号醉,以此類推。

'cat'>'Cat'// true'

上面代碼中辛块,小寫的c的Unicode編號(hào)(99)大于大寫的C的Unicode編號(hào)(67)畔派,所以返回true。

由于润绵,JavaScript的所有字符都有Unicode編號(hào)线椰,因此漢字也可以比較。

'大'>'小'// false

上面代碼中尘盼,“大”的Unicode編號(hào)是22823憨愉,“小”是23567,因此返回true卿捎。

嚴(yán)格相等運(yùn)算符

JavaScript提供兩個(gè)相等運(yùn)算符:==和===配紫。

簡(jiǎn)單說,它們的區(qū)別是相等運(yùn)算符(==)比較兩個(gè)值是否相等午阵,嚴(yán)格相等運(yùn)算符(===)比較它們是否為“同一個(gè)值”躺孝。如果兩個(gè)值不是同一類型享扔,嚴(yán)格相等運(yùn)算符(===)直接返回false,而相等運(yùn)算符(==)會(huì)將它們轉(zhuǎn)化成同一個(gè)類型植袍,再用嚴(yán)格相等運(yùn)算符進(jìn)行比較饲漾。

嚴(yán)格相等運(yùn)算符的運(yùn)算規(guī)則如下栅螟。

(1)不同類型的值

如果兩個(gè)值的類型不同,直接返回false稻艰。

1==="1"http:// falsetrue==="true"http:// false

上面代碼比較數(shù)值的1與字符串的“1”赐写、布爾值的true與字符串“true”邓嘹,因?yàn)轭愋筒煌眉郑Y(jié)果都是false透且。

(2)同一類的原始類型值

同一類型的原始類型的值(數(shù)值、字符串羽氮、布爾值)比較時(shí)应又,值相同就返回true,值不同就返回false乏苦。

1===0x1// true

上面代碼比較十進(jìn)制的1與十六進(jìn)制的1,因?yàn)轭愋秃椭刀枷嗤瓤穑祷豻rue汇荐。

需要注意的是,NaN與任何值都不相等(包括自身)盆繁。另外掀淘,正0等于負(fù)0。

NaN===NaN// false+0===-0// true

(3)同一類的復(fù)合類型值

兩個(gè)復(fù)合類型(對(duì)象油昂、數(shù)組革娄、函數(shù))的數(shù)據(jù)比較時(shí),不是比較它們的值是否相等冕碟,而是比較它們是否指向同一個(gè)對(duì)象拦惋。

({} === {})// false[] === []// false(function(){} ===function(){})// false

上面代碼分別比較兩個(gè)空對(duì)象、兩個(gè)空數(shù)組安寺、兩個(gè)空函數(shù)厕妖,結(jié)果都是不相等。原因是對(duì)于復(fù)合類型的值挑庶,嚴(yán)格相等運(yùn)算比較的是言秸,它們是否引用同一個(gè)內(nèi)存地址,而運(yùn)算符兩邊的空對(duì)象迎捺、空數(shù)組举畸、空函數(shù)的值,都存放在不同的內(nèi)存地址凳枝,結(jié)果當(dāng)然是false抄沮。另外,空對(duì)象的比較和空函數(shù)的比較,都放在括號(hào)內(nèi)合是,是為了避免JavaScript引擎把行首的空對(duì)象解釋成代碼塊了罪,把行首的空函數(shù)解釋成函數(shù)的定義。

如果兩個(gè)變量引用同一個(gè)對(duì)象聪全,則它們相等泊藕。

varv1 = {};varv2 = v1;v1 === v2// true

(4)undefined和null

undefined和null與自身嚴(yán)格相等。

undefined===undefined// truenull===null// true

由于變量聲明后默認(rèn)值是undefined难礼,因此兩個(gè)只聲明未賦值的變量是相等的娃圆。

varv1;varv2;v1 === v2// true

(5)嚴(yán)格不相等運(yùn)算符

嚴(yán)格相等運(yùn)算符有一個(gè)對(duì)應(yīng)的“嚴(yán)格不相等運(yùn)算符”(!==),兩者的運(yùn)算結(jié)果正好相反蛾茉。

1!=='1'// true

相等運(yùn)算符

相等運(yùn)算符比較相同類型的數(shù)據(jù)時(shí)讼呢,與嚴(yán)格相等運(yùn)算符完全一樣。

比較不同類型的數(shù)據(jù)時(shí)谦炬,相等運(yùn)算符會(huì)先將數(shù)據(jù)進(jìn)行類型轉(zhuǎn)換悦屏,然后再用嚴(yán)格相等運(yùn)算符比較。類型轉(zhuǎn)換規(guī)則如下键思。

(1)原始類型的值

原始類型的數(shù)據(jù)會(huì)轉(zhuǎn)換成數(shù)值類型再進(jìn)行比較础爬。

1==true// true// 等同于 1 === 10==false// true// 等同于 0 === 02==true// false// 等同于 2 === 12==false// false// 等同于 2 === 0'true'==true// false// 等同于 Number('true') === Number(true)// 等同于 NaN === 1''==0// true// 等同于 Number('') === 0// 等同于 0 === 0''==false// true// 等同于 Number('') === Number(false)// 等同于 0 === 0'1'==true// true// 等同于 Number('1') === Number(true)// 等同于 1 === 1'\n? 123? \t'==123// true// 因?yàn)樽址D(zhuǎn)為數(shù)字時(shí),省略前置和后置的空格

上面代碼將字符串和布爾值都轉(zhuǎn)為數(shù)值吼鳞,然后再進(jìn)行比較看蚜。字符串與布爾值的類型轉(zhuǎn)換規(guī)則,參見《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)赔桌。

(2)對(duì)象與原始類型值比較

對(duì)象(這里指廣義的對(duì)象供炎,包括數(shù)值和函數(shù))與原始類型的值比較時(shí),對(duì)象轉(zhuǎn)化成原始類型的值疾党,再進(jìn)行比較音诫。

[1] ==1// true// 等同于 Number([1]) == 1[1] =='1'// true// 等同于 String([1]) == Number('1')[1] ==true// true// 等同于 Boolean([1]) == true

上面代碼中,數(shù)組[1]分別與數(shù)值雪位、字符串和布爾值進(jìn)行比較纽竣,會(huì)先轉(zhuǎn)成該類型,再進(jìn)行比較茧泪。比如蜓氨,與數(shù)值1比較時(shí),數(shù)組[1]會(huì)被自動(dòng)轉(zhuǎn)換成數(shù)值1队伟,因此得到true穴吹。對(duì)象的類型轉(zhuǎn)換規(guī)則,參見《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)嗜侮。

(3)undefined和null

undefined和null與其他類型的值比較時(shí)港令,結(jié)果都為false啥容,它們互相比較時(shí)結(jié)果為true。

false==null// falsefalse==undefined// false0==null// false0==undefined// falseundefined==null// true

(4)相等運(yùn)算符的缺點(diǎn)

相等運(yùn)算符隱藏的類型轉(zhuǎn)換顷霹,會(huì)帶來一些違反直覺的結(jié)果咪惠。

''=='0'// false0==''// true0=='0'// true2==true// false2==false// falsefalse=='false'// falsefalse=='0'// truefalse==undefined// falsefalse==null// falsenull==undefined// true' \t\r\n '==0// true

上面這些表達(dá)式都很容易出錯(cuò),因此不要使用相等運(yùn)算符(==)淋淀,最好只使用嚴(yán)格相等運(yùn)算符(===)遥昧。

(5)不相等運(yùn)算符

相等運(yùn)算符有一個(gè)對(duì)應(yīng)的“不相等運(yùn)算符”(!=),兩者的運(yùn)算結(jié)果正好相反朵纷。

1!='1'// false

布爾運(yùn)算符

布爾運(yùn)算符用于將表達(dá)式轉(zhuǎn)為布爾值炭臭,一共包含四個(gè)運(yùn)算符。

取反運(yùn)算符:!

且運(yùn)算符:&&

或運(yùn)算符:||

三元運(yùn)算符:?:

取反運(yùn)算符(!)

取反運(yùn)算符形式上是一個(gè)感嘆號(hào)袍辞,用于將布爾值變?yōu)橄喾粗敌裕磘rue變成false,false變成true搅吁。

!true// false!false// true

對(duì)于非布爾值的數(shù)據(jù)威创,取反運(yùn)算符會(huì)自動(dòng)將其轉(zhuǎn)為布爾值。規(guī)則是谎懦,以下六個(gè)值取反后為true肚豺,其他值取反后都為false。

undefined

null

false

0(包括+0和-0)

NaN

空字符串('')

這意味著党瓮,取反運(yùn)算符有轉(zhuǎn)換數(shù)據(jù)類型的作用。

!undefined// true!null// true!0// true!NaN// true!""http:// true!54// false!'hello'// false![]// false!{}// false

上面代碼中盐类,不管什么類型的值寞奸,經(jīng)過取反運(yùn)算后,都變成了布爾值在跳。

如果對(duì)一個(gè)值連續(xù)做兩次取反運(yùn)算枪萄,等于將其轉(zhuǎn)為對(duì)應(yīng)的布爾值,與Boolean函數(shù)的作用相同猫妙。這是一種常用的類型轉(zhuǎn)換的寫法瓷翻。

!!x// 等同于Boolean(x)

上面代碼中,不管x是什么類型的值割坠,經(jīng)過兩次取反運(yùn)算后齐帚,變成了與Boolean函數(shù)結(jié)果相同的布爾值。所以彼哼,兩次取反就是將一個(gè)值轉(zhuǎn)為布爾值的簡(jiǎn)便寫法对妄。

取反運(yùn)算符的這種將任意數(shù)據(jù)自動(dòng)轉(zhuǎn)為布爾值的功能,對(duì)下面三種布爾運(yùn)算符(且運(yùn)算符敢朱、或運(yùn)算符剪菱、三元條件運(yùn)算符)都成立摩瞎。

且運(yùn)算符(&&)

且運(yùn)算符的運(yùn)算規(guī)則是:如果第一個(gè)運(yùn)算子的布爾值為true,則返回第二個(gè)運(yùn)算子的值(注意是值孝常,不是布爾值)旗们;如果第一個(gè)運(yùn)算子的布爾值為false,則直接返回第一個(gè)運(yùn)算子的值构灸,且不再對(duì)第二個(gè)運(yùn)算子求值上渴。

't'&&''// ""'t'&&'f'// "f"'t'&& (1+2)// 3''&&'f'// ""''&&''// ""varx =1;(1-1) && ( x +=1)// 0x// 1

上面代碼的最后一部分表示,由于且運(yùn)算符的第一個(gè)運(yùn)算子的布爾值為false冻押,則直接返回它的值0驰贷,而不再對(duì)第二個(gè)運(yùn)算子求值,所以變量x的值沒變洛巢。

這種跳過第二個(gè)運(yùn)算子的機(jī)制括袒,被稱為“短路”。有些程序員喜歡用它取代if結(jié)構(gòu)稿茉,比如下面是一段if結(jié)構(gòu)的代碼锹锰,就可以用且運(yùn)算符改寫。

if(i !==0) {? doSomething();}// 等價(jià)于i && doSomething();

上面代碼的兩種寫法是等價(jià)的漓库,但是后一種不容易看出目的恃慧,也不容易除錯(cuò),建議謹(jǐn)慎使用渺蒿。

且運(yùn)算符可以多個(gè)連用痢士,這時(shí)返回第一個(gè)布爾值為false的表達(dá)式的值。

true&&'foo'&&''&&4&&'foo'&&true// ''

上面代碼中第一個(gè)布爾值為false的表達(dá)式為第三個(gè)表達(dá)式茂装,所以得到一個(gè)空字符串怠蹂。

或運(yùn)算符(||)

或運(yùn)算符(||)的運(yùn)算規(guī)則是:如果第一個(gè)運(yùn)算子的布爾值為true,則返回第一個(gè)運(yùn)算子的值少态,且不再對(duì)第二個(gè)運(yùn)算子求值城侧;如果第一個(gè)運(yùn)算子的布爾值為false,則返回第二個(gè)運(yùn)算子的值彼妻。

't'||''// "t"'t'||'f'// "t"''||'f'// "f"''||''// ""

短路規(guī)則對(duì)這個(gè)運(yùn)算符也適用嫌佑。

或運(yùn)算符可以多個(gè)連用,這時(shí)返回第一個(gè)布爾值為true的表達(dá)式的值侨歉。

false||0||''||4||'foo'||true// 4

上面代碼中第一個(gè)布爾值為true的表達(dá)式是第四個(gè)表達(dá)式屋摇,所以得到數(shù)值4煞额。

或運(yùn)算符常用于為一個(gè)變量設(shè)置默認(rèn)值果覆。

functionsaveText(text){? text = text ||'';// ...}// 或者寫成saveText(this.text ||'')

上面代碼表示,如果函數(shù)調(diào)用時(shí)篙贸,沒有提供參數(shù)颊艳,則該參數(shù)默認(rèn)設(shè)置為空字符串茅特。

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

三元條件運(yùn)算符用問號(hào)(?)和冒號(hào)(:)忘分,分隔三個(gè)表達(dá)式。如果第一個(gè)表達(dá)式的布爾值為true白修,則返回第二個(gè)表達(dá)式的值妒峦,否則返回第三個(gè)表達(dá)式的值。

't'?'hello':'world'// "hello"0?'hello':'world'// "world"

上面代碼的t和0的布爾值分別為true和false兵睛,所以分別返回第二個(gè)和第三個(gè)表達(dá)式的值肯骇。

通常來說,三元條件表達(dá)式與if...else語句具有同樣表達(dá)效果祖很,前者可以表達(dá)的笛丙,后者也能表達(dá)。但是兩者具有一個(gè)重大差別假颇,if...else是語句胚鸯,沒有返回值;三元條件表達(dá)式是表達(dá)式笨鸡,具有返回值姜钳。所以,在需要返回值的場(chǎng)合形耗,只能使用三元條件表達(dá)式哥桥,而不能使用if..else。

console.log(true?'T':'F');

上面代碼中激涤,console.log方法的參數(shù)必須是一個(gè)表達(dá)式拟糕,這時(shí)就只能使用三元條件表達(dá)式。如果要用if...else語句倦踢,就必須改變整個(gè)代碼寫法了送滞。

位運(yùn)算符

簡(jiǎn)介

位運(yùn)算符用于直接對(duì)二進(jìn)制位進(jìn)行計(jì)算,一共有7個(gè)硼一。

或運(yùn)算(or):符號(hào)為|;累澡,表示兩個(gè)二進(jìn)制位中只要有一個(gè)為1梦抢,則結(jié)果為1般贼,否則為0。

與運(yùn)算(and):符號(hào)為&奥吩,表示如果兩個(gè)二進(jìn)制位都為1哼蛆,則結(jié)果為1,否則為0霞赫。

否運(yùn)算(not):符號(hào)為~腮介,表示將一個(gè)二進(jìn)制位變成相反值。

異或運(yùn)算(xor):符號(hào)為?端衰,表示如果兩個(gè)二進(jìn)制位中有且僅有一個(gè)為1時(shí)叠洗,結(jié)果為1甘改,否則為0。

左移運(yùn)算(left shift):符號(hào)為<<灭抑,詳見下文解釋十艾。

右移運(yùn)算(right shift):符號(hào)為>>,詳見下文解釋腾节。

帶符號(hào)位的右移運(yùn)算(zero filled right shift):符號(hào)為>>>忘嫉,詳見下文解釋。

這些位運(yùn)算符直接處理每一個(gè)比特位案腺,所以是非常底層的運(yùn)算庆冕,好處是速度極快,缺點(diǎn)是很不直觀劈榨,許多場(chǎng)合不能使用它們访递,否則會(huì)帶來過度的復(fù)雜性。

有一點(diǎn)需要特別注意鞋既,位運(yùn)算符只對(duì)整數(shù)起作用力九,如果一個(gè)運(yùn)算子不是整數(shù),會(huì)自動(dòng)轉(zhuǎn)為整數(shù)后再運(yùn)行邑闺。另外跌前,雖然在JavaScript內(nèi)部,數(shù)值都是以64位浮點(diǎn)數(shù)的形式儲(chǔ)存陡舅,但是做位運(yùn)算的時(shí)候抵乓,是以32位帶符號(hào)的整數(shù)進(jìn)行運(yùn)算的,并且返回值也是一個(gè)32位帶符號(hào)的整數(shù)靶衍。

i = i |0;

上面這行代碼的意思灾炭,就是將i(不管是整數(shù)或小數(shù))轉(zhuǎn)為32位整數(shù)。

利用這個(gè)特性颅眶,可以寫出一個(gè)函數(shù)蜈出,將任意數(shù)值轉(zhuǎn)為32位整數(shù)。

functionToInt32(x){returnx |0;}ToInt32(1.001)// 1ToInt32(1.999)// 1ToInt32(1)// 1ToInt32(-1)// -1ToInt32(Math.pow(2,32) +1)// 1ToInt32(Math.pow(2,32) -1)// -1

上面代碼中涛酗,最后兩行得到1和-1铡原,是因?yàn)橐粋€(gè)整數(shù)大于32位的數(shù)位都會(huì)被舍去。

“或運(yùn)算”與“與運(yùn)算”

這兩種運(yùn)算比較容易理解商叹,就是逐位比較兩個(gè)運(yùn)算子燕刻。“或運(yùn)算”的規(guī)則是剖笙,兩個(gè)二進(jìn)制位之中只要有一個(gè)為1卵洗,就返回1,否則返回0弥咪」澹“與運(yùn)算”的規(guī)則是十绑,兩個(gè)二進(jìn)制位之中只要有一個(gè)位為0,就返回0酷勺,否則返回1孽惰。

0|3// 30&3// 0

上面兩個(gè)表達(dá)式,0和3的二進(jìn)制形式分別是00和11鸥印,所以進(jìn)行“或運(yùn)算”會(huì)得到11(即3)勋功,進(jìn)行”與運(yùn)算“會(huì)得到00(即0)。

位運(yùn)算只對(duì)整數(shù)有效库说,遇到小數(shù)時(shí)狂鞋,會(huì)將小數(shù)部分舍去,只保留整數(shù)部分潜的。所以骚揍,將一個(gè)小數(shù)與0進(jìn)行或運(yùn)算,等同于對(duì)該數(shù)去除小數(shù)部分啰挪,即取整數(shù)位信不。

2.9|0// 2-2.9|0// -2

需要注意的是,這種取整方法不適用超過32位整數(shù)最大值2147483647的數(shù)亡呵。

2147483649.4|0;// -2147483647

否運(yùn)算

“否運(yùn)算”將每個(gè)二進(jìn)制位都變?yōu)橄喾粗担?變?yōu)?抽活,1變?yōu)?)。它的返回結(jié)果有時(shí)比較難理解锰什,因?yàn)樯婕暗接?jì)算機(jī)內(nèi)部的數(shù)值表示機(jī)制下硕。

~3// -4

上面表達(dá)式對(duì)3進(jìn)行“否運(yùn)算”,得到-4汁胆。之所以會(huì)有這樣的結(jié)果梭姓,是因?yàn)槲贿\(yùn)算時(shí),JavaScirpt內(nèi)部將所有的運(yùn)算子都轉(zhuǎn)為32位的二進(jìn)制整數(shù)再進(jìn)行運(yùn)算嫩码。3在JavaScript內(nèi)部是00000000000000000000000000000011誉尖,否運(yùn)算以后得到11111111111111111111111111111100,由于第一位是1铸题,所以這個(gè)數(shù)是一個(gè)負(fù)數(shù)铡恕。JavaScript內(nèi)部采用2的補(bǔ)碼形式表示負(fù)數(shù),即需要將這個(gè)數(shù)減去1回挽,再取一次反没咙,然后加上負(fù)號(hào)猩谊,才能得到這個(gè)負(fù)數(shù)對(duì)應(yīng)的10進(jìn)制值千劈。這個(gè)數(shù)減去1等于11111111111111111111111111111011,再取一次反得到00000000000000000000000000000100牌捷,再加上負(fù)號(hào)就是-4墙牌∥型裕考慮到這樣的過程比較麻煩,可以簡(jiǎn)單記憶成喜滨,一個(gè)數(shù)與自身的取反值相加捉捅,等于-1。

~-3// 2

上面表達(dá)式可以這樣算虽风,-3的取反值等于-1減去-3棒口,結(jié)果為2。

對(duì)一個(gè)整數(shù)連續(xù)兩次“否運(yùn)算”辜膝,得到它自身无牵。

~~3// 3

所有的位運(yùn)算都只對(duì)整數(shù)有效。否運(yùn)算遇到小數(shù)時(shí)厂抖,也會(huì)將小數(shù)部分舍去茎毁,只保留整數(shù)部分。所以忱辅,對(duì)一個(gè)小數(shù)連續(xù)進(jìn)行兩次否運(yùn)算七蜘,能達(dá)到取整效果。

~~2.9// 2~~47.11// 47~~1.9999// 1~~3// 3

使用否運(yùn)算取整墙懂,是所有取整方法中最快的一種橡卤。

對(duì)字符串進(jìn)行否運(yùn)算,JavaScript引擎會(huì)先調(diào)用Number函數(shù)损搬,將字符串轉(zhuǎn)為數(shù)值蒜魄。

// 以下例子相當(dāng)于~Number('011')~'011'// -12~'42 cats'// -1~'0xcafebabe'// 889275713~'deadbeef'// -1// 以下例子相當(dāng)于~~Number('011')~~'011';// 11~~'42 cats';// 0~~'0xcafebabe';// -889275714~~'deadbeef';// 0

Number函數(shù)將字符串轉(zhuǎn)為數(shù)值的規(guī)則,參見《數(shù)據(jù)的類型轉(zhuǎn)換》一節(jié)场躯。否運(yùn)算對(duì)特殊數(shù)值的處理是:超出32位的整數(shù)將會(huì)被截去超出的位數(shù)谈为,NaN和Infinity轉(zhuǎn)為0。

對(duì)于其他類型的參數(shù)踢关,否運(yùn)算也是先用Number轉(zhuǎn)為數(shù)值伞鲫,然后再進(jìn)行處理湾盒。

~~[]// 0~~NaN// 0~~null// 0

異或運(yùn)算

“異或運(yùn)算”在兩個(gè)二進(jìn)制位不同時(shí)返回1披坏,相同時(shí)返回0民珍。

0^3// 3

上面表達(dá)式中洪燥,0的二進(jìn)制形式是00公般,3的二進(jìn)制形式是11棉浸,它們每一個(gè)二進(jìn)制位都不同锋喜,所以得到11(即3)精居。

“異或運(yùn)算”有一個(gè)特殊運(yùn)用搂鲫,連續(xù)對(duì)兩個(gè)數(shù)a和b進(jìn)行三次異或運(yùn)算傍药,a?=b, b?=a, a?=b,可以互換它們的值(詳見維基百科)。這意味著拐辽,使用“異或運(yùn)算”可以在不引入臨時(shí)變量的前提下拣挪,互換兩個(gè)變量的值。

vara =10;varb =99;a ^= b, b ^= a, a ^= b;a// 99b// 10

這是互換兩個(gè)變量的值的最快方法俱诸。

異或運(yùn)算也可以用來取整菠劝。

12.9^0// 12

左移運(yùn)算符(<<)

左移運(yùn)算符表示將一個(gè)數(shù)的二進(jìn)制值,向前移動(dòng)指定的位數(shù)睁搭,尾部補(bǔ)0赶诊,即乘以2的指定次方。

// 4 的二進(jìn)制形式為100园骆,// 左移一位為1000(即十進(jìn)制的8)// 相當(dāng)于乘以2的1次方4<<1// 8-4<<1// -8

上面代碼中甫何,-4左移一位得到-8,是因?yàn)?4的二進(jìn)制形式是11111111111111111111111111111100遇伞,左移一位后得到11111111111111111111111111111000辙喂,該數(shù)轉(zhuǎn)為十進(jìn)制(減去1后取反,再加上負(fù)號(hào))即為-8鸠珠。

如果左移0位巍耗,就相當(dāng)于將該數(shù)值轉(zhuǎn)為32位整數(shù),等同于取整渐排,對(duì)于正數(shù)和負(fù)數(shù)都有效炬太。

13.5<<0// 13-13.5<<0// -13

左移運(yùn)算符用于二進(jìn)制數(shù)值非常方便。

varcolor = {r:186,g:218,b:85};// RGB to HEXvarrgb2hex =function(r, g, b){return'#'+ ((1<<24) + (r <<16) + (g <<8) + b)? ? .toString(16)? ? .substr(1);}rgb2hex(color.r,color.g,color.b)// "#bada55"

上面代碼使用左移運(yùn)算符驯耻,將顏色的RGB值轉(zhuǎn)為HEX值亲族。

右移運(yùn)算符(>>)

右移運(yùn)算符表示將一個(gè)數(shù)的二進(jìn)制形式向右移動(dòng),頭部補(bǔ)上最左位的值可缚,即整數(shù)補(bǔ)0霎迫,負(fù)數(shù)補(bǔ)1。

4>>1// 2/*

// 因?yàn)?的二進(jìn)制形式為00000000000000000000000000000100帘靡,

// 右移一位得到00000000000000000000000000000010知给,

// 即為十進(jìn)制的2

*/-4>>1// -2/*

// 因?yàn)?4的二進(jìn)制形式為11111111111111111111111111111100,

// 右移一位描姚,頭部補(bǔ)1涩赢,得到11111111111111111111111111111110,

// 即為十進(jìn)制的-2

*/

右移運(yùn)算可以模擬2的整除運(yùn)算。

5>>1// 相當(dāng)于 5 / 2 = 221>>2// 相當(dāng)于 21 / 4 = 521>>3// 相當(dāng)于 21 / 8 = 221>>4// 相當(dāng)于 21 / 16 = 1

帶符號(hào)位的右移運(yùn)算符(>>>)

該運(yùn)算符表示將一個(gè)數(shù)的二進(jìn)制形式向右移動(dòng)轩勘,不管正數(shù)或負(fù)數(shù)筒扒,頭部一律補(bǔ)0。所以绊寻,該運(yùn)算總是得到正值花墩,這就是它的名稱“帶符號(hào)位的右移”的涵義悬秉。對(duì)于正數(shù),該運(yùn)算的結(jié)果與右移運(yùn)算符(>>)完全一致观游,區(qū)別主要在于負(fù)數(shù)。

4>>>1// 2-4>>>1// 2147483646/*

// 因?yàn)?4的二進(jìn)制形式為11111111111111111111111111111100驮俗,

// 帶符號(hào)位的右移一位懂缕,得到01111111111111111111111111111110,

// 即為十進(jìn)制的2147483646王凑。

*/

這個(gè)運(yùn)算實(shí)際上將一個(gè)值轉(zhuǎn)為32位無符號(hào)整數(shù)搪柑。

查看一個(gè)負(fù)整數(shù)在計(jì)算機(jī)內(nèi)部的儲(chǔ)存形式,最快的方法就是使用這個(gè)運(yùn)算符索烹。

-1>>>0// 4294967295

上面代碼表示工碾,-1作為32位整數(shù)時(shí),內(nèi)部的儲(chǔ)存形式使用無符號(hào)整數(shù)格式解讀百姓,值為 4294967295(即2^32 -1渊额,等于32個(gè)1)。

開關(guān)作用

位運(yùn)算符可以用作設(shè)置對(duì)象屬性的開關(guān)垒拢。

假定某個(gè)對(duì)象有四個(gè)開關(guān)旬迹,每個(gè)開關(guān)都是一個(gè)變量。那么求类,可以設(shè)置一個(gè)四位的二進(jìn)制數(shù)奔垦,它的每個(gè)位對(duì)應(yīng)一個(gè)開關(guān)。

varFLAG_A =1;// 0001varFLAG_B =2;// 0010varFLAG_C =4;// 0100varFLAG_D =8;// 1000

上面代碼設(shè)置A尸疆、B椿猎、C、D四個(gè)開關(guān)寿弱,每個(gè)開關(guān)分別占有一個(gè)二進(jìn)制位犯眠。

然后,就可以用“與運(yùn)算”檢驗(yàn)症革,當(dāng)前設(shè)置是否打開了指定開關(guān)阔逼。

varflags =3;// 二進(jìn)制的0101if(flags & FLAG_C) {// ...}// 0101 & 0100 => 0100 => true

上面代碼檢驗(yàn)是否打開了開關(guān)C。如果打開地沮,會(huì)返回true嗜浮,否則返回false。

現(xiàn)在假設(shè)需要打開ABD三個(gè)開關(guān)摩疑,我們可以構(gòu)造一個(gè)掩碼變量危融。

varmask = FLAG_A | FLAG_B | FLAG_D;// 0001 | 0010 | 1000 => 1011

上面代碼對(duì)ABD三個(gè)變量進(jìn)行“或運(yùn)算”,得到掩碼值為二進(jìn)制的1011雷袋。

有了掩碼吉殃,“或運(yùn)算”可以將當(dāng)前設(shè)置改成指定設(shè)置辞居。

flags = flags | mask;

“與運(yùn)算”可以將當(dāng)前設(shè)置中凡是與開關(guān)設(shè)置不一樣的項(xiàng),全部關(guān)閉蛋勺。

flags = flags & mask;

“異或運(yùn)算”可以切換(toggle)當(dāng)前設(shè)置瓦灶,即第一次執(zhí)行可以得到當(dāng)前設(shè)置的相反值,再執(zhí)行一次又得到原來的值抱完。

flags = flags ^ mask;

“否運(yùn)算”可以翻轉(zhuǎn)當(dāng)前設(shè)置贼陶,即原設(shè)置為0,運(yùn)算后變?yōu)?巧娱;原設(shè)置為1碉怔,運(yùn)算后變?yōu)?。

flags = ~flags;

其他運(yùn)算符

圓括號(hào)運(yùn)算符

在JavaScript中禁添,圓括號(hào)是一種運(yùn)算符撮胧,它有兩種用法:如果把表達(dá)式放在圓括號(hào)之中,作用是求值老翘;如果跟在函數(shù)的后面芹啥,作用是調(diào)用函數(shù)。

把表達(dá)式放在圓括號(hào)之中铺峭,將返回表達(dá)式的值叁征。

{% highlight javascript %}

(1) // 1

('a') // a

(1+2) // 3

{% endhighlight %}

把對(duì)象放在圓括號(hào)之中,則會(huì)返回對(duì)象的值逛薇,即對(duì)象本身捺疼。

varo = {p:1};(o)// Object {p: 1}

將函數(shù)放在圓括號(hào)中,會(huì)返回函數(shù)本身永罚。如果圓括號(hào)緊跟在函數(shù)的后面啤呼,就表示調(diào)用函數(shù),即對(duì)函數(shù)求值呢袱。

functionf(){return1;}(f)// function f(){return 1;}f()// 1

上面的代碼先定義了一個(gè)函數(shù)官扣,然后依次將函數(shù)放在圓括號(hào)之中、將圓括號(hào)跟在函數(shù)后面羞福,得到的結(jié)果是不一樣的惕蹄。

由于圓括號(hào)的作用是求值,如果將語句放在圓括號(hào)之中治专,就會(huì)報(bào)錯(cuò)卖陵,因?yàn)檎Z句沒有返回值。

(vara =1)// SyntaxError: Unexpected token var

void運(yùn)算符

void運(yùn)算符的作用是執(zhí)行一個(gè)表達(dá)式张峰,然后不返回任何值泪蔫,或者說返回undefined。

void0// undefinedvoid(0)// undefined

上面是void運(yùn)算符的兩種寫法喘批,都正確撩荣。建議采用后一種形式铣揉,即總是使用括號(hào)。因?yàn)関oid運(yùn)算符的優(yōu)先性很高餐曹,如果不使用括號(hào)逛拱,容易造成錯(cuò)誤的結(jié)果。比如台猴,void 4 + 7實(shí)際上等同于(void 4) + 7朽合。

下面是void運(yùn)算符的一個(gè)例子。

varx =3;void(x =5)//undefinedx// 5

這個(gè)運(yùn)算符主要是用于書簽工具(bookmarklet)卿吐,以及用于在超級(jí)鏈接中插入代碼旁舰,目的是返回undefined可以防止網(wǎng)頁跳轉(zhuǎn)锋华。

點(diǎn)擊打開新窗口

上面代碼用于在網(wǎng)頁中創(chuàng)建一個(gè)鏈接嗡官,點(diǎn)擊后會(huì)打開一個(gè)新窗口。如果沒有void毯焕,點(diǎn)擊后就會(huì)在當(dāng)前窗口打開鏈接衍腥。

下面是常見的網(wǎng)頁中觸發(fā)鼠標(biāo)點(diǎn)擊事件的寫法。

文字

上面代碼有一個(gè)問題纳猫,函數(shù)f必須返回false婆咸,或者說onclick事件必須返回false,否則會(huì)引起瀏覽器跳轉(zhuǎn)到example.com芜辕。

functionf(){// some codereturnfalse;}

或者寫成

文字

void運(yùn)算符可以取代上面兩種寫法尚骄。

文字

下面的代碼會(huì)提交表單,但是不會(huì)產(chǎn)生頁面跳轉(zhuǎn)侵续。

文字

逗號(hào)運(yùn)算符

逗號(hào)運(yùn)算符用于對(duì)兩個(gè)表達(dá)式求值倔丈,并返回后一個(gè)表達(dá)式的值。

'a','b'// "b"varx =0;vary = (x++,10);x// 1y// 10

上面代碼中状蜗,逗號(hào)運(yùn)算符返回后一個(gè)表達(dá)式的值需五。

運(yùn)算順序

(1)運(yùn)算符的優(yōu)先級(jí)

JavaScript各種運(yùn)算符的優(yōu)先級(jí)別(Operator Precedence)是不一樣的。優(yōu)先級(jí)高的運(yùn)算符先執(zhí)行轧坎,優(yōu)先級(jí)低的運(yùn)算符后執(zhí)行。

4+5*6// 34

上面的代碼中,乘法運(yùn)算符(*)的優(yōu)先性高于加法運(yùn)算符(+)付鹿,所以先執(zhí)行乘法泛豪,再執(zhí)行加法,相當(dāng)于下面這樣捎泻。

4+ (5*6)// 34

如果多個(gè)運(yùn)算符混寫在一起记劝,常常會(huì)導(dǎo)致令人困惑的代碼。

varx =1;vararr = [];vary = arr.length <=0|| arr[0] ===undefined? x : arr[0];

上面代碼中族扰,變量y的值就很難看出來厌丑,因?yàn)檫@個(gè)表達(dá)式涉及5個(gè)運(yùn)算符定欧,到底誰的優(yōu)先級(jí)最高,實(shí)在不容易記住怒竿。

根據(jù)語言規(guī)格砍鸠,這五個(gè)運(yùn)算符的優(yōu)先級(jí)從高到低依次為:小于等于(<=)、嚴(yán)格相等(===)耕驰、或(||)爷辱、三元(?:)、等號(hào)(=)朦肘。因此上面的表達(dá)式饭弓,實(shí)際的運(yùn)算順序如下。

vary = ((arr.length <=0) || (arr[0] ===undefined)) ? x : arr[0];

記住所有運(yùn)算符的優(yōu)先級(jí)媒抠,幾乎是不可能的弟断,也是沒有必要的。

(2)圓括號(hào)的作用

圓括號(hào)可以用來提高運(yùn)算的優(yōu)先級(jí)趴生,因?yàn)樗膬?yōu)先級(jí)是最高的阀趴,即圓括號(hào)中的運(yùn)算符會(huì)第一個(gè)運(yùn)算。

(4+5) *6// 54

上面代碼中苍匆,由于使用了圓括號(hào)刘急,加法會(huì)先于乘法執(zhí)行。

由于運(yùn)算符的優(yōu)先級(jí)別十分繁雜浸踩,且都是來自硬性規(guī)定叔汁,因此建議總是使用圓括號(hào),保證運(yùn)算順序清晰可讀检碗,這對(duì)代碼的維護(hù)和除錯(cuò)至關(guān)重要据块。

(3)左結(jié)合與右結(jié)合

對(duì)于優(yōu)先級(jí)別相同的運(yùn)算符,大多數(shù)情況后裸,計(jì)算順序總是從左到右瑰钮,這叫做運(yùn)算符的“左結(jié)合”(left-to-right associativity),即從左邊開始計(jì)算微驶。

x + y + z

上面代碼先計(jì)算最左邊的x與y的和浪谴,然后再計(jì)算與z的和。

但是少數(shù)運(yùn)算符的計(jì)算順序是從右到左因苹,即從右邊開始計(jì)算苟耻,這叫做運(yùn)算符的“右結(jié)合”(right-to-left associativity)。其中扶檐,最主要的是賦值運(yùn)算符(=)和三元條件運(yùn)算符(?:)凶杖。

w = x = y = z;

q = a ? b : c ? d : e ? f : g;

上面代碼的運(yùn)算結(jié)果,相當(dāng)于下面的樣子款筑。

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

q = a ? b : (c ? d : (e ? f : g));

數(shù)據(jù)類型轉(zhuǎn)換

JavaScript是一種動(dòng)態(tài)類型語言智蝠,變量沒有類型限制腾么,可以隨時(shí)賦予任意值。

varx = y ?1:'a';

上面代碼中杈湾,變量x到底是數(shù)值還是字符串解虱,取決于另一個(gè)變量y的值。只有在代碼運(yùn)行時(shí)漆撞,才可能知道x的類型殴泰。

雖然變量沒有類型,但是數(shù)據(jù)本身和各種運(yùn)算符是有類型的浮驳。如果運(yùn)算符發(fā)現(xiàn)悍汛,數(shù)據(jù)的類型與預(yù)期不符,就會(huì)自動(dòng)轉(zhuǎn)換類型至会。比如离咐,減法運(yùn)算符預(yù)期兩側(cè)的運(yùn)算子應(yīng)該是數(shù)值,如果不是奋献,就會(huì)自動(dòng)將它們轉(zhuǎn)為數(shù)值健霹。

'4'-'3'// 1

上面代碼中旺上,雖然是兩個(gè)字符串相減瓶蚂,但是依然會(huì)得到結(jié)果1,原因就在于JavaScript將它們自動(dòng)轉(zhuǎn)為了數(shù)值宣吱。

本節(jié)講解數(shù)據(jù)類型自動(dòng)轉(zhuǎn)換的規(guī)則窃这,在此之前,先講解如何手動(dòng)強(qiáng)制轉(zhuǎn)換數(shù)據(jù)類型征候。

強(qiáng)制轉(zhuǎn)換

強(qiáng)制轉(zhuǎn)換主要指使用Number杭攻、String和Boolean三個(gè)構(gòu)造函數(shù),手動(dòng)將各種類型的值疤坝,轉(zhuǎn)換成數(shù)字兆解、字符串或者布爾值。

Number()

使用Number函數(shù)跑揉,可以將任意類型的值轉(zhuǎn)化成數(shù)值锅睛。

下面分成兩種情況討論,一種是參數(shù)是原始類型的值历谍,另一種是參數(shù)是對(duì)象现拒。

(1)原始類型值的轉(zhuǎn)換規(guī)則

原始類型的值主要是字符串、布爾值望侈、undefined和null印蔬,它們都能被Number轉(zhuǎn)成數(shù)值或NaN。

// 數(shù)值:轉(zhuǎn)換后還是原來的值Number(324)// 324// 字符串:如果可以被解析為數(shù)值脱衙,則轉(zhuǎn)換為相應(yīng)的數(shù)值Number('324')// 324// 字符串:如果不可以被解析為數(shù)值侥猬,返回NaNNumber('324abc')// NaN// 空字符串轉(zhuǎn)為0Number('')// 0// 布爾值:true 轉(zhuǎn)成1例驹,false 轉(zhuǎn)成0Number(true)// 1Number(false)// 0// undefined:轉(zhuǎn)成 NaNNumber(undefined)// NaN// null:轉(zhuǎn)成0Number(null)// 0

Number函數(shù)將字符串轉(zhuǎn)為數(shù)值,要比parseInt函數(shù)嚴(yán)格很多退唠∶咭基本上,只要有一個(gè)字符無法轉(zhuǎn)成數(shù)值铜邮,整個(gè)字符串就會(huì)被轉(zhuǎn)為NaN仪召。

parseInt('42 cats')// 42Number('42 cats')// NaN

上面代碼中,parseInt逐個(gè)解析字符松蒜,而Number函數(shù)整體轉(zhuǎn)換字符串的類型扔茅。

另外,Number函數(shù)會(huì)自動(dòng)過濾一個(gè)字符串前導(dǎo)和后綴的空格秸苗。

Number('\t\v\r12.34\n')// 12.34

(2)對(duì)象的轉(zhuǎn)換規(guī)則

如果參數(shù)是對(duì)象召娜,Number將其轉(zhuǎn)為數(shù)值的規(guī)則比較復(fù)雜。JavaScript的內(nèi)部處理步驟如下惊楼。

調(diào)用對(duì)象自身的valueOf方法玖瘸。如果返回原始類型的值,則直接對(duì)該值使用Number函數(shù)檀咙,不再進(jìn)行后續(xù)步驟雅倒。

如果valueOf方法返回的還是對(duì)象,則改為調(diào)用對(duì)象自身的toString方法弧可。如果返回原始類型的值蔑匣,則對(duì)該值使用Number函數(shù),不再進(jìn)行后續(xù)步驟棕诵。

如果toString方法返回的是對(duì)象裁良,就報(bào)錯(cuò)。

請(qǐng)看下面的例子校套。

varobj = {a:1};Number(obj)// NaN// 等同于if(typeofobj.valueOf() ==='object') {Number(obj.toString());}else{Number(obj.valueOf());}

上面代碼中价脾,Number函數(shù)將obj對(duì)象轉(zhuǎn)為數(shù)值。首先笛匙,調(diào)用obj.valueOf方法, 結(jié)果返回對(duì)象本身侨把;于是,繼續(xù)調(diào)用obj.toString方法膳算,這時(shí)返回字符串[object Object]座硕,對(duì)這個(gè)字符串使用Number函數(shù),得到NaN涕蜂。

默認(rèn)情況下华匾,對(duì)象的valueOf方法返回對(duì)象本身,所以一般總是會(huì)調(diào)用toString方法,而toString方法返回對(duì)象的類型字符串(比如[object Object])蜘拉。所以萨西,會(huì)有下面的結(jié)果。

Number({})// NaN

如果toString方法返回的不是原始類型的值旭旭,結(jié)果就會(huì)報(bào)錯(cuò)谎脯。

varobj = {valueOf:function(){return{};? },toString:function(){return{};? }};Number(obj)// TypeError: Cannot convert object to primitive value

上面代碼的valueOf和toString方法,返回的都是對(duì)象持寄,所以轉(zhuǎn)成數(shù)值時(shí)會(huì)報(bào)錯(cuò)源梭。

從上面的例子可以看出,valueOf和toString方法稍味,都是可以自定義的废麻。

Number({valueOf:function(){return2;? }})// 2Number({toString:function(){return3;? }})// 3Number({valueOf:function(){return2;? },toString:function(){return3;? }})// 2

上面代碼對(duì)三個(gè)對(duì)象使用Number函數(shù)。第一個(gè)對(duì)象返回valueOf方法的值模庐,第二個(gè)對(duì)象返回toString方法的值烛愧,第三個(gè)對(duì)象表示valueOf方法先于toString方法執(zhí)行。

String()

使用String函數(shù)掂碱,可以將任意類型的值轉(zhuǎn)化成字符串怜姿。轉(zhuǎn)換規(guī)則如下。

(1)原始類型值的轉(zhuǎn)換規(guī)則

數(shù)值:轉(zhuǎn)為相應(yīng)的字符串疼燥。

字符串:轉(zhuǎn)換后還是原來的值沧卢。

布爾值:true轉(zhuǎn)為"true",false轉(zhuǎn)為"false"悴了。

undefined:轉(zhuǎn)為"undefined"搏恤。

null:轉(zhuǎn)為"null"违寿。

String(123)// "123"String('abc')// "abc"String(true)// "true"String(undefined)// "undefined"String(null)// "null"

(2)對(duì)象的轉(zhuǎn)換規(guī)則

String函數(shù)將對(duì)象轉(zhuǎn)為字符串的步驟湃交,與Number函數(shù)的處理步驟基本相同,只是互換了valueOf方法和toString方法的執(zhí)行順序藤巢。

先調(diào)用對(duì)象自身的toString方法搞莺。如果返回原始類型的值,則對(duì)該值使用String函數(shù)掂咒,不再進(jìn)行以下步驟才沧。

如果toString方法返回的是對(duì)象,再調(diào)用valueOf方法绍刮。如果返回原始類型的值温圆,則對(duì)該值使用String函數(shù),不再進(jìn)行以下步驟孩革。

如果valueOf方法返回的是對(duì)象岁歉,就報(bào)錯(cuò)镣丑。

下面是一個(gè)例子。

String({a:1})// "[object Object]"http:// 等同于String({a:1}.toString())// "[object Object]"

上面代碼先調(diào)用對(duì)象的toString方法储狭,發(fā)現(xiàn)返回的是字符串[object Object]纺荧,就不再調(diào)用valueOf方法了。

如果toString法和valueOf方法非剃,返回的都是對(duì)象置逻,就會(huì)報(bào)錯(cuò)。

varobj = {valueOf:function(){console.log('valueOf');return{};? },toString:function(){console.log('toString');return{};? }};String(obj)// TypeError: Cannot convert object to primitive value

下面是通過自定義toString方法备绽,改變轉(zhuǎn)換成字符串時(shí)的返回值的例子券坞。

String({toString:function(){return3;? }})// "3"String({valueOf:function(){return2;? }})// "[object Object]"String({valueOf:function(){return2;? },toString:function(){return3;? }})// "3"

上面代碼對(duì)三個(gè)對(duì)象使用String函數(shù)。第一個(gè)對(duì)象返回toString方法的值(數(shù)值3)肺素,第二個(gè)對(duì)象返回的還是toString方法的值([object Object])报慕,第三個(gè)對(duì)象表示toString方法先于valueOf方法執(zhí)行。

Boolean()

使用Boolean函數(shù)压怠,可以將任意類型的變量轉(zhuǎn)為布爾值眠冈。

它的轉(zhuǎn)換規(guī)則相對(duì)簡(jiǎn)單:除了以下六個(gè)值的轉(zhuǎn)換結(jié)果為false,其他的值全部為true菌瘫。

undefined

null

-0

0或+0

NaN

''(空字符串)

Boolean(undefined)// falseBoolean(null)// falseBoolean(0)// falseBoolean(NaN)// falseBoolean('')// false

注意蜗顽,所有對(duì)象(包括空對(duì)象)的轉(zhuǎn)換結(jié)果都是true,甚至連false對(duì)應(yīng)的布爾對(duì)象new Boolean(false)也是true雨让。

Boolean({})// trueBoolean([])// trueBoolean(newBoolean(false))// true

所有對(duì)象的布爾值都是true雇盖,這是因?yàn)镴avaScript語言設(shè)計(jì)的時(shí)候,出于性能的考慮栖忠,如果對(duì)象需要計(jì)算才能得到布爾值崔挖,對(duì)于obj1 && obj2這樣的場(chǎng)景,可能會(huì)需要較多的計(jì)算庵寞。為了保證性能狸相,就統(tǒng)一規(guī)定,對(duì)象的布爾值為true捐川。

自動(dòng)轉(zhuǎn)換

下面介紹自動(dòng)轉(zhuǎn)換脓鹃,它是以強(qiáng)制轉(zhuǎn)換為基礎(chǔ)的。

遇到以下三種情況時(shí)古沥,JavaScript會(huì)自動(dòng)轉(zhuǎn)換數(shù)據(jù)類型瘸右,即轉(zhuǎn)換是自動(dòng)完成的,對(duì)用戶不可見岩齿。

// 1. 不同類型的數(shù)據(jù)互相運(yùn)算123+'abc'// "123abc"http:// 2. 對(duì)非布爾值類型的數(shù)據(jù)求布爾值if('abc') {console.log('hello')}// "hello"http:// 3. 對(duì)非數(shù)值類型的數(shù)據(jù)使用一元運(yùn)算符(即“+”和“-”)+ {foo:'bar'}// NaN- [1,2,3]// NaN

自動(dòng)轉(zhuǎn)換的規(guī)則是這樣的:預(yù)期什么類型的值太颤,就調(diào)用該類型的轉(zhuǎn)換函數(shù)。比如盹沈,某個(gè)位置預(yù)期為字符串龄章,就調(diào)用String函數(shù)進(jìn)行轉(zhuǎn)換。如果該位置即可以是字符串,也可能是數(shù)值瓦堵,那么默認(rèn)轉(zhuǎn)為數(shù)值基协。

由于自動(dòng)轉(zhuǎn)換具有不確定性,而且不易除錯(cuò)菇用,建議在預(yù)期為布爾值澜驮、數(shù)值、字符串的地方惋鸥,全部使用Boolean杂穷、Number和String函數(shù)進(jìn)行顯式轉(zhuǎn)換。

自動(dòng)轉(zhuǎn)換為布爾值

當(dāng)JavaScript遇到預(yù)期為布爾值的地方(比如if語句的條件部分)卦绣,就會(huì)將非布爾值的參數(shù)自動(dòng)轉(zhuǎn)換為布爾值耐量。系統(tǒng)內(nèi)部會(huì)自動(dòng)調(diào)用Boolean函數(shù)。

因此除了以下六個(gè)值滤港,其他都是自動(dòng)轉(zhuǎn)為true廊蜒。

undefined

null

-0

0或+0

NaN

''(空字符串)

下面這個(gè)例子中,條件部分的每個(gè)值都相當(dāng)于false溅漾,使用否定運(yùn)算符后山叮,就變成了true。

if( !undefined&& !null&& !0&& !NaN&& !'') {console.log('true');}// true

下面兩種寫法添履,有時(shí)也用于將一個(gè)表達(dá)式轉(zhuǎn)為布爾值屁倔。它們內(nèi)部調(diào)用的也是Boolean函數(shù)。

// 寫法一expression ?true:false// 寫法二!! expression

自動(dòng)轉(zhuǎn)換為字符串

當(dāng)JavaScript遇到預(yù)期為字符串的地方暮胧,就會(huì)將非字符串的數(shù)據(jù)自動(dòng)轉(zhuǎn)為字符串锐借。系統(tǒng)內(nèi)部會(huì)自動(dòng)調(diào)用String函數(shù)。

字符串的自動(dòng)轉(zhuǎn)換往衷,主要發(fā)生在加法運(yùn)算時(shí)钞翔。當(dāng)一個(gè)值為字符串,另一個(gè)值為非字符串炼绘,則后者轉(zhuǎn)為字符串嗅战。

'5'+1// '51''5'+true// "5true"'5'+false// "5false"'5'+ {}// "5[object Object]"'5'+ []// "5"'5'+function(){}// "5function (){}"'5'+undefined// "5undefined"'5'+null// "5null"

這種自動(dòng)轉(zhuǎn)換很容易出錯(cuò)。

varobj = {width:'100'};obj.width +20// "10020"

上面代碼中俺亮,開發(fā)者可能期望返回120,但是由于自動(dòng)轉(zhuǎn)換疟呐,實(shí)際上返回了一個(gè)字符10020脚曾。

自動(dòng)轉(zhuǎn)換為數(shù)值

當(dāng)JavaScript遇到預(yù)期為數(shù)值的地方,就會(huì)將參數(shù)值自動(dòng)轉(zhuǎn)換為數(shù)值启具。系統(tǒng)內(nèi)部會(huì)自動(dòng)調(diào)用Number函數(shù)本讥。

除了加法運(yùn)算符有可能把運(yùn)算子轉(zhuǎn)為字符串,其他運(yùn)算符都會(huì)把運(yùn)算子自動(dòng)轉(zhuǎn)成數(shù)值。

'5'-'2'// 3'5'*'2'// 10true-1// 0false-1// -1'1'-1// 0'5'* []// 0false/'5'// 0'abc'-1// NaN

上面代碼中拷沸,運(yùn)算符兩側(cè)的運(yùn)算子色查,都被轉(zhuǎn)成了數(shù)值。

一元運(yùn)算符也會(huì)把運(yùn)算子轉(zhuǎn)成數(shù)值撞芍。

+'abc'// NaN-'abc'// NaN+true// 1-false// 0

錯(cuò)誤處理機(jī)制

Error對(duì)象

一旦代碼解析或運(yùn)行時(shí)發(fā)生錯(cuò)誤秧了,JavaScript引擎就會(huì)自動(dòng)產(chǎn)生并拋出一個(gè)Error對(duì)象的實(shí)例,然后整個(gè)程序就中斷在發(fā)生錯(cuò)誤的地方序无。

Error對(duì)象的實(shí)例有三個(gè)最基本的屬性:

name:錯(cuò)誤名稱

message:錯(cuò)誤提示信息

stack:錯(cuò)誤的堆棧(非標(biāo)準(zhǔn)屬性验毡,但是大多數(shù)平臺(tái)支持)

利用name和message這兩個(gè)屬性,可以對(duì)發(fā)生什么錯(cuò)誤有一個(gè)大概的了解帝嗡。

if(error.name){console.log(error.name +": "+ error.message);}

上面代碼表示晶通,顯示錯(cuò)誤的名稱以及出錯(cuò)提示信息。

stack屬性用來查看錯(cuò)誤發(fā)生時(shí)的堆棧哟玷。

functionthrowit(){thrownewError('');}functioncatchit(){try{? ? throwit();? }catch(e) {console.log(e.stack);// print stack trace}}catchit()// Error//? ? at throwit (~/examples/throwcatch.js:9:11)//? ? at catchit (~/examples/throwcatch.js:3:9)//? ? at repl:1:5

上面代碼顯示狮辽,拋出錯(cuò)誤首先是在throwit函數(shù),然后是在catchit函數(shù)巢寡,最后是在函數(shù)的運(yùn)行環(huán)境中隘竭。

JavaScript的原生錯(cuò)誤類型

Error對(duì)象是最一般的錯(cuò)誤類型,在它的基礎(chǔ)上讼渊,JavaScript還定義了其他6種錯(cuò)誤动看,也就是說,存在Error的6個(gè)派生對(duì)象爪幻。

(1)SyntaxError

SyntaxError是解析代碼時(shí)發(fā)生的語法錯(cuò)誤菱皆。

// 變量名錯(cuò)誤var1a;// 缺少括號(hào)console.log'hello');

(2)ReferenceError

ReferenceError是引用一個(gè)不存在的變量時(shí)發(fā)生的錯(cuò)誤。

unknownVariable// ReferenceError: unknownVariable is not defined

另一種觸發(fā)場(chǎng)景是挨稿,將一個(gè)值分配給無法分配的對(duì)象仇轻,比如對(duì)函數(shù)的運(yùn)行結(jié)果或者this賦值。

console.log() =1// ReferenceError: Invalid left-hand side in assignmentthis=1// ReferenceError: Invalid left-hand side in assignment

上面代碼對(duì)函數(shù)console.log的運(yùn)行結(jié)果和this賦值奶甘,結(jié)果都引發(fā)了ReferenceError錯(cuò)誤篷店。

(3)RangeError

RangeError是當(dāng)一個(gè)值超出有效范圍時(shí)發(fā)生的錯(cuò)誤。主要有幾種情況臭家,一是數(shù)組長(zhǎng)度為負(fù)數(shù)疲陕,二是Number對(duì)象的方法參數(shù)超出范圍,以及函數(shù)堆棧超過最大值钉赁。

newArray(-1)// RangeError: Invalid array length(1234).toExponential(21)// RangeError: toExponential() argument must be between 0 and 20

(4)TypeError

TypeError是變量或參數(shù)不是預(yù)期類型時(shí)發(fā)生的錯(cuò)誤蹄殃。比如,對(duì)字符串你踩、布爾值诅岩、數(shù)值等原始類型的值使用new命令讳苦,就會(huì)拋出這種錯(cuò)誤,因?yàn)閚ew命令的參數(shù)應(yīng)該是一個(gè)構(gòu)造函數(shù)吩谦。

new123//TypeError: number is not a funcvarobj = {};obj.unknownMethod()// TypeError: undefined is not a function

上面代碼的第二種情況鸳谜,調(diào)用對(duì)象不存在的方法,會(huì)拋出TypeError錯(cuò)誤式廷。

(5)URIError

URIError是URI相關(guān)函數(shù)的參數(shù)不正確時(shí)拋出的錯(cuò)誤咐扭,主要涉及encodeURI()、decodeURI()懒棉、encodeURIComponent()草描、decodeURIComponent()、escape()和unescape()這六個(gè)函數(shù)策严。

decodeURI('%2')// URIError: URI malformed

(6)EvalError

eval函數(shù)沒有被正確執(zhí)行時(shí)穗慕,會(huì)拋出EvalError錯(cuò)誤。該錯(cuò)誤類型已經(jīng)不再在ES5中出現(xiàn)了妻导,只是為了保證與以前代碼兼容逛绵,才繼續(xù)保留。

以上這6種派生錯(cuò)誤倔韭,連同原始的Error對(duì)象术浪,都是構(gòu)造函數(shù)。開發(fā)者可以使用它們寿酌,人為生成錯(cuò)誤對(duì)象的實(shí)例胰苏。

newError("出錯(cuò)了!");newRangeError("出錯(cuò)了醇疼,變量超出有效范圍硕并!");newTypeError("出錯(cuò)了,變量類型無效秧荆!");

上面代碼表示新建錯(cuò)誤對(duì)象的實(shí)例倔毙,實(shí)質(zhì)就是手動(dòng)拋出錯(cuò)誤∫冶簦可以看到陕赃,錯(cuò)誤對(duì)象的構(gòu)造函數(shù)接受一個(gè)參數(shù),代表錯(cuò)誤提示信息(message)颁股。

自定義錯(cuò)誤

除了JavaScript內(nèi)建的7種錯(cuò)誤對(duì)象么库,還可以定義自己的錯(cuò)誤對(duì)象。

functionUserError(message){this.message = message ||"默認(rèn)信息";this.name ="UserError";}UserError.prototype =newError();UserError.prototype.constructor = UserError;

上面代碼自定義一個(gè)錯(cuò)誤對(duì)象UserError豌蟋,讓它繼承Error對(duì)象廊散。然后,就可以生成這種自定義的錯(cuò)誤了梧疲。

newUserError("這是自定義的錯(cuò)誤允睹!");

throw語句

throw語句的作用是中斷程序執(zhí)行,拋出一個(gè)意外或錯(cuò)誤幌氮。它接受一個(gè)表達(dá)式作為參數(shù)缭受,可以拋出各種值。

// 拋出一個(gè)字符串throw"Error该互!";// 拋出一個(gè)數(shù)值throw42;// 拋出一個(gè)布爾值throwtrue;// 拋出一個(gè)對(duì)象throw{toString:function(){return"Error!"; } };

上面代碼表示米者,throw可以接受各種值作為參數(shù)。JavaScript引擎一旦遇到throw語句宇智,就會(huì)停止執(zhí)行后面的語句蔓搞,并將throw語句的參數(shù)值,返回給用戶随橘。

如果只是簡(jiǎn)單的錯(cuò)誤喂分,返回一條出錯(cuò)信息就可以了柒爸,但是如果遇到復(fù)雜的情況碍舍,就需要在出錯(cuò)以后進(jìn)一步處理拷窜。這時(shí)最好的做法是使用throw語句手動(dòng)拋出一個(gè)Error對(duì)象搓萧。

thrownewError('出錯(cuò)了!');

上面語句新建一個(gè)Error對(duì)象催什,然后將這個(gè)對(duì)象拋出柴我,整個(gè)程序就會(huì)中斷在這個(gè)地方痴脾。

throw語句還可以拋出用戶自定義的錯(cuò)誤纱控。

functionUserError(message){this.message = message ||"默認(rèn)信息";this.name ="UserError";}UserError.prototype.toString =function(){returnthis.name +': "'+this.message +'"';}thrownewUserError("出錯(cuò)了牙言!");

可以通過自定義一個(gè)assert函數(shù)酸钦,規(guī)范化throw拋出的信息。

functionassert(expression, message){if(!expression)throw{name:'Assertion Exception',message: message};}

上面代碼定義了一個(gè)assert函數(shù)咱枉,它接受一個(gè)表達(dá)式和一個(gè)字符串作為參數(shù)卑硫。一旦表達(dá)式不為真,就拋出指定的字符串庞钢。它的用法如下拔恰。

assert(typeofmyVar !='undefined','myVar is undefined!');

console對(duì)象的assert方法,與上面函數(shù)的工作機(jī)制一模一樣基括,所以可以直接使用颜懊。

console.assert(typeofmyVar !='undefined','myVar is undefined!');

try...catch結(jié)構(gòu)

為了對(duì)錯(cuò)誤進(jìn)行處理,需要使用try...catch結(jié)構(gòu)风皿。

try{thrownewError('出錯(cuò)了!');}catch(e) {console.log(e.name +": "+ e.message);console.log(e.stack);}// Error: 出錯(cuò)了!//? at :3:9//? ...

上面代碼中河爹,try代碼塊一拋出錯(cuò)誤(上例用的是throw語句),JavaScript引擎就立即把代碼的執(zhí)行桐款,轉(zhuǎn)到catch代碼塊咸这。可以看作魔眨,錯(cuò)誤可以被catch代碼塊捕獲媳维。catch接受一個(gè)參數(shù)酿雪,表示try代碼塊拋出的值。

functionthrowIt(exception){try{throwexception;? }catch(e) {console.log('Caught: '+ e);? }}throwIt(3);// Caught: 3throwIt('hello');// Caught: hellothrowIt(newError('An error happened'));// Caught: Error: An error happened

上面代碼中侄刽,throw語句先后拋出數(shù)值指黎、字符串和錯(cuò)誤對(duì)象。

catch代碼塊捕獲錯(cuò)誤之后州丹,程序不會(huì)中斷醋安,會(huì)按照正常流程繼續(xù)執(zhí)行下去。

try{throw"出錯(cuò)了";}catch(e) {console.log(111);}console.log(222);// 111// 222

上面代碼中墓毒,try代碼塊拋出的錯(cuò)誤吓揪,被catch代碼塊捕獲后,程序會(huì)繼續(xù)向下執(zhí)行所计。

catch代碼塊之中柠辞,還可以再拋出錯(cuò)誤,甚至使用嵌套的try...catch結(jié)構(gòu)醉箕。

varn =100;try{thrown;}catch(e) {if(e <=50) {// ...}else{throwe;? }}

上面代碼中钾腺,catch代碼之中又拋出了一個(gè)錯(cuò)誤。

為了捕捉不同類型的錯(cuò)誤讥裤,catch代碼塊之中可以加入判斷語句放棒。

try{? foo.bar();}catch(e) {if(einstanceofEvalError) {console.log(e.name +": "+ e.message);? }elseif(einstanceofRangeError) {console.log(e.name +": "+ e.message);? }// ...}

上面代碼中,catch捕獲錯(cuò)誤之后己英,會(huì)判斷錯(cuò)誤類型(EvalError還是RangeError)间螟,進(jìn)行不同的處理。

try...catch結(jié)構(gòu)是JavaScript語言受到Java語言影響的一個(gè)明顯的例子损肛。這種結(jié)構(gòu)多多少少是對(duì)結(jié)構(gòu)化編程原則一種破壞厢破,處理不當(dāng)就會(huì)變成類似goto語句的效果,應(yīng)該謹(jǐn)慎使用治拿。

finally代碼塊

try...catch結(jié)構(gòu)允許在最后添加一個(gè)finally代碼塊摩泪,表示不管是否出現(xiàn)錯(cuò)誤,都必需在最后運(yùn)行的語句劫谅。

functioncleansUp(){try{thrownewError('Sorry...');? }finally{console.log('Performing clean-up');? }}cleansUp()// Performing clean-up// Error: Sorry...

上面代碼說明见坑,throw語句拋出錯(cuò)誤以后,finally繼續(xù)得到執(zhí)行捏检。

functionidle(x){try{console.log(x);return'result';? }finally{console.log("FINALLY");? }}idle('hello')// hello// FINALLY// "result"

上面代碼說明荞驴,即使有return語句在前,finally代碼塊依然會(huì)得到執(zhí)行贯城,且在其執(zhí)行完畢后熊楼,才會(huì)顯示return語句的值。

下面的例子說明能犯,return語句的執(zhí)行是排在finally代碼之前鲫骗,只是等finally代碼執(zhí)行完畢后才返回犬耻。

varcount =0;functioncountUp(){try{returncount;? }finally{? ? count++;? }}countUp()// 0count// 1

上面代碼說明,return語句的count的值挎峦,是在finally代碼塊運(yùn)行之前香追,就獲取完成了合瓢。

下面是finally代碼塊用法的典型場(chǎng)景坦胶。

openFile();try{? writeFile(Data);}catch(e) {? handleError(e);}finally{? closeFile();}

上面代碼首先打開一個(gè)文件,然后在try代碼塊中寫入文件晴楔,如果沒有發(fā)生錯(cuò)誤顿苇,則運(yùn)行finally代碼塊關(guān)閉文件;一旦發(fā)生錯(cuò)誤税弃,則先使用catch代碼塊處理錯(cuò)誤纪岁,再使用finally代碼塊關(guān)閉文件。

下面的例子充分反應(yīng)了try...catch...finally這三者之間的執(zhí)行順序则果。

functionf(){try{console.log(0);throw"bug";? }catch(e) {console.log(1);returntrue;// 這句原本會(huì)延遲到finally代碼塊結(jié)束再執(zhí)行console.log(2);// 不會(huì)運(yùn)行}finally{console.log(3);returnfalse;// 這句會(huì)覆蓋掉前面那句returnconsole.log(4);// 不會(huì)運(yùn)行}console.log(5);// 不會(huì)運(yùn)行}varresult = f();// 0// 1// 3result// false

上面代碼中幔翰,catch代碼塊結(jié)束執(zhí)行之前,會(huì)先執(zhí)行finally代碼塊西壮。從catch轉(zhuǎn)入finally的標(biāo)志遗增,不僅有return語句,還有throw語句款青。

functionf(){try{throw'出錯(cuò)了做修!';? }catch(e) {console.log('捕捉到內(nèi)部錯(cuò)誤');throwe;// 這句原本會(huì)等到finally結(jié)束再執(zhí)行}finally{returnfalse;// 直接返回}}try{? f();}catch(e) {// 此處不會(huì)執(zhí)行console.log('caught outer "bogus"');}//? 捕捉到內(nèi)部錯(cuò)誤

上面代碼中,進(jìn)入catch代碼塊之后抡草,一遇到throw語句饰及,就會(huì)去執(zhí)行finally代碼塊,其中有return false語句康震,因此就直接返回了燎含,不再會(huì)回去執(zhí)行catch代碼塊剩下的部分了。

某些情況下腿短,甚至可以省略catch代碼塊屏箍,只使用finally代碼塊。

openFile();try{? writeFile(Data);}finally{? closeFile();}

編程風(fēng)格

所謂"編程風(fēng)格"(programming style)答姥,指的是編寫代碼的樣式規(guī)則铣除。不同的程序員,往往有不同的編程風(fēng)格鹦付。

有人說尚粘,編譯器的規(guī)范叫做"語法規(guī)則"(grammar),這是程序員必須遵守的敲长;而編譯器忽略的部分郎嫁,就叫"編程風(fēng)格"(programming style)秉继,這是程序員可以自由選擇的。這種說法不完全正確泽铛,程序員固然可以自由選擇編程風(fēng)格尚辑,但是好的編程風(fēng)格有助于寫出質(zhì)量更高、錯(cuò)誤更少盔腔、更易于維護(hù)的程序杠茬。

所以,"編程風(fēng)格"的選擇不應(yīng)該基于個(gè)人愛好弛随、熟悉程度瓢喉、打字量等因素,而要考慮如何盡量使代碼清晰易讀舀透、減少出錯(cuò)栓票。你選擇的,不是你喜歡的風(fēng)格愕够,而是一種能夠清晰表達(dá)你的意圖的風(fēng)格走贪。這一點(diǎn),對(duì)于JavaScript這種語法自由度很高的語言尤其重要惑芭。

必須牢記的一點(diǎn)是坠狡,如果你選定了一種“編程風(fēng)格”,就應(yīng)該堅(jiān)持遵守强衡,切忌多種風(fēng)格混用擦秽。如果你加入他人的項(xiàng)目,就應(yīng)該遵守現(xiàn)有的風(fēng)格漩勤。

縮進(jìn)

空格和Tab鍵感挥,都可以產(chǎn)生縮進(jìn)效果(indent)。

Tab鍵可以節(jié)省擊鍵次數(shù)越败,但不同的文本編輯器對(duì)Tab的顯示不盡相同触幼,有的顯示四個(gè)空格,有的顯示兩個(gè)空格究飞,所以有人覺得置谦,空格鍵可以使得顯示效果更統(tǒng)一。

無論你選擇哪一種方法亿傅,都是可以接受的媒峡,要做的就是始終堅(jiān)持這一種選擇。不要一會(huì)使用Tab鍵葵擎,一會(huì)使用空格鍵谅阿。

區(qū)塊

如果循環(huán)和判斷的代碼體只有一行,JavaScript允許該區(qū)塊(block)省略大括號(hào)。

if(a)? b();? c();

上面代碼的原意可能是下面這樣签餐。

if(a) {? b();? c();}

但是寓涨,實(shí)際效果卻是下面這樣。

if(a) {? b();}? c();

因此氯檐,總是使用大括號(hào)表示區(qū)塊戒良。

另外,區(qū)塊起首的大括號(hào)的位置冠摄,有許多不同的寫法糯崎。

最流行的有兩種。一種是起首的大括號(hào)另起一行:

block{// ...}

另一種是起首的大括號(hào)跟在關(guān)鍵字的后面耗拓。

block {// ...}

一般來說拇颅,這兩種寫法都可以接受。但是乔询,JavaScript要使用后一種,因?yàn)镴avaScript會(huì)自動(dòng)添加句末的分號(hào)韵洋,導(dǎo)致一些難以察覺的錯(cuò)誤竿刁。

return{key: value};// 相當(dāng)于return;{key: value};

上面的代碼的原意,是要返回一個(gè)對(duì)象搪缨,但實(shí)際上返回的是undefined食拜,因?yàn)镴avaScript自動(dòng)在return語句后面添加了分號(hào)。為了避免這一類錯(cuò)誤副编,需要寫成下面這樣负甸。

return{key: value};

因此,表示區(qū)塊起首的大括號(hào)痹届,不要另起一行呻待。

圓括號(hào)

圓括號(hào)(parentheses)在JavaScript中有兩種作用,一種表示函數(shù)的調(diào)用队腐,另一種表示表達(dá)式的組合(grouping)蚕捉。

// 圓括號(hào)表示函數(shù)的調(diào)用console.log('abc');// 圓括號(hào)表示表達(dá)式的組合(1+2) *3

我們可以用空格,區(qū)分這兩種不同的括號(hào)柴淘。

表示函數(shù)調(diào)用時(shí)迫淹,函數(shù)名與左括號(hào)之間沒有空格。

表示函數(shù)定義時(shí)为严,函數(shù)名與左括號(hào)之間沒有空格敛熬。

其他情況時(shí),前面位置的語法元素與左括號(hào)之間第股,都有一個(gè)空格应民。

按照上面的規(guī)則,下面的寫法都是不規(guī)范的。

foo (bar)return(a+b);if(a ===0) {...}functionfoo(b){...}function(x){...}

上面代碼的最后一行是一個(gè)匿名函數(shù)瑞妇,function是語法關(guān)鍵字稿静,不是函數(shù)名幕屹,所以與左括號(hào)之間應(yīng)該要有一個(gè)空格迎献。

行尾的分號(hào)

分號(hào)表示一條語句的結(jié)束。JavaScript規(guī)定矗积,行尾的分號(hào)可以省略蔓倍。事實(shí)上悬钳,確實(shí)有一些開發(fā)者行尾從來不寫分號(hào)。但是偶翅,由于下面要討論的原因默勾,建議還是不要這個(gè)分號(hào)。

不使用分號(hào)的情況

有一些語法結(jié)構(gòu)不需要在語句的結(jié)尾添加分號(hào)聚谁,主要是以下三種情況母剥。

(1)for和while循環(huán)

for( ; ; ) {}// 沒有分號(hào)while(true) {}// 沒有分號(hào)

需要注意的是do...while循環(huán)是有分號(hào)的。

do{? a--;}while(a >0);// 分號(hào)不能省略

(2)分支語句:if形导,switch环疼,try

if(true) {}// 沒有分號(hào)switch() {}// 沒有分號(hào)try{}catch{}// 沒有分號(hào)

(3)函數(shù)的聲明語句

functionf(){}// 沒有分號(hào)

但是函數(shù)表達(dá)式仍然要使用分號(hào)。

varf =functionf(){};

以上三種情況朵耕,如果使用了分號(hào)炫隶,并不會(huì)出錯(cuò)。因?yàn)檠植埽忉屢鏁?huì)把這個(gè)分號(hào)解釋為空語句伪阶。

分號(hào)的自動(dòng)添加

除了上一節(jié)的三種情況,所有語句都應(yīng)該使用分號(hào)处嫌。但是栅贴,如果沒有使用分號(hào),大多數(shù)情況下锰霜,JavaScript會(huì)自動(dòng)添加筹误。

vara =1// 等同于vara =1;

這種語法特性被稱為“分號(hào)的自動(dòng)添加”(Automatic Semicolon Insertion,簡(jiǎn)稱ASI)癣缅。

因此厨剪,有人提倡省略句尾的分號(hào)。麻煩的是友存,如果下一行的開始可以與本行的結(jié)尾連在一起解釋祷膳,JavaScript就不會(huì)自動(dòng)添加分號(hào)。

// 等同于 var a = 3vara=3// 等同于 'abc'.length'abc'.length// 等同于 return a + b;returna +b;// 等同于 obj.foo(arg1, arg2);obj.foo(arg1,arg2);// 等同于 3 * 2 + 10 * (27 / 6)3*2+10* (27/6)

上面代碼都會(huì)多行放在一起解釋屡立,不會(huì)每一行自動(dòng)添加分號(hào)直晨。這些例子還是比較容易看出來的,但是下面這個(gè)例子就不那么容易看出來了。

x = y(function(){// ...})();// 等同于x = y(function(){...})();

下面是更多不會(huì)自動(dòng)添加分號(hào)的例子勇皇。

// 解釋為 c(d+e)vara = b + c(d+e).toString();// 解釋為 a = b/hi/g.exec(c).map(d)// 正則表達(dá)式的斜杠罩句,會(huì)當(dāng)作除法運(yùn)算符a = b/hi/g.exec(c).map(d);// 解釋為'b'['red', 'green'],// 即把字符串當(dāng)作一個(gè)數(shù)組敛摘,按索引取值vara ='b'['red','green'].forEach(function(c){console.log(c);})// 解釋為 function(x) { return x }(a++)// 即調(diào)用匿名函數(shù)门烂,結(jié)果f等于0vara =0;varf =function(x){returnx }(a++)

只有下一行的開始與本行的結(jié)尾,無法放在一起解釋兄淫,JavaScript引擎才會(huì)自動(dòng)添加分號(hào)屯远。

if(a <0) a =0console.log(a)// 等同于下面的代碼,// 因?yàn)?console沒有意義if(a <0) a =0;console.log(a)

另外捕虽,如果一行的起首是“自增”(++)或“自減”(--)運(yùn)算符慨丐,則它們的前面會(huì)自動(dòng)添加分號(hào)。

a = b = c =1a++b--cconsole.log(a, b, c)// 1 2 0

上面代碼之所以會(huì)得到“1 2 0”的結(jié)果泄私,原因是自增和自減運(yùn)算符前房揭,自動(dòng)加上了分號(hào)。上面的代碼實(shí)際上等同于下面的形式挖滤。

a = b = c =1;a;++b;--c;

如果continue崩溪、break、return和throw這四個(gè)語句后面斩松,直接跟換行符,則會(huì)自動(dòng)添加分號(hào)觉既。這意味著惧盹,如果return語句返回的是一個(gè)對(duì)象的字面量,起首的大括號(hào)一定要寫在同一行瞪讼,否則得不到預(yù)期結(jié)果钧椰。

return{first:'Jane'};// 解釋成return;{first:'Jane'};

由于解釋引擎自動(dòng)添加分號(hào)的行為難以預(yù)測(cè),因此編寫代碼的時(shí)候不應(yīng)該省略行尾的分號(hào)符欠。

不應(yīng)該省略結(jié)尾的分號(hào)嫡霞,還有一個(gè)原因。有些JavaScript代碼壓縮器不會(huì)自動(dòng)添加分號(hào)希柿,因此遇到?jīng)]有分號(hào)的結(jié)尾诊沪,就會(huì)讓代碼保持原狀,而不是壓縮成一行曾撤,使得壓縮無法得到最優(yōu)的結(jié)果端姚。

另外,不寫結(jié)尾的分號(hào)挤悉,可能會(huì)導(dǎo)致腳本合并出錯(cuò)渐裸。所以,有的代碼庫(kù)在第一行語句開始前,會(huì)加上一個(gè)分號(hào)昏鹃。

;vara =1;// ...

上面這種寫法就可以避免與其他腳本合并時(shí)尚氛,排在前面的腳本最后一行語句沒有分號(hào),導(dǎo)致運(yùn)行出錯(cuò)的問題洞渤。

全局變量

JavaScript最大的語法缺點(diǎn)阅嘶,可能就是全局變量對(duì)于任何一個(gè)代碼塊,都是可讀可寫您宪。這對(duì)代碼的模塊化和重復(fù)使用奈懒,非常不利。

因此宪巨,避免使用全局變量磷杏。如果不得不使用,用大寫字母表示變量名捏卓,比如UPPER_CASE极祸。

變量聲明

JavaScript會(huì)自動(dòng)將變量聲明"提升"(hoist)到代碼塊(block)的頭部。

if(!o) {varo = {};}// 等同于varo;if(!o) {? o = {};}

為了避免可能出現(xiàn)的問題怠晴,最好把變量聲明都放在代碼塊的頭部遥金。

for(vari =0; i <10; i++) {// ...}// 寫成vari;for(i =0; i <10; i++) {// ...}

另外,所有函數(shù)都應(yīng)該在使用之前定義蒜田,函數(shù)內(nèi)部的變量聲明稿械,都應(yīng)該放在函數(shù)的頭部。

new命令

JavaScript使用new命令冲粤,從構(gòu)造函數(shù)生成一個(gè)新對(duì)象美莫。

varo =newmyObject();

上面這種做法的問題是,一旦你忘了加上new梯捕,myObject()內(nèi)部的this關(guān)鍵字就會(huì)指向全局對(duì)象厢呵,導(dǎo)致所有綁定在this上面的變量,都變成全局變量傀顾。

因此襟铭,建議使用Object.create()命令,替代new命令短曾。如果不得不使用new寒砖,為了防止出錯(cuò),最好在視覺上把構(gòu)造函數(shù)與其他函數(shù)區(qū)分開來错英。比如入撒,構(gòu)造函數(shù)的函數(shù)名,采用首字母大寫(InitialCap)椭岩,其他函數(shù)名一律首字母小寫茅逮。

with語句

with可以減少代碼的書寫璃赡,但是會(huì)造成混淆。

with(o) { foo = bar;}

上面的代碼献雅,可以有四種運(yùn)行結(jié)果:

o.foo = bar;

o.foo = o.bar;

foo = bar;

foo = o.bar;

這四種結(jié)果都可能發(fā)生碉考,取決于不同的變量是否有定義。因此挺身,不要使用with語句侯谁。

相等和嚴(yán)格相等

JavaScript有兩個(gè)表示"相等"的運(yùn)算符:"相等"(==)和"嚴(yán)格相等"(===)。

因?yàn)?相等"運(yùn)算符會(huì)自動(dòng)轉(zhuǎn)換變量類型章钾,造成很多意想不到的情況:

0==''// true1==true// true2==true// false0=='0'// truefalse=='false'// falsefalse=='0'// true’ \t\r\n' == 0 // true

因此墙贱,不要使用“相等”(==)運(yùn)算符,只使用“嚴(yán)格相等”(===)運(yùn)算符贱傀。

語句的合并

有些程序員追求簡(jiǎn)潔惨撇,喜歡合并不同目的的語句。比如府寒,原來的語句是

a = b;if(a) {// ...}

他喜歡寫成下面這樣魁衙。

if(a = b) {// ...}

雖然語句少了一行,但是可讀性大打折扣株搔,而且會(huì)造成誤讀剖淀,讓別人誤解這行代碼的意思是下面這樣。

if(a === b){// ...}

建議不要將不同目的的語句纤房,合并成一行纵隔。

自增和自減運(yùn)算符

自增(++)和自減(--)運(yùn)算符,放在變量的前面或后面炮姨,返回的值不一樣巨朦,很容易發(fā)生錯(cuò)誤。事實(shí)上剑令,所有的++運(yùn)算符都可以用+= 1代替。

++x// 等同于x +=1;

改用+= 1拄查,代碼變得更清晰了吁津。有一個(gè)很可笑的例子,某個(gè)JavaScript函數(shù)庫(kù)的源代碼中出現(xiàn)了下面的片段:

++x;

++x;

這個(gè)程序員忘了堕扶,還有更簡(jiǎn)單碍脏、更合理的寫法。

x +=2;

建議自增(++)和自減(--)運(yùn)算符盡量使用+=和-=代替稍算。

switch...case結(jié)構(gòu)

switch...case結(jié)構(gòu)要求典尾,在每一個(gè)case的最后一行必須是break語句,否則會(huì)接著運(yùn)行下一個(gè)case糊探。這樣不僅容易忘記钾埂,還會(huì)造成代碼的冗長(zhǎng)河闰。

而且,switch...case不使用大括號(hào)褥紫,不利于代碼形式的統(tǒng)一姜性。此外,這種結(jié)構(gòu)類似于goto語句髓考,容易造成程序流程的混亂部念,使得代碼結(jié)構(gòu)混亂不堪,不符合面向?qū)ο缶幊痰脑瓌t氨菇。

functiondoAction(action){switch(action) {case'hack':return'hack';break;case'slash':return'slash';break;case'run':return'run';break;default:thrownewError('Invalid action.');? }}

上面的代碼建議改寫成對(duì)象結(jié)構(gòu)儡炼。

functiondoAction(action){varactions = {'hack':function(){return'hack';? ? },'slash':function(){return'slash';? ? },'run':function(){return'run';? ? }? };if(typeofactions[action] !=='function') {thrownewError('Invalid action.');? }returnactions[action]();}

建議避免使用switch...case結(jié)構(gòu),用對(duì)象結(jié)構(gòu)代替查蓉。


著作權(quán)歸作者所有乌询。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處js基礎(chǔ)知識(shí)奶是。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末楣责,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子聂沙,更是在濱河造成了極大的恐慌秆麸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件及汉,死亡現(xiàn)場(chǎng)離奇詭異沮趣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)坷随,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門房铭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人温眉,你說我怎么就攤上這事缸匪。” “怎么了类溢?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵凌蔬,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我闯冷,道長(zhǎng)砂心,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任蛇耀,我火速辦了婚禮辩诞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纺涤。我一直安慰自己译暂,他們只是感情好抠忘,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪书幕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天荧嵌,我揣著相機(jī)與錄音,去河邊找鬼砾淌。 笑死啦撮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的汪厨。 我是一名探鬼主播赃春,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼劫乱!你這毒婦竟也來了织中?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤衷戈,失蹤者是張志新(化名)和其女友劉穎狭吼,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體殖妇,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刁笙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谦趣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疲吸。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖前鹅,靈堂內(nèi)的尸體忽然破棺而出摘悴,到底是詐尸還是另有隱情,我是刑警寧澤舰绘,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布烦租,位于F島的核電站,受9級(jí)特大地震影響除盏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挫以,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一者蠕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掐松,春花似錦踱侣、人聲如沸粪小。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽探膊。三九已至,卻和暖如春待榔,著一層夾襖步出監(jiān)牢的瞬間逞壁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工锐锣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腌闯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓雕憔,卻偏偏與公主長(zhǎng)得像姿骏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子斤彼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345