JavaScript語言精粹

JavaScript語言精粹

前言

約定:=> 表示參考相關(guān)文章或書籍; JS是JavaScript的縮寫。

本書專注于JavaScript的精華部分,同時會偶爾警告要去避免的糟粕部分。作者提煉出的JavaScript精華子集,更可靠,更易讀,更易于維護。

本書的目的是揭示JavaScript中的精華,讓大家知道它是一門杰出的動態(tài)編程語言。

文章有點長,大家就先湊合著看吧,趁著昨晚微信小程序開放公測了,現(xiàn)在學(xué)好JS,干一波微信小程序

JavaScript:世界上最被誤解的語言

第1章 精華

為什么要使用JavaScript語言

你沒有選擇,JavaScript是唯一一門所有瀏覽器都可以識別的語言。

JavaScript有缺陷,但它真的很優(yōu)秀。輕量級又富有表現(xiàn)力,一旦熟練掌握就能體會到函數(shù)式編程的樂趣。

分析JavaScript

JavaScript優(yōu)秀的思想:函數(shù),弱類型,動態(tài)對象,對象字面量。糟糕的思想:基于全局變量的編程模型。

函數(shù):基于詞法來劃分作用域,而不是動態(tài)劃分作用域 =>《JavaScript權(quán)威指南》第5章“8.8.1 詞法作用域”。

弱類型:強類型允許編譯器在編譯時檢測錯誤。事實證明,強類型并不會讓你的測試工作變得輕松。弱類型是自由的,不需要建立復(fù)雜的類層次,也不用做強制類型轉(zhuǎn)換。

對象字面量:通過列出對象的組成部分,它們就能簡單地被創(chuàng)建出來。JSON的靈感來源于此(作者是JSON的創(chuàng)立者)。

全局變量:JavaScript依賴于全局變量來進行連接。所有編譯單元的所有頂級變量被撮合到一個被稱為全局對象(the global object)的公共命名空間中。全局變量是魔鬼,而它們在JavaScript中卻是基礎(chǔ),非常糟糕!

一個簡單的實驗場

第2章 語法

空白

空白可能表現(xiàn)為被格式化的字符或注釋的形式。空白通常沒有意義,但有時候必須要用它來分隔字符序列,否則它們就會被合并成一個符號。

varthat=this;//var和this之間的空格不能移除,其他的空格都可以移除

注釋:JavaScript提供兩種注釋形式,塊注釋和行注釋。注釋應(yīng)該被優(yōu)先用來提高程序的可讀性。

注釋一定要精確地描述代碼,沒有用的注釋比沒有注釋更糟糕!

塊注釋:/* */,由于這些字符可能出現(xiàn)在正則表達(dá)式字面量里,所以不建議使用塊注釋。

行注釋://,建議使用行注釋替代塊注釋。

標(biāo)識符

標(biāo)識符由一個字母開頭(JS規(guī)范中還允許以下劃線_和美元符$開頭),其后可選擇性地加上一個或多個字母,數(shù)字或下劃線。

標(biāo)識符不能使用的保留字:abstract,boolean,break,byte,case,catch,char,class,const,continue,debugger,default,delete,do,double,else,enum,export,extends,false,final,finally,float,for,function,goto,if,implements,import,in,instanceof,int,interface,long,native,new,null,package,private,protected,public,return,short,static,supper,switch,synchronized,this,throw,throws,transient,true,try,typeof,var,volatile,void,while,with。

該列表中不包括一些本應(yīng)該被保留而沒有保留的字,諸如undefined,NaN和Infinity。

JS不允許使用保留字來命名變量或參數(shù)。更糟糕的是,JS不允許在對象字面量中,或者用點運算符提取對象屬性時,使用保留字作為對象的屬性名。

標(biāo)識符被用于語句,變量,參數(shù),屬性名,運算符和標(biāo)記。

數(shù)字

JS只有一個數(shù)字類型,在內(nèi)部被表示為64位的浮點數(shù),和Java的double數(shù)字類型一樣。與其他大多數(shù)編程語言不同的是,它沒有分離出整數(shù)類型,所以1和1.0的值相同。這避免了短整型的溢出問題和一大堆因數(shù)字類型導(dǎo)致的錯誤。

數(shù)字字面量有指數(shù)部分,則這個字面量的值等于e之前的數(shù)字與10的e之后數(shù)字的次方相乘。100=1e2。

負(fù)數(shù):前置運算符-加數(shù)字。-100。

NaN:一個數(shù)值,表示不能產(chǎn)生正常運算結(jié)果。NaN不等于任何值,包括它自己??梢允褂煤瘮?shù)isNaN(number)來檢測NaN。

Infinity:表示所有大于1.79769313486231570e+308的值。

數(shù)字擁有方法,JS中的Math對象包含一套作用于數(shù)字的方法。Math.floor(number)可以將一個數(shù)字轉(zhuǎn)換為一個整數(shù)。

字符串

字符串字面量可以被包在一對單引號或雙引號中,可能包含0個或多個字符。\反斜杠是轉(zhuǎn)義字符。JS被創(chuàng)建的時候,Unicode是一個16位的字符集,所以JS中的所有字符都是16位的。

JS沒有字符類型。要表示一個字符,只需創(chuàng)建僅包含一個字符的字符串即可。

轉(zhuǎn)義字符:用于把正常情況下不被允許的字符插入到字符串中,比如反斜線,引號和控制符。\u約定用來指定數(shù)字字符編碼。"A" == "\u0041"。

字符串是不可變的,可以通過length屬性獲取長度,通過+連接其他字符串。兩個包含完全相同的字符且字符順序也相同的字符串被認(rèn)為是相同的字符串,'c' + 'a' + 't' === 'cat'。

字符串有相應(yīng)的方法,比如'cat'.toUpperCase() === 'CAT'。

語句

var:var語句用于函數(shù)內(nèi)部,則定義的是這個函數(shù)的私有變量。

label:swith,while,for和do語句允許有一個可選的前置標(biāo)簽(label),它配合break語句來使用。

語句執(zhí)行順序:通常按照從上到下的順序執(zhí)行,JS可以通過條件語句(if和switch),循環(huán)語句(while,for和do),強制跳轉(zhuǎn)語句(break,return和throw)和函數(shù)調(diào)用來改變執(zhí)行序列。

代碼塊:包在一對花括號中的一組語句。JS中的代碼塊不會創(chuàng)建新的作用域,因此變量應(yīng)該定義在函數(shù)的頭部,而不是在代碼塊中。

JS的假值:false,null,undefined,空字符串' ',數(shù)字0,數(shù)字NaN,其他所有的值都被當(dāng)做真,包括true, 字符串"false",以及所有的對象。

JS中的語句,比如if,switch,while,for,for in,do,try catch,throw, return(沒有指定返回表達(dá)式,則返回undefined),break,和Java中的語義相同。JS不允許在return關(guān)鍵字和表達(dá)式之間換行,也不允許在break關(guān)鍵字和標(biāo)簽之間換行。

表達(dá)式

表達(dá)式:最簡單的表達(dá)式是字面量值(比如字符串或數(shù)字),變量,內(nèi)置的值(true,false,null,undefined,NaN和Infinity),以new開頭的調(diào)用表達(dá)式,以delete開頭的屬性提取表達(dá)式,包在圓括號中的表達(dá)式,以一個前置運算符作為前導(dǎo)的表達(dá)式,或者表達(dá)式后面跟著:

一個中置運算符與另一個表達(dá)式;

三元運算符?后面跟著另一個表達(dá)式,然后接一個:,再然后接第3個表達(dá)式;

一個函數(shù)調(diào)用;

一個屬性提取表達(dá)式。

運算符優(yōu)先級:下表中排在越上面的優(yōu)先級越高,結(jié)合性越強。圓括號可以改變正常情況下的優(yōu)先級。

運算符說明

() . []調(diào)用函數(shù)與提取屬性

delete new typeof + - !一元運算符

* / %乘法,除法,求余

+ -加法/連接,減法

>= <= > <不等式運算符

=== !==等式運算符

&&邏輯與

II邏輯或

?:三元

typeof:typeof運算符產(chǎn)生的值有'number','string','boolean','undefined','function'和'object'。如果運算符是一個數(shù)組或null,則結(jié)果是'object',其實不應(yīng)該是這樣的?。ㄗ髡咭馑际菓?yīng)該為array或null)。

函數(shù)調(diào)用運算符:函數(shù)調(diào)用引發(fā)函數(shù)的執(zhí)行,函數(shù)調(diào)用運算符是跟隨在函數(shù)名后面的一對圓括號。圓括號中可能包含傳遞給這個函數(shù)的參數(shù)。

屬性存取表達(dá)式:用于獲取或設(shè)置一個對象或數(shù)組的屬性和元素。

字面量

字面量(literal):包括number字面量,string字面量,object字面量,array字面量,function,regexp字面量(正則表達(dá)式)。

對象字面量:一種可以方便地按指定規(guī)格創(chuàng)建新對象的表示法。屬性名可以是標(biāo)識符或字符串,這些名字被當(dāng)作字面量名而不是變量名來對待,所以對象的屬性名在編譯時才能知道。屬性的值就是表達(dá)式。

數(shù)組字面量:一種可以方便地按指定規(guī)格創(chuàng)建新數(shù)組的表示法。

函數(shù)

函數(shù)字面量:定義函數(shù)值,可以指定可選的名字,用于遞歸地調(diào)用自己??梢灾付▍?shù)列表,函數(shù)主體包括變量定義和語句。

第3章 對象

簡單數(shù)據(jù)類型:JS的簡單數(shù)據(jù)類型包括數(shù)字,字符串,布爾值(true和false),null值和undefined值。這些類型雖然擁有方法,但它們是不可變的,所以不能稱為對象。

對象:JS中的對象是可變的鍵控集合(keyed collections)。在JS中,數(shù)組,函數(shù),正則表達(dá)式都是對象。 對象是屬性的容器,其中每個屬性都擁有名字和值。屬性的名字可以是空字符串在內(nèi)的任意字符串。屬性值可以是除undefined值以外的任何值。JS里的對象是無類型的,且允許對象繼承和嵌套。

對象字面量

對象字面量:一個對象字面量就是包圍在一對花括號中的零或多個"名/值"對,它可以方便的創(chuàng)建新對象值。對象字面量可以出現(xiàn)在任何允許表達(dá)式出現(xiàn)的地方。如果屬性名是一個合法的JS標(biāo)識符且不是保留字,則并不強制要求用引號括住屬性名。JS的標(biāo)識符包含連接符(-)是不合法的,但允許包含下劃線(_)。

檢索

檢索對象:可以使用.或[]檢索對象,優(yōu)先考慮使用.表示法,因為它更緊湊且可讀性更好。如果字符串表達(dá)式不是合法的JS標(biāo)識符,則必須使用[]來檢索對象。

檢索一個不存在的成員屬性的值將返回undefined,可以使用||運算符填充默認(rèn)值。

varmiddle=stooge["middle-name"]||"(none)";

嘗試從undefined的成員屬性中取值將導(dǎo)致TypeError異常,可以通過&&運算符避免錯誤。

flight.equipment//undefinedflight.equipment.model//throw 'TypeError'flight.equipment&&flight.equipment.model//undefined

更新

更新對象:對象的值可以通過賦值語句來更新。如果屬性值已經(jīng)存在于對象里,則這個屬性的值會被替換,否則該屬性會被擴充到對象中。

引用

對象引用:對象通過引用來傳遞,它們永遠(yuǎn)不會被復(fù)制。

vara={},b={},c={};//a,b,c引用不同的空對象a=b=c={};//a,b,c引用相同的空對象

原型

原型:每個對象都連接到一個原型對象,并且它可以從中繼承屬性。所有通過對象字面量創(chuàng)建的對象都連接到Ojbect.prototype,它是JS中的標(biāo)配對象。

原型選擇:當(dāng)創(chuàng)建一個新對象時,可以選擇某個對象作為它的原型,JS提供的實現(xiàn)機制雜亂而復(fù)雜,其實可以被明顯地簡化。

原型選擇簡化方法:為Object增加一個create方法,這個方法創(chuàng)建一個使用原對象作為其原型的新對象。

if(typeofObject.create!=='function'){//書中代碼為Object.beget,印刷錯誤?Object.create=function(o){varF=function(){};F.prototype=o;returnnewF();? ? }}varanother_stooge=Object.create(stooge);

原型連接與委托機制:原型連接在更新時不起作用,當(dāng)對某個對象做出改變時,不會觸及該對象的原型。原型連接只在檢索值的時候才被用到。如果嘗試獲取對象中不存在的屬性值,則JS會試著從原型對象中獲取該屬性值。如果原型對象也沒有該屬性,則繼續(xù)從原型對象的原型中尋找,依此類推,直到到達(dá)終點Object.prototype。如果仍舊找不到,則返回undefined。這個過程就是委托。

原型關(guān)系是一種動態(tài)的關(guān)系。如果向原型中添加一個新的屬性,則該屬性會立即對所有基于該原型創(chuàng)建的對象可見。

反射

反射:檢查對象并確定對象的屬性。typeof操作符可以方便的確定屬性的類型。

處理不需要的屬性:當(dāng)你想讓對象在運行時動態(tài)獲自身信息時,關(guān)注更多的是數(shù)據(jù),這時應(yīng)該讓你的程序做檢查并丟棄掉值為函數(shù)的屬性。使用hasOwnProperty方法可以檢查對象是否擁有獨有的屬性,如果有則返回true,它不會檢查原型鏈。

flight.hasOwnProperty('number')//trueflight.hasOwnProperty('constructor')//false

枚舉

for in:可用來遍歷一個對象中的所有屬性名。該枚舉過程將會列出所有的屬性-包括函數(shù)和原型中的屬性,這些一般都需要過濾掉。最常用的過濾器是hasOwnProperty方法,及使用typeof排除函數(shù)。for in遍歷, 屬性名出現(xiàn)的順序是不確定的。如果想要確保屬性以特定的順序出現(xiàn),則創(chuàng)建一個數(shù)組,將屬性以正確的順序放入,使用for獲取它們的值。

刪除

delete運算符:用于刪除對象的屬性。如果對象包含該屬性,則該屬性會被移除。它不會觸及原型中的任何對象。刪除對象的屬性可能會讓來自原型鏈中的屬性透現(xiàn)出來。

減少全局變量污染

JS的全局變量:JS可以很隨意地定義全局變量來容納你的應(yīng)用的所有資源。遺憾的是,全局變量會削弱程序的靈活性,應(yīng)該避免使用。

最小化全局變量:為你的應(yīng)用只創(chuàng)建一個唯一的全局變量?。ê竺鏁榻B另一種有效減少全局污染的方法:閉包)

varMYAPP={};//命名空間,整個應(yīng)用的容器MYAPP.stooge={"first-name":"Joe","last-name":"Howard"};

第4章 函數(shù)

JS設(shè)計最出色的就是它的函數(shù)的實現(xiàn),幾乎接近于完美!

函數(shù):函數(shù)包含一組語句,它們是JS的基礎(chǔ)模塊單元,用于代碼復(fù)用,信息隱藏和組合調(diào)用。函數(shù)用于指定對象的行為。一般來說,編程就是將一組需求分解為一組函數(shù)與數(shù)據(jù)結(jié)構(gòu)的技能。(編程如此簡單??)

函數(shù)對象

函數(shù)對象:JS中的函數(shù)就是對象。對象是"名/值"對的集合并擁有一個連到原型對象的隱藏連接。對象字面量產(chǎn)生的對象連接到Object.prototype。函數(shù)對象連接到Function.prototype(該原型對象本身連接到Object.prototype)。每個函數(shù)在創(chuàng)建時會附加兩個隱藏屬性:函數(shù)的上下文和實現(xiàn)函數(shù)行為的代碼(類似于句柄)。

函數(shù)字面量

函數(shù)字面量:包括4個部分,function + 函數(shù)名(可以省略,匿名函數(shù))+ 圓括號中的參數(shù)列表 + 花括號中的語句。函數(shù)對象是通過函數(shù)字面量來創(chuàng)建的。函數(shù)字面量可以出現(xiàn)在任何允許表達(dá)式出現(xiàn)的地方。

閉包:函數(shù)可以被定義在其他函數(shù)中。一個內(nèi)部函數(shù)除了可以訪問自己的參數(shù)和變量,同時它也能自由訪問父函數(shù)的參數(shù)和變量。通過函數(shù)字面量創(chuàng)建的函數(shù)對象包含一個連到外部上下文的連接。這被成為閉包(closure)。它是JS強大表現(xiàn)力的來源。

調(diào)用

函數(shù)調(diào)用:調(diào)用一個函數(shù)會暫停當(dāng)前函數(shù)的執(zhí)行,傳遞控制權(quán)和參數(shù)給新函數(shù)。除了聲明時定義的形參,每個函數(shù)還接收兩個附加的參數(shù):this和arguments。

this與調(diào)用模式:參數(shù)this的值取決于調(diào)用的模式。在JS中,一共有4種調(diào)用模式:方法調(diào)用模式,函數(shù)調(diào)用模式,構(gòu)造器調(diào)用模式和apply調(diào)用模式。這些模式在如何初始化關(guān)鍵參數(shù)this上存在差異。

調(diào)用運算符:調(diào)用運算符是跟在任何產(chǎn)生一個函數(shù)值的表達(dá)式之后的一對圓括號。圓括號內(nèi)包含參數(shù)列表。實際參數(shù)(arguments)個數(shù)與形式參數(shù)(parameters)個數(shù)不匹配時,不會導(dǎo)致運行時錯誤。如果實參過多,則超出的參數(shù)值會被忽略。如果實參過少,缺失的值會被替換為undefined。對參數(shù)值不會進行類型檢查:任何類型的值都可以被傳遞給任何參數(shù)。

方法調(diào)用模式

方法:當(dāng)一個函數(shù)被保存為對象的一個屬性時,我們稱之為方法。當(dāng)方法被調(diào)用時,this被綁定到該對象。如果調(diào)用表達(dá)式包含一個提取屬性的動作(.或[]),那它就是被當(dāng)作一個方法來調(diào)用。

varmyObj={//創(chuàng)建myObj對象,有一個value屬性和一個increment方法.value:0,increment:function(inc){//increment方法接受一個可選的參數(shù)。如果該參數(shù)不是數(shù)字,則默認(rèn)使用1.this.value+=typeofinc==='number'?inc:1;? }}myObj.increment();document.writeln(myObj.value)// 1myObj.increment();document.writeln(myObj.value)//3

方法可以使用this訪問自己所屬的對象,所以它能從對象中取值或?qū)ο筮M行修改。this到對象的綁定發(fā)生在調(diào)用的時候,這樣的延遲綁定使得函數(shù)可以高度復(fù)用this。

公共方法:通過this可取得它們所屬對象的上下文的方法稱為公共方法。

函數(shù)調(diào)用模式

函數(shù)調(diào)用:當(dāng)一個函數(shù)并非一個對象的屬性時,那么它就是被當(dāng)做一個函數(shù)來調(diào)用的。以此模式調(diào)用函數(shù)時,this被綁定到全局對象。這是語言設(shè)計的一個錯誤!如果設(shè)計正確,那么當(dāng)內(nèi)部函數(shù)被調(diào)用時,this應(yīng)該仍然綁定到外部函數(shù)的this變量。這個錯誤設(shè)計的后果是方法不能利用內(nèi)部函數(shù)來幫助它工作,因為內(nèi)部函數(shù)的this被綁定了錯誤的值,所以不能共享該方法對對象的訪問權(quán)。

解決方案:在外部方法中定義一個變量that,并賦值為this,內(nèi)部函數(shù)可以通過that訪問this。

myObj.double=function(){//給myObj增加一個double方法varthat=this;//解決方法varhelper=function(){//double方法內(nèi)部的函數(shù)that.value=add(that.value,that.value);? ? };helper();//以函數(shù)的形式調(diào)用helper};myObj.double();//以方法的形式調(diào)用doubledocument.writeln(myObj.value);//6

構(gòu)造器調(diào)用模式

JS是一門基于原型繼承的語言,對象可以直接從其他對象繼承屬性。該語言是無類型的。

構(gòu)造器函數(shù):函數(shù)創(chuàng)建的目的是結(jié)合new前綴來調(diào)用,那它就被稱為構(gòu)造器函數(shù)。按照約定,它們保存在以大寫格式命名的變量里。

構(gòu)造器函數(shù)缺點:如果調(diào)用構(gòu)造器函數(shù)時,沒有在前面加上new,可能會產(chǎn)生非常糟糕的事情,即沒有編譯時警告,也沒有運行時警告,所以約定非常重要。不推薦使用這種形式的構(gòu)造器函數(shù)。(下一章有更好的替代方式)

varQuo=function(str){//創(chuàng)建一個名為Quo的構(gòu)造器函數(shù),它創(chuàng)建一個帶有status屬性的對象。this.status=str;};//給Quo的所有實例提供一個get_status的公共方法Quo.prototype.get_status=function(){returnthis.status;};//構(gòu)造一個Quo實例varmyQuo=newQuo("confused");document.writeln(myQuo.get_status());//打印顯示"confused"

Apply調(diào)用模式

JS是一門函數(shù)式的面向?qū)ο缶幊陶Z言,所以函數(shù)可以擁有方法。

apply方法:apply方法允許我們構(gòu)建一個參數(shù)數(shù)組傳遞給調(diào)用函數(shù),同時允許我們選擇this的值。apply方法接收兩個參數(shù),第1個是要綁定給this的值,第2個是一個參數(shù)數(shù)組。

//構(gòu)建一個包含兩個數(shù)字的數(shù)組,并將它們相加vararray=[3,4];varsum=add.apply(null,array);//sum=7//構(gòu)造一個包含status成員的對象varstatusObject={? status:'A-OK'}//statusObject并沒有繼承自Qup.prototype,但我們可以在statusObject上調(diào)用get_status方法,//盡管statusObject并沒有一個名為get_status的方法。varstatus=Quo.prototype.get_status.apply(statusObject);//status='A-OK'

參數(shù)

arguments:函數(shù)調(diào)用時會隱式傳遞arguments數(shù)組。函數(shù)通過此參數(shù)能訪問所有它被調(diào)用時傳遞給它的參數(shù)列表,包括那些沒有被分配給函數(shù)聲明時定義的形參的多余參數(shù)。利用該特性可以編寫不需要指定參個數(shù)的函數(shù),不過不是特別有用。

arguments的語言設(shè)計錯誤:arguments并不是一個真正的數(shù)組。它只是一個"類似數(shù)組(array-like)"的對象。arguments擁有一個length屬性,但它沒有任何數(shù)組的方法。

返回

正常返回:函數(shù)從第一個語句開始執(zhí)行,并在遇到關(guān)閉函數(shù)體的}時結(jié)束。然后把控制權(quán)交還給調(diào)用該函數(shù)的程序。

return:return語句可用來使函數(shù)提前返回。當(dāng)return被執(zhí)行時,函數(shù)立即返回而不再執(zhí)行余下的語句。

一個函數(shù)總會返回一個值。如果沒有指定返回值,則返回undefined。如果函數(shù)調(diào)用時在前面加上new前綴,且返回值不是一個對象,則返回this(該新對象)。

異常

throw語句:throw中斷函數(shù)的執(zhí)行。它應(yīng)該拋出一個exception對象(自定義對象),該對象包含一個用來識別異常類型的name屬性和一個描述性的message屬性,還可以添加其他屬性。

try catch:一個try語句只會有一個捕獲所有異常的catch代碼塊(跟Java異常機制不同)。

擴充類型的功能

通過給Object.prototype添加方法,可以讓該方法對所有對象可用; 通過給Function.prototype增加方法, 可以使該方法對所有函數(shù)可用。由于JS原型繼承的動態(tài)本質(zhì),新的方法立刻被賦予到所有的對象實例上,即使對象實例是在方法增加之前創(chuàng)建的。

功能擴充實例:

//為Function.prototype增加method方法,方便以后創(chuàng)建新的方法Function.prototype.method=function(name,func){if(!this.prototype[name]){//沒有該方法時才添加this.prototype[name]=func;? ? }returnthis;};//為Number.prototype增加一個integer方法,用于提取數(shù)字中的整數(shù)部分Number.method('integer',function(){returnMath[this<0?'ceil':'floor'] (this);});//為String添加移除字符串首尾空白的方法String.method('trim',function(){returnthis.replace(/^\s+|\s+$/g,'');})

遞歸

遞歸函數(shù):直接或間接地調(diào)用自身的函數(shù)。

漢諾塔問題:

varhanoi=function(disc,src,aux,dst){if(disc>0){hanoi(disc-1,src,dst,aux);document.writeln('Move disc'+disc+'from'+src+'to'+dst);hanoi(disc-1,aux,src,dst);? ? }};hanoi(3,'Src','Aux','Dst');

遞歸函數(shù)操作樹形結(jié)構(gòu):

//定義walk_the_DOM函數(shù),它從某個指定的節(jié)點開始,按HTML源碼中的順序訪問該樹的每個節(jié)點。//它會調(diào)用傳入的函數(shù),并依次傳遞每個節(jié)點給它。walk_the_DOM調(diào)用自身去處理每個子節(jié)點。varwalk_the_DOM=functionwalk(node,func){func(node);? ? node=node.firstChild;while(node){walk(node,func);? ? ? node=node.nextSibling;? ? }};//定義getElementsByAttribute函數(shù),它以一個屬性名稱字符串和一個可選的匹配值作為參數(shù),調(diào)用//walk_the_DOM,傳遞一個用來查找節(jié)點屬性名的函數(shù)作為參數(shù)。匹配的節(jié)點累加到結(jié)果數(shù)組中。vargetElementsByAttribute=function(att,value){varresults=[];walk_the_DOM(document.body,function(node){varactual=node.nodeType===1&&node.getAttribute(attr);if(typeofactual==='string'&&(actual===value||typeofvalue!=='string')){results.push(node);? ? ? ? }? ? });returnresults;};

尾遞歸優(yōu)化:一種在函數(shù)的最后執(zhí)行遞歸調(diào)用語句的特殊形式的遞歸。這意味著如果一個函數(shù)返回自身遞歸調(diào)用的結(jié)果,那么調(diào)用的過程會被替換為一個循環(huán),它可以顯著提高速度。But,JS當(dāng)前沒有提供尾遞歸優(yōu)化。深度遞歸的函數(shù)可能會因為堆棧溢出而運行失敗。

//構(gòu)造一個帶尾遞歸的函數(shù)(返回自身調(diào)用結(jié)果),JS沒對這種形式的遞歸做優(yōu)化。varfactorial=functionfactorial(i,a){? ? a=a||1;if(i<2){returna;? ? }returnfactorial(i-1, a*i);};document.writenln(factorial(4));//24

作用域

作用域:作用域控制變量和參數(shù)的可見性及生命周期。For us,作用域減少了名稱沖突,并提供了自動內(nèi)存管理。

JS作用域:不支持塊級作用域,支持函數(shù)作用域。定義在函數(shù)中的參數(shù)和變量在函數(shù)外部不可見,而在函數(shù)內(nèi)部任何位置定義的變量,在該函數(shù)內(nèi)部任何地方都可見。由于JS缺少塊級作用域,所以不建議延遲聲明變量,最好的做法是在函數(shù)體的頂部聲明函數(shù)中可能用到的所有變量。

varfoo=function(){vara=3, b=5;varbar=function(){varb=7, c=11;//此時,a為3, b為7, c為11a+=b+c;//此時,a為21, b為7, c為11};//此時,a為3, b為5, 而c還沒定義bar();//此時,a為21, b為5};

閉包

作用域的好處是內(nèi)部函數(shù)可以訪問定義它們的外部函數(shù)的參數(shù)和變量。當(dāng)內(nèi)部函數(shù)擁有比它的外部函數(shù)更長的生命周期時,內(nèi)部函數(shù)引用的外部函數(shù)變量不會被釋放(Java中一般會引起內(nèi)存泄漏,而JS閉包恰好利用該特性)。

示例:

//創(chuàng)建一個myObj對象,把匿名函數(shù)調(diào)用結(jié)果賦值給它。varmyObj=(function(){varvalue=0;//由于函數(shù)作用域,外部對value的操作只能基于return的對象的兩個方法。//該匿名函數(shù)返回一個包含兩個方法的對象,并且這些方法繼續(xù)享有訪問value變量的特權(quán)。return{increment:function(inc){? ? ? value+=typeofinc==='number'?inc:1;? ? },getValue:function(){returnvalue;? ? }? };}());

重構(gòu)Quo構(gòu)造器:

//將status作為私有屬性,提供對應(yīng)的getter方法(原版本直接可以訪問status,提供getter沒意義,還需要顯示new)。varquo=function(status){return{get_status:function(){returnstatus;? ? }? };};varmyQuo=quo("amazed");//由于不需要加上new,所以名字沒有首字母大寫document.writeln(myQuo.get_status());

注:當(dāng)調(diào)用quo時,它返回一個包含get_status方法的新對象。該對象的一個引用保存在myQuo中。即使quo已經(jīng)返回,但get_status方法仍然享有訪問quo對象的status屬性的特權(quán)。get_status方法并不是訪問該參數(shù)的一個副本,而是參數(shù)本身。因為該函數(shù)可以訪問它被創(chuàng)建時所處的上下文環(huán)境,這被稱為閉包。

加深理解:

//糟糕的例子//構(gòu)造一個用于顯示節(jié)點序號的函數(shù),但由于用錯誤的方式給數(shù)組中的節(jié)點設(shè)置事件處理函數(shù),而造成每次點擊總是顯示節(jié)點的數(shù)目varadd_the_handlers=function(nodes){vari;for(i=0; i

注:避免在循環(huán)中創(chuàng)建函數(shù),它可能只會帶來無謂的計算,還會引起混淆。建議先在循環(huán)之外創(chuàng)建一個輔助函數(shù),讓輔助函數(shù)返回一個綁定當(dāng)前i值的函數(shù),這樣就不會導(dǎo)致混淆。

回調(diào)

回調(diào):函數(shù)使得對不連續(xù)事件的處理變得更容易,因為我們可以注冊回調(diào)函數(shù),以異步的方式處理請求。

C/S請求響應(yīng)模式對比:

同步方式:網(wǎng)絡(luò)上的同步請求會導(dǎo)致客戶端進入假死狀態(tài),特別是網(wǎng)絡(luò)傳輸或服務(wù)器很慢時。

request=prepare_the_request();response=send_request_synchronously(request);display(response);

異步方式:發(fā)起異步請求,提供一個當(dāng)服務(wù)器響應(yīng)到達(dá)時隨即觸發(fā)的回調(diào)函數(shù)。異步函數(shù)立即返回,這樣客戶端就不會阻塞。

request=prepare_the_request();send_request_asynchronously(request,function(response){display(response);});

模塊

模塊:為了屏蔽JS全局變量的使用,我們可以使用函數(shù)和閉包來構(gòu)造模塊。模塊是一個提供接口卻隱藏狀態(tài)與實現(xiàn)的函數(shù)或?qū)ο蟆?/p>

模塊模式:模塊模式利用了函數(shù)作用域和閉包來創(chuàng)建對象與私有成員的關(guān)聯(lián)。在下面示例中,只有deentityify方法有權(quán)訪問字符實體表這個數(shù)據(jù)對象。

模塊模式的一般形式:一個定義了私有變量和函數(shù)的函數(shù); 利用閉包創(chuàng)建可以訪問私有變量和函數(shù)的特權(quán)函數(shù); 最后返回這個特權(quán)函數(shù),或把它們保存到一個可訪問的地方。

模塊模式好處:使用模塊模式可以摒棄全局變量的使用。模塊模式促進信息隱藏和其他優(yōu)秀的設(shè)計實踐,比如單例模式,利于應(yīng)用程序的封裝。模塊模式也可以用來產(chǎn)生安全的對象,比如示例2中用來產(chǎn)生序列號的對象。

模塊示例:為String增加deentityify方法

//尋找字符串中的HTML字符實體并把它們替換為對應(yīng)的字符。String.method('deentityify',function(){//字符實體表。它映射字符實體的名字到對應(yīng)的字符。把該信息放到全局變量不合適,//定義在函數(shù)的內(nèi)部會帶來運行時的損耗(每次執(zhí)行此函數(shù),該字面量都會被求值一次)。varentity={? ? quot:'"',? ? lt:'<',? ? gt:'>'};//返回deentityify方法returnfunction(){//這才是deentityify方法。它調(diào)用字符串的replace方法,查找'&'開頭和';'結(jié)束的子字符串。//如果這些字符可以在實體表中找到,則將其替換為映射表中的值。returnthis.replace(/&([^&;]+);/g,function(a,b){varr=entity[b];returntypeofr==='string'?r:a ;? ? };};}());document.writeln('<">'.deentityify());//<">

模塊示例2:產(chǎn)生序列號

varserial_marker=function(){varprefix='';varseq=0;return{set_prefix:function(p){? ? ? ? ? prefix=String(p);? ? ? },set_seq:function(s){? ? ? ? ? seq=s;? ? ? },gensym:function(){varresult=prefix+seq;? ? ? ? ? seq+=1;returnresult;? ? ? }? };};//雖然seqer可變,且可以替換它的方法,但替換后的方法依然不能訪問私有成員。varseqer=serial_marker();seqer.set_prefix('Q');seqer.set_seq(1000);varunique=seqer.gensym();//unique='Q1000'

級聯(lián)

級聯(lián):在單獨一條語句中依次調(diào)用同一個對象的多個方法,關(guān)鍵是返回this而不是undefined。JQuery等JS框架常使用級聯(lián)簡化編程。級聯(lián)技術(shù)可以產(chǎn)生極富表現(xiàn)力的接口,并保持單個接口的簡單性。

柯里化

柯里化:也稱局部套用,是把多參數(shù)函數(shù)轉(zhuǎn)換為一系列單參數(shù)函數(shù)并進行調(diào)用的技術(shù)??吕锘试S我們把函數(shù)與傳遞給它的參數(shù)相結(jié)合,產(chǎn)生出一個新的函數(shù)。

柯里化示例:curry

//curry方法創(chuàng)建一個保存原始函數(shù)和要被套用的參數(shù)的閉包來工作。它返回另一個函數(shù),該函數(shù)被調(diào)用時,//會返回調(diào)用原始函數(shù)的結(jié)果,并傳遞調(diào)用curry時的參數(shù)加上當(dāng)時調(diào)用的參數(shù)。Function.method('curry',function(){varargs=arguments, that=this;returnfunction(){//Wrong! 本意使用Array的concat方法連接兩個數(shù)組,但arguments不是真正的數(shù)組,它沒有concat方法。returnthat.apply(null,args.concat(arguments));? };? });//修正版:對兩個arguments應(yīng)用數(shù)組的slice方法,產(chǎn)生擁有concat方法的常規(guī)數(shù)組。Function.method('curry',function(){varslice=Array.prototype.slice,? ? ? args=slice.apply(arguments);//arguments:傳給原方法的參數(shù)that=this;returnfunction(){returnthat.apply(null,args.concat(slice.apply(arguments)));//arguments:傳遞給curry的參數(shù)};});

記憶

記憶:函數(shù)可以將先前操作的結(jié)果記錄在某個對象里,從而避免無謂的重復(fù)運算,這種優(yōu)化被稱為記憶。JS的對象和數(shù)組能很方便的實現(xiàn)這種優(yōu)化。

帶記憶功能的函數(shù):memoizer

//memoizer取得一個初始的memo數(shù)組和formula函數(shù),返回一個管理memo存儲和調(diào)用formula的recur函數(shù)。//把這個recur函數(shù)和它的參數(shù)傳遞給formula函數(shù)。varmemoizer=function(memo,formula){varrecur=function(n){varresult=memo[n];if(typeofresult!=='number'){? ? ? ? ? result=formula(recur, n);? ? ? ? ? memo[n]=result;? ? ? }returnresult;? };returnrecur;};//應(yīng)用memoizer產(chǎn)生可記憶的階乘函數(shù)varfactorial=memoizer([1,1],function(recur,n){returnn*recur(n-1);});

第5章 繼承

繼承的好處:代碼重用; 引入類型系統(tǒng)的規(guī)范,程序員無需做顯式類型轉(zhuǎn)換。

JS的繼承:JS是一門弱類型語言,從不需要類型轉(zhuǎn)換。對于JS對象來說,重要的是它能做什么,而不是它從哪里來,它是什么(鴨式辯型)。JS基于原型的特性,意味著對象直接從其他對象繼承,而不像基于類的語言。下面是JS常用的繼承模式。

偽類

原型與偽類:JS的原型存在諸多矛盾,它的某些復(fù)雜的語法看起來就像那些基于類的語言,這些語法問題掩蓋了它的原型機制。它不能直接從其他對象繼承,反而插入了一個多余的間接層:通過構(gòu)造器函數(shù)產(chǎn)生對象。

當(dāng)一個函數(shù)對象被創(chuàng)建時,F(xiàn)unction構(gòu)造器產(chǎn)生的函數(shù)對象會運行類似如下的代碼:

this.prototype={constructor:this};

新函數(shù)對象被賦予一個prototype屬性,它的值是一個包含constructor屬性且屬性值為該新函數(shù)的對象。這個prototype對象是存放繼承特征的地方。由于JS語言沒有提供確定構(gòu)造器函數(shù)的方法(不像Java),所以每個函數(shù)都會得到一個prototype對象。constructor屬性沒什么用,重要的是prototype對象。

當(dāng)采用構(gòu)造器調(diào)用模式,即用new前綴去調(diào)用一個函數(shù)時,函數(shù)執(zhí)行的方式會被修改。如果new運算符是一個方法而不是一個運算符,它可能會像這樣執(zhí)行:

Function.method('new',function(){//創(chuàng)建一個繼承構(gòu)造器函數(shù)原型對象的新對象varthat=Object.create(this.prototype);//調(diào)用構(gòu)造器函數(shù),綁定-this-到新對象上varother=this.apply(that,arguments);//如果它的返回值不是一個對象,就返回該新對象。return(typeofother==='object'&&other)||that;});

偽類之偽:偽類本意是想向面向?qū)ο罂繑n,但它看起來格格不入。沒有私有環(huán)境,所有的屬性都是公開的,無法訪問super(父類)的方法。更糟糕的是,使用構(gòu)造器如果忘記加new前綴,那么this將不會綁定到新對象上,而是綁定到全局對象上。這樣不但沒有擴充新對象,反而破壞了全局變量環(huán)境。出現(xiàn)這種情況事,既沒有編譯時錯誤,也沒有運行時警告。

放棄偽類:與其時刻擔(dān)心忘記加new前綴,不如根本就不使用new(即使有構(gòu)造器函數(shù)命名首字母大寫的約定)。借鑒類的表示法可能誤導(dǎo)程序員去編寫過于深入與復(fù)雜的層次結(jié)構(gòu)。許多復(fù)雜類層次結(jié)構(gòu)產(chǎn)生原因是因為靜態(tài)類型檢查的約束,而JS完全擺脫了那些約束。在基于類的語言中,類繼承是代碼重用的唯一方式(當(dāng)然還有組合),而JS有更多且更好的選擇。所以放棄使用偽類,堅持JS的本色!

對象說明符

對象說明符:使用對象說明符來描述要構(gòu)建的對象規(guī)格說明,而不是傳一大串參數(shù),這樣可以避免參數(shù)順序的問題,又能和JSON配合。

原型

原型,從構(gòu)造有用對象開始:在一個純粹的原型模式中,我們會摒棄類,轉(zhuǎn)而專注于對象?;谠偷睦^承比基于類的繼承在概念上更為簡單:一個新對象可以繼承一個舊對象的屬性。通過構(gòu)造一個有用的對象,接著構(gòu)造更多類似的對象,這就可以完全避免把一個應(yīng)用拆解成一系列嵌套抽象類的分類過程。

差異化繼承:通過定制一個新的對象,我們指明它與所基于的基本對象的區(qū)別。

函數(shù)化

函數(shù)化:前面幾種繼承模式的一個弱點是沒法保護隱私。對象的所有屬性都是可見的。我們無法得到私有變量和私有函數(shù)。不要試圖通過偽裝私有(pretend privacy)來實現(xiàn)私有屬性的保護(給私有屬性起個怪模怪樣的名字,并希望其他使用代碼的用戶假裝看不到這些奇怪的成員,掩耳盜鈴?。瑧?yīng)該使用模塊模式來完成該效果!

函數(shù)化,從構(gòu)造一個生成對象的函數(shù)開始:

創(chuàng)建一個新對象。構(gòu)造方式很多,比如構(gòu)造一個對象字面量,或者new+構(gòu)造器函數(shù),或者調(diào)用任意一個會返回對象的函數(shù)。

有選擇地定義私有實例變量和方法。這些就是函數(shù)中通過var語句定義的普通變量。

給這個新對象擴充方法。這些方法擁有特權(quán)去訪問變量,以及在第2步中通過var語句定義的變量。

返回這個新對象。

偽代碼:

//spec對象包含構(gòu)造器需要構(gòu)造新實例的所有信息。varconstructor=function(spec,my){//聲明該對象私有的實例變量和方法? varthat, 其他私有變量實例;//my為繼承鏈中的構(gòu)造器提供秘密共享的容器,可選my=my||{};//把共享的變量和函數(shù)添加到my中;my.member=value;.....//構(gòu)造新對象并賦值給thatthat=一個新對象;//擴充that,添加組成該對象接口的特權(quán)方法varmethodical=function(){...};that.methodical=methodical;//返回thatreturnthat;};

示例:

varmammal=function(spec){varthat={};that.get_name=function(){returnspec.name;? ? };that.says=function(){returnspec.saying||'';? ? };returnthat;};varmyMammal=mammal({name:'Herb'});

父類方法:函數(shù)化模式給我們提供處理父類方法的方法。

//構(gòu)造一個superior方法,它取得一個方法名并返回調(diào)用那個方法的函數(shù)。該函數(shù)會調(diào)用原來的方法。Object.method('superior',function(name){varthat=this, method=that[name];returnfunction(){returnmethod.apply(that,arguments);? ? };});

函數(shù)化模式優(yōu)點:函數(shù)化模式有很大的靈活性。相比偽類模式,它不僅帶來的工作更少,還讓我們得到更好的封裝和信息隱藏,以及訪問父類方法的能力。如果對象的所有狀態(tài)都是私有的,那就可以保證對象的完整性不被破壞。如果使用函數(shù)化樣式創(chuàng)建一個對象,并且該對象的所有方法都不使用this或that,那該對象就是持久性的。一個持久性對象就是一個簡單功能函數(shù)的集合。一個持久性的對象不會被入侵。訪問一個持久性的對象時,除非有方法授權(quán),否則攻擊者不能訪問對象的內(nèi)部狀態(tài)。

部件

一套部件組裝出對象。

示例:

//構(gòu)造一個給任何對象添加簡單事件處理特性的函數(shù)。//它會給對象添加一個on方法,一個fire方法和一個私有的事件注冊對象。vareventuality=function(that){varregistry={};//在一個對象上觸發(fā)事件。該事件可以是一個包含事件名稱的字符串,或一個擁有包含事件名稱的//type屬性的對象。通過'on'方法注冊的事件處理程序中匹配事件名稱的函數(shù)將被調(diào)用。that.fire=function(event){vararray, func, handler, i, type=typeofevent==='string'?event:event.type;//如果這個事件存在一組事件處理程序,則遍歷它們并按順序依次執(zhí)行。if(registry.hasOwnProperty(type)){? ? ? ? ? array=registry[type];for(i=0; i

第6章 數(shù)組

數(shù)組:一段線性分配的內(nèi)存,通過整數(shù)計算偏移并訪問其中的元素。數(shù)組是一種性能出色的數(shù)據(jù)結(jié)構(gòu)。不幸的,JS沒有提供這樣的結(jié)構(gòu)。

JS數(shù)組:JS提供一種擁有一些類數(shù)組(array-like)特性的對象。它把數(shù)組的下標(biāo)轉(zhuǎn)變?yōu)樽址?,用其作為屬性。它明顯比一個真正的數(shù)組慢,但使用起來更方便。屬性的檢索和更新的方式與對象一模一樣,只不過多一個可以用整數(shù)作為屬性名的特性。

數(shù)組字面量

數(shù)組字面量:數(shù)組字面量提供一種非常方便地創(chuàng)建新數(shù)組的表示法。JS數(shù)組允許混合類型。

varempty=[];//數(shù)組的第一個值將獲得屬性名'0',第二個值將獲得屬性名'1',依次類推:varnumbers=['zero','one','two','three','four','five','six','seven','eight','nine','ten'];empty[1];//undefinednumbers[1];//'one'empty.length;//0numbers.length;//10

長度

JS數(shù)組長度:JS數(shù)組的length沒有上界,如果你用大于或等于當(dāng)前l(fā)ength的數(shù)字作為下標(biāo)來存儲一個元素,那么lengt值會被增大以容納新元素,不會發(fā)生數(shù)組越界錯誤。

注意:length屬性的值是這個數(shù)組最大整數(shù)屬性名加上1,不一定等于數(shù)組里屬性的個數(shù)!

varmyArray=[];myArray.length;//0myArray[10000]=true;myArray.length;//10001

[]后置下標(biāo)運算符:[]后置下表運算符把它所含的表達(dá)式轉(zhuǎn)換成一個字符串,如果該表達(dá)式有toString方法,就使用方法的值。這個字符串將被用作屬性名。如果這個字符串看起來像一個大于等于這個數(shù)組當(dāng)前l(fā)ength且小于2^32-1的正整數(shù),那么這個數(shù)組的length將會被重新設(shè)置為新的下標(biāo)加1??梢灾苯釉O(shè)置length的值,設(shè)置更大的length不會給數(shù)組分配更多的空間,但把length設(shè)小將導(dǎo)致所有下標(biāo)大于等于length的屬性被刪除。

刪除

刪除:由于JS數(shù)組是對象,所以delete運算符可以用來從數(shù)組中移除元素:

//刪除之后數(shù)組會留下一個空洞,除非移動每個數(shù)組的元素deletenumbers[2];//numbers 是['zero', 'one', undefined, 'three'...]

splice方法:如果即想刪除元素又不想留下空洞,可以使用splice方法。由于需要移除和重新插入,splice對于大型數(shù)組來說可能效率不高。

//第1個參數(shù)是數(shù)組中的一個序號,第2個參數(shù)是要刪除的元素個數(shù)。//任何額外的參數(shù)都會在序號那個點的位置被插入到數(shù)組中。numbers.splice(2,1);//numbers 是['zero', 'one', 'three'...]

枚舉

for in:由于JS數(shù)組其實就是對象,所以可以用for in語句來遍歷一個數(shù)組的所有屬性。遺憾的是,for in無法保證屬性的順序,而大多數(shù)要遍歷數(shù)組的場合都期望按照阿拉伯?dāng)?shù)字順序產(chǎn)生元素。此外,可能從原型中得到意外屬性的問題依舊存在。

for循環(huán):常規(guī)的for語句可以避免for in語句的問題。

vari;for(i=0; i

容易混淆的地方

對象與數(shù)組的選擇:當(dāng)屬性名是小而連續(xù)的整數(shù)時,你應(yīng)該使用數(shù)組。否則,使用對象。JS本身對數(shù)組和對象的區(qū)別是混亂的。typeof運算符報告數(shù)組的類型是'object',這沒有任何意義。

如何區(qū)別對象和數(shù)組:is_array(JS沒有提供相應(yīng)機制)

//此方法不能識別從不同的窗口(window)或幀(frame)里構(gòu)造的數(shù)組。varis_array=function(value){returnvalue&&typeofvalue==='object'&&value.constructor===Array;};//bettervaris_array=function(value){returnObject.prototype.toString.apply(value)==='[object Array]';};

方法

Array.prototype:可以通過Array.prototype給數(shù)組擴充方法。

指定初始值

JS數(shù)組通常不會預(yù)置值。如果你用[]得到一個新數(shù)組,它將是空的。如果你訪問一個不存在的元素,得到的值則是undefined。JS應(yīng)該提供為數(shù)組指定初始值的方法,但我們可以彌補這個疏忽:

Array.dim=function(dimension,initial){vara=[], i;for(i=0; i

多維數(shù)組:JS沒有多為數(shù)組,但就像大多數(shù)類C語言一樣,它支持元素為數(shù)組的數(shù)組:

varmatrix=[? [0,1,2],? [3,4,5],? [6,7,8]];matrix[2][1];//7//注意:Array.dim(n, [])在這里不能工作,如果使用它,每個元素都指向同一個數(shù)組的引用,后果不堪設(shè)想。

初始化多維數(shù)組:一個空的矩陣每個單元都會擁有一個初始值undefined。

Array.matrix=function(m,n,initial){vara, i, j, mat=[];//由于JS變量作用域問題,將變量在函數(shù)體最前面聲明。for(i=0; i

第7章 正則表達(dá)式

正則表達(dá)式:一門簡單語言的語法規(guī)范,它應(yīng)用在一些方法中,對字符串中的信息實現(xiàn)查找,替換和提取操作。JS中,正則表達(dá)式相較于等效的字符串處理有著顯著的性能優(yōu)勢。JS正則表達(dá)式不支持注釋和空白。

一個例子

匹配URL:parse_url

varparse_url=/^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;varurl="http://www.ora.com:80/goodparts?q#fragement";varresult=parse_url.exec(url);varnames=['url','scheme','slash','host','port','path','query','hash'];varblanks='';vari;for(i=0; i

結(jié)構(gòu)

創(chuàng)建RegExp對象的兩個方法:正則表達(dá)式字面量(優(yōu)先考慮)和使用RegExp構(gòu)造器。

正則表達(dá)式字面量:正則表達(dá)式字面量被包圍在一對斜杠中,這多少有點令人迷惑,因為斜杠也被用作除法運算符和注釋符。

正則表達(dá)式標(biāo)識:

標(biāo)識含義

g全局的(匹配多次;不同的方法對g標(biāo)識的處理各不相同)

i大小寫不敏感(忽略字符大小寫)

m多行(^和$能匹配行結(jié)束符)

RegExp構(gòu)造器:使用RegExp構(gòu)造器創(chuàng)建正則表達(dá)式時需要注意雙寫反斜杠以及對引號進行轉(zhuǎn)義。RegExp構(gòu)造器適用于必須在運行時動態(tài)生成正則的情形。

//創(chuàng)建一個匹配JS字符串的正則varmy_regexp=newRegExp("\"(?:\\\\.|[^\\\\\\\"])*\"",'g');

RegExp對象的屬性:

屬性用法

global如果標(biāo)識g被使用,值為true

ignoreCase如果標(biāo)識i被使用,值為true

lastIndex下一次exec匹配開始的索引,初始值為0

multiline如果標(biāo)識m被使用,值為true

source正則表達(dá)式源碼文本

正則對象共享問題:

functionmake_a_matcher(){return/a/gi;}varx=marke_a_matcher();vary=make_a_matcher();//當(dāng)心!x和y是相同的對象!x.lastIndex=10;document.writeln(y.lastIndex);// 10

元素

正則表達(dá)式分支:一個正則表達(dá)式分支包含一個或多個正則表達(dá)式序列。這些序列被|(豎線)字符分割。如果這些序列中的任何一項符合匹配條件,那這個選擇就被匹配。它嘗試按順序依次匹配這些序列項。

//匹配in,不會匹配int,因為in已被成功匹配"into".match(/in|int/);

正則表達(dá)式序列:一個正則表達(dá)式序列包含一個或多個正則表達(dá)式因子。每個因子能選擇是否跟隨一個量詞,這個量詞決定著這個因子被允許出現(xiàn)的次數(shù)。如果沒有指定這個量詞,那么該因子只會被匹配一次。

正則表達(dá)式因子:一個正則表達(dá)式因子可以是一個字符,一個由圓括號包圍的組,一個字符類,或一個轉(zhuǎn)義序列。除了控制字符和特殊字符以外,所有的字符都會被按照字面處理:\ / [ ] { } ? + * | . ^ $ (如果希望這些字符按字面去匹配,需要用\前綴對其進行轉(zhuǎn)義)。一個未被轉(zhuǎn)義的.會匹配除行結(jié)束符以外的任何字符。當(dāng)lastIndex屬性值為0時,一個未轉(zhuǎn)義的^會匹配文本的開始。當(dāng)指定了m標(biāo)識時,它也能匹配行結(jié)束符。一個未轉(zhuǎn)義的$將匹配文本的結(jié)束。當(dāng)指定了m標(biāo)識時,它也能匹配行結(jié)束符。

正則表達(dá)式轉(zhuǎn)義:

\f:換頁符 \n:換行符 \r:回車符 \t:制表符(tab) \u:unicode字符表示的十六進制常量。

\d:匹配一個數(shù)字,[0-9],\D與其相反[^0-9]。

\s:等同于[\f\n\r\t\u000B\0020\u00A0\u2028\u2029],這是unicode空白符的一個不完全子集,\S則表示與其相反的:[^\f\n\r\t\u000B\0020\u00A0\u2028\u2029]。

\w:等同于[0-9A-Z_a-z],\W與其相反:[^0-9A-Z_a-z]。\W本意是希望表示出現(xiàn)在話語中的字符。遺憾的是,它所定義的類實際上對任何真正的語言來說都不起作用。如果你需要匹配信件一類的文本,你必須指定自己的類。

\b:字邊界標(biāo)識,它能方便的對文本的字邊界進行匹配。遺憾的是,它使用\w去尋找字邊界,所以它對多語言應(yīng)用來說是完全無用的。這并不是一個好的特性。

\1:指向分組1所捕獲到的文本的一個引用,所以它能被再次匹配。

//用于搜索文本中重復(fù)的單詞,該單詞的后面跟著一個或多個空白,然后再跟著與它相同的單詞。vardoubled_words=/([A-Za-z\u00C0-\u1FFF\u2800-\uFFFD]+)\s+\1/gi;

分組:

捕獲型:一個捕獲型分組是一個被包圍在圓括號中的正則表達(dá)式分支。任何匹配這個分組的字符都會被捕獲。每個捕獲型分組都被指定了一個數(shù)字。在正則表達(dá)式中第一個捕獲(的是分組1,第二個捕獲(的是分組2。

非捕獲型:非捕獲型分組有一個(?:前綴。非捕獲型分組僅做簡單的匹配,并不會捕獲所匹配的文本。這會帶來微弱的性能優(yōu)勢。非捕獲型分組不會干擾捕獲型分組的編號。

向前正向匹配(positive lookahead):向前正向匹配分組有一個(?=前綴。類似于非捕獲型分組,但在這個組匹配后,文本會倒回到它開始的地方,實際上并不匹配任何東西。這不是一個好的特性。

向前負(fù)向匹配(negative lookahead):向前負(fù)向匹配分組有一個(?!前綴。類似于向前正向匹配,但只有當(dāng)它匹配失敗時它才繼續(xù)向前進行匹配。這不是一個好的特性。

正則表達(dá)式字符集(RegExp Class):正則表達(dá)式字符集是一種指定一組字符的便利方式。例如,如果想匹配一個元音字母,可以寫做(?:a|e|i|o|u),但更方便方法應(yīng)該是寫成一個類[aeiou]。類提供另外兩個便利。第1個是能夠指定字符范圍,比如[a-z]。另一個方便之處是類的求反。如果[后的第一個字符是^,那么這個類會排除這些特殊字符,比如[^a-z]會匹配任何一個非小寫字母的字符。

正則表達(dá)式字符轉(zhuǎn)義(RegExp Class Escape):在字符類中需要轉(zhuǎn)義的特殊字符- / [ \ ] ^

正則表達(dá)式量詞:正則表達(dá)式因子可以用一個正則表達(dá)式量詞后綴來決定這個因子應(yīng)該被匹配的次數(shù)。包圍在一個花括號中的一個數(shù)字表示這個因子應(yīng)該被匹配的次數(shù)。?等同于{0,1},*等同于{0,},+等同于{1,}。如果只有一個量詞,表示趨向于進行貪婪性匹配,即匹配盡可能多的副本直至達(dá)到上限。如果這個量詞附加一個后綴?,則表示趨向于進行非貪婪匹配,即只匹配必要的副本。一般情況下最好堅持使用貪婪匹配。

第8章 方法

Array

array.concat(item...) : concat方法產(chǎn)生一個新數(shù)組,它包含一份array的淺復(fù)制(shallow copy)并把一個或多個參數(shù)item附加在其后。如果參數(shù)item是一個數(shù)組,那么它的每個元素都會被分別添加。功能和array.push(item...)類似。

vara=['a','b','c'];varb=['x','y','z'];varc=a.concat(b,true);// c=['a', 'b', 'c', 'x', 'y', 'z', true];

array.join(separator):join方法把一個array中的每個元素構(gòu)造成一個字符串,并用separator分隔符把它們連接在一起。默認(rèn)的separator是逗號','??梢允褂每瞻鬃址鳛閟eparator實現(xiàn)無間隔的連接。對于連接大量的字符串片段,將它們放到一個數(shù)組中并用join方法連接通常比+元素運算符連接效率高。(注:現(xiàn)在多數(shù)瀏覽器對+運算符連接字符串做了特別優(yōu)化,性能已顯著高于Array.join(),多數(shù)情況下,建議連接字符串首選+運算符)。

vara=['a','b','c'];a.push('d');varc=a.join('');//c = 'abcd';

array.pop():pop方法移除array中的最后一個元素并返回該元素。如果array是empty,它會返回undefined。

vara=['a','b','c'];varc=a.pop();//c = 'c', a = ['a', 'b'];//pop的一種實現(xiàn)方式Array.method('pop',function(){returnthis.splice(this.length-1,1)[0];});

array.push(item...):push方法把一個或多個參數(shù)item附加到一個數(shù)組的尾部。和concat方法不同的是,它會修改array,如果參數(shù)item是一個數(shù)組,它會把參數(shù)數(shù)組作為單個元素整個添加到數(shù)組中,并返回這個array的新長度值。

vara=['a','b','c'];varb=['x','y','z'];varc=a.push(b,true);// a=['a', 'b', 'c', ['x', 'y', 'z'], true] , c=5。//push可以這樣實現(xiàn)Array.method('push',function(){this.splice.apply(this,? ? ? ? [this.length,0].concat(Array.prototype.splice.apply(arguments)));returnthis.length;});

array.reverse():reverse方法反轉(zhuǎn)array里的元素的順序,并返回array本身。

vara=['a','b','c'];varb=a.reverse();//a=b=['c', 'b', 'a']

array.shift():shift方法移除數(shù)組array中的第1個元素并返回該元素。如果這個數(shù)組array是空的,則返回undefined。shift通常比pop慢得多:

vara=['a','b','c'];varc=a.shift();//a=['b', 'c'], c='a'//shift可以這樣實現(xiàn)Array.method('shift',function(){returnthis.splice(0,1)[0];});

array.slice(star, end):slice方法對array中的一段做淺復(fù)制。end參數(shù)可選,默認(rèn)是array.length。如果兩個參數(shù)中的任何一個是負(fù)數(shù),array.length會和它們相加,試圖讓它們變成非負(fù)數(shù)。如果start大于等于array.length,得到的結(jié)果是一個新的空數(shù)組。

vara=['a','b','c'];varb=a.slice(0,1);//b=['a']varc=a.slice(1);//c=['b', 'c']vard=a.slice(1,2);//d=['b']

array.sort(comparefn):sort方法對array中的內(nèi)容進行排序。它不能正確地給一組數(shù)字排序:

//JS的默認(rèn)比較函數(shù)把要被排序的元素都視為字符串。varn=[4,8,15,16,23,42];n.sort();//n=[15, 16, 23, 4, 42, 8] //自定義比較函數(shù):相等返回0, 第1個參數(shù)排列在前面則返回一個負(fù)數(shù),否則返回一個正數(shù)n.sort(function(a,b){returna-b;});// n=[4, 8, 15, 16, 23, 42];//如果想使排序適用范圍更廣,需要判斷元素類型。

array.splice(start, deleteCount, item...):splice方法從array中移除一個或多個元素,并用新的item替換它們。

//splice主要用于從一個數(shù)組中刪除元素vara=['a','b','c'];varr=a.splice(1,1,'ache','bug');//a = ['a', 'ache', 'bug', 'c'], r = ['b']

array.unshift(item...):unshift方法像push方法一樣,用于把元素添加到數(shù)組中,但它是把item插入到array的開始部分而不是尾部。它返回array的新length。

vara=['a','b','c'];varr=a.unshift('?','@');//a=['?', '@', 'a', 'b', 'c'] , r=5

Function

function.apply(thisArg, argArray) : apply方法調(diào)用function,傳遞一個會被綁定到this上的對象和一個可選的數(shù)組作為參數(shù)。apply方法被用在apply調(diào)用模式中。

//bnd:返回一個函數(shù),調(diào)用這個函數(shù)就像調(diào)用那個對象的一個方法Function.method('bind',function(that){varmethod=this,? ? ? slice=Array.prototype.slice,? ? ? args=slice.apply(arguments, [1]);returnfunction(){returnmethod.apply(that,args.concat(slice.apply(arguments, [0])));? };});varx=function(){returnthis.value;}.bind({value:666});alert(x());//666

Number

number.toExponential(fractionDigits) : 把number轉(zhuǎn)換成一個指數(shù)形式的字符串。可選參數(shù)fractionDigits控制其小數(shù)點后的數(shù)字位數(shù),值必須在0~20。

number.toFixed(fractionDigits) : 把number轉(zhuǎn)換為一個十進制數(shù)形式的字符串??蛇x參數(shù)fractionDigits控制其小數(shù)點后的數(shù)字位數(shù),值必須在0~20,默認(rèn)為0。

number.toPrecision(precision) : 把number轉(zhuǎn)換為一個十進制數(shù)形式的字符串??蛇x參數(shù)precision控制數(shù)字的精度,值必須在0~21。

number.toString(radix) : 把number轉(zhuǎn)換為一個字符串??蛇x參數(shù)radix控制基數(shù),值必須在2~36,默認(rèn)為10。通常情況下,number.toString()可以簡單地寫為String(number);

Object

object.hasOwnProperty(name) : 如果這個object包含一個名為name的屬性,那么hasOwnProperty方法返回true。原型鏈中的同名屬性不會被檢查,當(dāng)name為"hasOwnProperty"時不起作用,返回false。

RegExp

regexp.exec(string):如果成功匹配regexp和字符串string,則返回一個數(shù)組。數(shù)組中下標(biāo)為0的元素將包含正則表達(dá)式regexp匹配的子字符串。下標(biāo)為1的元素是分組1捕獲的文本,下標(biāo)為2的元素是分組2捕獲的文本,依次類推。如果匹配失敗,則返回null。

exec循環(huán)調(diào)用:如果regexp帶有一個g標(biāo)識(全局標(biāo)識),那么查找將從regexp.lastIndex(初始值為0)位置開始。如果匹配成功,那regexp.lastIndex將被設(shè)置為該匹配后第一個字符的位置。不成功的匹配將設(shè)置regexp.lastIndex為0。這允許你通過循環(huán)exec去查詢一個匹配模式在一個字符串中發(fā)生了幾次。需要注意的是,如果你提前退出了循環(huán),再次進入這個循環(huán)前必須把regexp.lastIndex重置為0。而且,^因子僅匹配regexp.lastIndex為0的情況。

regexp.test(string) : 如果regexp匹配string,則返回true; 否則,返回false。不要對這個方法使用g標(biāo)識。

String

參考JS String API! 使用類似Java String。

第9章 代碼風(fēng)格

Google JavaScript 代碼風(fēng)格指南

Google JSON 風(fēng)格指南

CoffeeScript 編碼風(fēng)格指南

第10章 優(yōu)美的特性

函數(shù)是頂級對象

基于原型繼承的動態(tài)對象

對象字面量和數(shù)組字面量

附錄A 毒瘤

全局變量

在任何函數(shù)之外通過var聲明的變量。

直接給全局對象添加一個屬性,比如web瀏覽器的全局對象window。

直接使用未經(jīng)聲明的變量,這被稱為隱式的全局變量:foo = value;

作用域

JS沒有提供塊級作用域:代碼塊中聲明的變量在包含此代碼塊的函數(shù)的任何位置都是可見的。最好在每個函數(shù)的開頭部分聲明所有變量。

自動插入分號

JS自動修復(fù)機制:通過自動插入分號來修正有缺損的程序。但是,它可能會掩蓋更為嚴(yán)重的錯誤。

//如果return語句返回一個值,這個值表達(dá)式的開始部分必須和return位于同一行。return//自動插入分號會讓它返回undefined{? status:true};//正確寫法return{? status:true};

保留字

Unicode

typeof

typeof不能區(qū)分null和對象,typeof null 返回的是'object'。

parseInt

parseInt是一個把字符串轉(zhuǎn)換為整數(shù)的函數(shù)。它遇到非數(shù)字時會停止解析,parseInt("16")和parseInt("16 tons")產(chǎn)生的結(jié)果相同。如果該字符串第1個字符是0,那么該字符串會基于八進制而不是十進制來求值。在八進制中,8和9不是數(shù)字,所以parseInt("08")和parseInt("09")結(jié)果都是0。這個錯誤會導(dǎo)致程序解析日期和時間時出現(xiàn)問題。幸運的是,parseInt可以接受一個基數(shù)作為參數(shù),parseInt("08",10)結(jié)果為8,建議加上基數(shù)參數(shù)。

+

+運算符可以用于加法運算或字符串連接,具體如何執(zhí)行取決于參數(shù)的類型。如果想使用+做加法運算,需確保兩個運算數(shù)都是整數(shù)。

浮點數(shù)

二進制的浮點數(shù)不能正確地處理十進制的小數(shù),因此0.1+0.2不等于0.3。不過,浮點數(shù)中的整數(shù)運算是精確的,小數(shù)可以通過指定精度來避免錯誤。常見的貨幣轉(zhuǎn)換,可以先將元乘以100轉(zhuǎn)換為分,然后用分進行計算,最后再除以100轉(zhuǎn)換為元。

NaN NaN是IEEE754中定義的一個特殊的數(shù)量值,用于表示不是一個數(shù)字,盡管typeof NaN === 'number'返回true。

判斷數(shù)字和NaN:typeof不能辨別NaN和數(shù)字,而且NaN也不等于它自己。如果需要區(qū)分?jǐn)?shù)字和NaN,可以使用JS的isNaN函數(shù)。

判斷數(shù)字:判斷一個值是否可用作數(shù)字的最佳方法是使用isFinite函數(shù),它會篩選掉NaN和Infinity。遺憾的是,isFinite會試圖把運算數(shù)轉(zhuǎn)換為一個數(shù)字,所以如果值事實上不是一個數(shù)字,它就不是一個好的測試。

//自定義isNumber函數(shù)varisNumber=functionisNumber(value){returntypeofvalue==='number'&&isFinite(value);};

偽數(shù)組

假值

hasOwnProperty

hasOwnProperty是方法而不是運算符,這就有hasOwnProperty被其他函數(shù)甚至一個非函數(shù)的值替換的危險!

對象

附錄B 糟粕

==

with語句

eval

continue語句

swith穿越

缺少塊的語句

++ --

位運算符

function語句對比function表達(dá)式

類型的包裝對象

new

void

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,613評論 0 13
  • 首發(fā)于:segmentfault《JavaScript語言精粹 修訂版》 讀書筆記 之前看到這篇文章,前端網(wǎng)老姚淺...
    若川i閱讀 937評論 0 3
  • 第二章 語法 數(shù)字 JavaScript 只有一個數(shù)字類型,它在內(nèi)部被表示為64位的浮點數(shù),和Java的doubl...
    SongLiang閱讀 408評論 0 0
  • 語法 1.數(shù)字 a. javascript只有一種數(shù)字類型,表示64位的浮點數(shù),避免了短整型的溢出問題。1和1.0...
    theCoder閱讀 285評論 0 1
  • 金婆領(lǐng)路,胡帥殿后,一行三人靠著手電摸索著前行。 簡小妖暗暗有些奇怪,這金婆一個眼睛瞎了,倒是絲毫不妨礙她夜行,或...
    丫是老段閱讀 243評論 2 1

友情鏈接更多精彩內(nèi)容