全棧必備JavaScript基礎(chǔ)
1995年,誕生了JavaScript語(yǔ)言,那一年,我剛剛從大學(xué)畢業(yè)。在今年RedMonk 推出的2017 年第一季度編程語(yǔ)言排行榜中,JavaScript 排第一,Java 第二,Python 反超 PHP 排第三,PHP 第四,C# 和 C++ 并列第五。RedMonk 排名的主要依舊是各種編程語(yǔ)言在 Stack Overflow 和 GitHub 上的表現(xiàn),比如編程語(yǔ)言在 Stack Overflow 上的討論數(shù)量,在 GitHub 上的代碼量等。盡管有一定的片面性,還是說(shuō)明了JavaScript 應(yīng)用的廣泛性。從全棧的角度看,Javascript 是必備的一種編程語(yǔ)言。
ECMAScript 和 JavaScript 的關(guān)系
JavaScript 誕生于Netscape,但是在1996年,微軟發(fā)布了與JavaScript 兼容的JScript,面對(duì)兼容和發(fā)展的需要,網(wǎng)景公司的先賢們努力加入了 ECMA International 標(biāo)準(zhǔn)化組織,致力于JavaScript 的標(biāo)準(zhǔn)化,命名為ECMAScript。后來(lái),由于歷史的原因, JavaScript標(biāo)準(zhǔn)的開(kāi)發(fā)主體變成了Mozila基金會(huì)。
簡(jiǎn)單地,ECMAScript 是JavaScript語(yǔ)言的標(biāo)準(zhǔn)規(guī)范,就像C++的標(biāo)準(zhǔn)相對(duì)于C++語(yǔ)言那樣。
JavaScript 是怎樣的語(yǔ)言
在mozilla 開(kāi)發(fā)者網(wǎng)站上是這樣描述JavaScript的:
JavaScript (JS) is a lightweight interpreted or JIT-compiled programming language with first-class functions.
意思是說(shuō)JavaScript 是一個(gè)輕量級(jí)解釋或即時(shí)編譯的函數(shù)式語(yǔ)言,里面有很多的概念,輕量、解釋、編譯、即時(shí)編譯、函數(shù)式。在老碼農(nóng)看來(lái),簡(jiǎn)單起見(jiàn),理解為擴(kuò)展語(yǔ)言較為方便。
一般的編程語(yǔ)言都有著自己相對(duì)獨(dú)立的執(zhí)行環(huán)境,但是JavaScript的執(zhí)行環(huán)境依賴在宿主環(huán)境中,宿主環(huán)境尤其是客戶端的宿主環(huán)境提供了更多統(tǒng)一的環(huán)境變量,比如瀏覽器中的window,document等。實(shí)際上,JavaScript 和DOM 是可分的,對(duì)于不同的運(yùn)行環(huán)境,有著不同的內(nèi)置宿主對(duì)象。JavaScript作為擴(kuò)展語(yǔ)言在內(nèi)置的宿主環(huán)境中運(yùn)行,全局對(duì)象在程序啟動(dòng)前就已經(jīng)存在了。
JavaScript的時(shí)空基礎(chǔ)
從空間觀的角度看,JavaScript包括數(shù)據(jù)結(jié)構(gòu),操作符,語(yǔ)句與表達(dá)式,函數(shù);從時(shí)間的角度看,包括作用域,處理方式,模塊與庫(kù)。
數(shù)據(jù)結(jié)構(gòu)
JavaScript 中包含的六種基本類型:
- Boolean
- Null
- Undefined
- Number
- String
- Symbol (ECMAScript 6)
其它全是對(duì)象。值是有類型的,變量是沒(méi)有類型的,類型定義了值的行為特征,變量在沒(méi)有持有值的時(shí)候是undefined。 JavaScript對(duì)值和引用的賦值/傳遞在語(yǔ)法上沒(méi)有區(qū)別,完全根據(jù)值的類型來(lái)判定。
對(duì)于對(duì)象的屬性和方法而言,全局變量和全局函數(shù)是全局對(duì)象的屬性,全局對(duì)象相當(dāng)于宿主對(duì)象的根對(duì)象。需要注意是屬性的屬性中那些不可變對(duì)象的實(shí)現(xiàn)方式:
- 對(duì)象常量: 結(jié)合writable和configurable:false 可以創(chuàng)建一個(gè)真正的常量屬性
- 禁止擴(kuò)張:Object.preventExtensions(..)來(lái)禁止一個(gè)對(duì)象添加新屬性并保留已有屬性
- 密封: 在 Object.seal(..) 后不能增,刪,改 該屬性
- 凍結(jié): Object.freeze(..) 會(huì)禁止對(duì)于對(duì)象本身及任意直接屬性的修改
數(shù)據(jù)類型的判定可以通過(guò) contructor,instanceof, isPrototypeOf等方法實(shí)現(xiàn),對(duì)于鴨子類型的判定還可以使用 in 的相關(guān)操作。符號(hào)并非對(duì)象,而是一種簡(jiǎn)單標(biāo)量基本類型。
JavaScript 中的強(qiáng)制類型轉(zhuǎn)換總是返回基本類型值,將對(duì)象強(qiáng)制轉(zhuǎn)換為String 是通過(guò)ToPrimitive抽象操作完成的,而toJSON()是返回一個(gè)能夠被字符串化的安全的JSON值。
操作符
操作符是空間元素連接的紐帶之一,JavaScript操作符包括算術(shù),連接,相等,比較,邏輯,位,類型判斷,條件,new,delete, void,",", ".", "[]"等。
在JavaScript中以操作符進(jìn)行操作往往都附帶著類型轉(zhuǎn)換。
一元運(yùn)算符+ 是顯式強(qiáng)制類型轉(zhuǎn)換,而~是先轉(zhuǎn)換為32位數(shù)字,然后按位反轉(zhuǎn)。|| 和&& 更應(yīng)該算是選擇器運(yùn)算符,其返回值不一定是布爾值,而是兩個(gè)操作數(shù)其中的一個(gè)值。一般先對(duì)第一個(gè)操作數(shù)進(jìn)行toBoolean強(qiáng)制類型轉(zhuǎn)換,然后再執(zhí)行條件判斷。例如:a||b 理解成a?a:b 更通俗。對(duì)&& 而言,如果第一個(gè)是真值,則把第二個(gè)作為返回值,a&&b 理解成a?b:a 。
== 和=== 都會(huì)對(duì)操作數(shù)進(jìn)行類型檢查,并執(zhí)行隱性類型轉(zhuǎn)換,需要注意的是:
- 如果兩邊的值中有true或false,千萬(wàn)不要使用==
- 如果兩邊有[],””或者0,盡量不要使用==
這里是Github上關(guān)于各種相等性的矩陣:
語(yǔ)句與表達(dá)式
操作符與變量/常量等連接形成了語(yǔ)句和表達(dá)式,例如表達(dá)式a+1中的null 被強(qiáng)制轉(zhuǎn)換為0。 語(yǔ)句包括聲明與塊,控制語(yǔ)句有判斷,循環(huán),break,continue,return,異常等。每個(gè)語(yǔ)句都有一個(gè)結(jié)果值,哪怕是undefined。
正則表達(dá)式是非常重要的一類表達(dá)式,主要使用RegExp類,執(zhí)行方法test效率高,exec 會(huì)得到一個(gè)結(jié)果對(duì)象的數(shù)組。
逗號(hào)運(yùn)算符可以把多個(gè)獨(dú)立的表達(dá)式串聯(lián)成一個(gè)語(yǔ)句,{ }在不同情況下的意思不盡相同,作為語(yǔ)句塊,{ ..} 和for/while循環(huán)以及if條件語(yǔ)句中代碼塊的作用基本相同。{a,b} 實(shí)際上是{a:a,b:b}的簡(jiǎn)化版本。
try..catch..finally 中,如果finally中拋出異常,函數(shù)會(huì)在此處終止。需要注意的是,如果此前try中已經(jīng)有return設(shè)置了返回值,則該值會(huì)被丟棄。finally中的return也會(huì)覆蓋try和catch中的return的返回值。
函數(shù)與作用域
函數(shù)就是具有運(yùn)算邏輯的對(duì)象,匿名函數(shù)不利于調(diào)試,回調(diào)函數(shù)是一種控制反轉(zhuǎn)。所有的函數(shù)(對(duì)象)都具有名為prototype的屬性,prototype屬性引用的對(duì)象是prototype對(duì)象;所有的對(duì)象都含有一個(gè)隱式鏈接,用以指向在對(duì)象生成過(guò)程中所使用的構(gòu)造函數(shù)的prototype對(duì)象。
匿名函數(shù)沒(méi)有name 標(biāo)識(shí)符,具有如下缺陷:
- 代碼更難理解
- 調(diào)試棧更難追蹤
- 自我引用(遞歸,事件(解除)綁定,等)更難
如果function是聲明的第一個(gè)詞,那就是函數(shù)聲明,否則就是函數(shù)表達(dá)式。立即執(zhí)行函數(shù)表達(dá)式形如:(function …)( )
時(shí)空密不可分,作用域是時(shí)空連接的紐帶之一。作用域包括全局,函數(shù),塊級(jí)作用域。作用域是根據(jù)名稱查找變量的一套規(guī)則,遍歷嵌套作用域鏈的規(guī)則簡(jiǎn)單:引擎從當(dāng)前執(zhí)行作用域逐級(jí)向上查找。閉包可以理解為具有狀態(tài)的函數(shù)。
函數(shù)作用域指屬于這個(gè)函數(shù)的全部變量都可以在整個(gè)函數(shù)的范圍內(nèi)使用或復(fù)用。塊作用域形如 with, try/catch, ES6 引入了let,const等。
動(dòng)態(tài)作用域并不關(guān)心函數(shù)和作用域是如何聲明以及在何處聲明的,只關(guān)心它們從何處調(diào)用的。詞法作用域是定義在詞法分析階段的作用域,詞法作用域查找會(huì)在第一個(gè)匹配的標(biāo)識(shí)符時(shí)停止。作用域鏈?zhǔn)腔谡{(diào)用棧的,而不是代碼中的作用域嵌套。ReferenceError 是與作用域判別失敗相關(guān),而TypeError則是作用域判別成功,但是對(duì)結(jié)果的操作非法或不合理。
this 提供了一種優(yōu)雅方式來(lái)隱式“傳遞”一個(gè)對(duì)象引用。 this 即沒(méi)有指向函數(shù)的自身,也沒(méi)有指向函數(shù)的作用域,是在函數(shù)被調(diào)用時(shí)發(fā)生的綁定,它指向什么完全取決于函數(shù)在哪里被調(diào)用。如果分析this綁定的話,可以使用調(diào)試工具得到調(diào)用棧,然后找到棧中的第二個(gè)元素,就是真正的調(diào)用位置。
this 的綁定規(guī)則有:
- 默認(rèn)綁定:獨(dú)立的函數(shù)調(diào)用,嚴(yán)格模式不能將全局對(duì)象用于默認(rèn)綁定
- 隱式綁定:把函數(shù)調(diào)用中的this 綁定到函數(shù)引用中的上下文對(duì)象
- 顯式綁定:通過(guò)call()和apply()方法可以直接指定this的綁定對(duì)象。其中,硬綁定是一種顯式的強(qiáng)制綁定,ES5中提供了內(nèi)置方法Function.prototype.bind, API中調(diào)用的上下文和bind的作用一樣。
- new 綁定,構(gòu)造函數(shù)只是一些使用new操作符調(diào)用的函, 使用new 來(lái)調(diào)用函數(shù)的操作過(guò)程大致如下:
- 創(chuàng)建一個(gè)全新的對(duì)象
- 這個(gè)新對(duì)象會(huì)被執(zhí)行[[Prototype]]鏈接
- 這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的this
- 如果函數(shù)沒(méi)有返回其他對(duì)象,那么new表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象
如果同時(shí)存在多種綁定,那么綁定的優(yōu)先級(jí)大致如下:
- 由new調(diào)用綁定到新創(chuàng)建的對(duì)象
- 由call 或者apply(或bind)調(diào)用綁定到指定的對(duì)象
- 由上下文對(duì)象調(diào)用綁定到那個(gè)上下文對(duì)象
- 默認(rèn)在在嚴(yán)格模式下綁定到undefined,否則綁定到全局對(duì)象
更安全地使用this 綁定的做法是傳入一個(gè)特殊的對(duì)象,把this 綁定到這個(gè)對(duì)象。需要注意的是,箭頭函數(shù)不使用this的4種規(guī)則,而是根據(jù)外層(函數(shù)或全局)作用域來(lái)決定this。
還要注意一點(diǎn),eval 和 with 會(huì)導(dǎo)致作用域變化而引起性能下降,盡量不要使用。eval() 函數(shù)中的字符串是代碼,用來(lái)執(zhí)行動(dòng)態(tài)創(chuàng)建的代碼,嚴(yán)格模式有自己的作用域,還存在安全隱患;with 是重復(fù)引用一個(gè)對(duì)象中的多個(gè)屬性的快捷方式,通過(guò)將一個(gè)對(duì)象的引用當(dāng)作作用域來(lái)處理,會(huì)改變作用域范圍。
處理和執(zhí)行方式
JavaScript引擎本身沒(méi)有時(shí)間概念,只是一個(gè)按需執(zhí)行任意代碼片段的環(huán)境,事件調(diào)度總是由包含它的宿主環(huán)境來(lái)執(zhí)行。一旦有事件需要運(yùn)行,事件循環(huán)隊(duì)列就會(huì)運(yùn)行,直到隊(duì)列清空,用戶交互、IO和定時(shí)器等事件源會(huì)向事件隊(duì)列加入事件。
由于JavaScript的單線程特性,很多函數(shù)的代碼具有原子性。
回調(diào)函數(shù)封裝了程序的延續(xù)性,常見(jiàn)設(shè)計(jì)是分離回調(diào)(一個(gè)用于成功通知,一個(gè)用于出錯(cuò)通知)。另一種回調(diào)模式是“error-first”,可能受到防御式編程的影響,NodeJS API 采用了此類的風(fēng)格,如果成功的話,這個(gè)參數(shù)就會(huì)被清空。需要注意的是,回調(diào)函數(shù)的嵌套往往稱為回調(diào)地獄。
Deferred是一種將異步處理串聯(lián)書(shū)寫(xiě)并執(zhí)行的機(jī)制,Deferred對(duì)象是一種具有unresolved,resolved,rejected 中某一種狀態(tài)的對(duì)象。Deferred內(nèi)部機(jī)制是先注冊(cè)回調(diào)函數(shù),Deferred對(duì)象狀態(tài)發(fā)生變化時(shí)執(zhí)行該函數(shù),是一種提高代碼可讀性的機(jī)制。
Deferred對(duì)象的狀態(tài)遷移只能發(fā)生一次,以then(),done(),fail(),always(),pipe()指定后續(xù)函數(shù)的方法,通過(guò)when()來(lái)并行處理,將Deferred 對(duì)象中的一部分方法刪除后得到是Promise對(duì)象,對(duì)狀態(tài)的管理由最初創(chuàng)建該Deferred對(duì)象的所有者來(lái)執(zhí)行。
Promise 封裝了依賴于時(shí)間的狀態(tài),從而使得本身與時(shí)間無(wú)關(guān),Promise 可以按照可預(yù)測(cè)的方式進(jìn)行,而不用關(guān)心時(shí)序或底層的結(jié)果。一旦Promise決議完成,就成為了不變值,可以安全地吧這個(gè)值傳遞給第三方,并確保不會(huì)改變。
Promise 是一種在異步任務(wù)中作為兩個(gè)或更多步驟的流程控制機(jī)制,時(shí)序上的this-then-that。 不僅表達(dá)了多步異步序列的流程控制,還是一個(gè)從一個(gè)步驟到下一個(gè)步驟傳遞消息的消息通道。事件監(jiān)聽(tīng)對(duì)象可以當(dāng)成是對(duì)promise 的一種模擬,對(duì)控制反轉(zhuǎn)的恢復(fù)實(shí)現(xiàn)了更好的關(guān)注點(diǎn)分離。
判斷是否是Promise 值的示例代碼如下:
- if(
- p !==null &&
- ( typeof p ===“object” || typeof p ===“function”) && typeof p.then===“function”)
- {
- console.log(“thenable”);
- }
- else{
- console.log(“not thenable”);
- }
生成器是一類特殊的函數(shù),可以一次或多次啟動(dòng)和停止,并不非的一定要完成,生成器把while true 帶回了Javascript的世界。其中,yield 委托的主要目的是代碼組織,以達(dá)到與普通函數(shù)調(diào)用的對(duì)稱。從生成器yield出一個(gè)Promise, 并且讓這個(gè)Promise 通過(guò)一個(gè)輔助函數(shù)恢復(fù)這個(gè)生成器,這是通過(guò)生成器管理異步的好方法之一。
需要注意的是,如果在Promise.all([..]) 中傳入空數(shù)組,會(huì)立即完成, 而Promise.race([..]) 則會(huì)掛住。 在各種Promise庫(kù)中,finally ( .. ) 還是會(huì)創(chuàng)建并返回一個(gè)新Promise的。
模塊與庫(kù)
模塊和庫(kù)是JavaScript 時(shí)空中的另一紐帶,提高了代碼的復(fù)用性和開(kāi)發(fā)效率。
模塊充分利用了閉包的強(qiáng)大能力,從模塊中返回一個(gè)實(shí)際的對(duì)象并不是必須的,也可以直接返回一個(gè)內(nèi)部函數(shù),例如:jQauery 和 $標(biāo)識(shí)符就是jQuery 模塊的公共API。
模塊有兩個(gè)必要條件:
- 必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次
- 封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問(wèn)或修改私有的狀態(tài)
import 可以將一個(gè)模塊的一個(gè)或多個(gè)API導(dǎo)入到當(dāng)前作用域中,并分別綁定在一個(gè)變量上;module 則將整個(gè)模塊的API 導(dǎo)入并綁定到一個(gè)變量上, export 將當(dāng)前模塊的一個(gè)標(biāo)識(shí)符導(dǎo)出為公共API。
大多數(shù)模塊所依賴的加載器/管理器本質(zhì)上是將這種模塊定義封裝進(jìn)一個(gè)API?;诤瘮?shù)的模塊并不是一個(gè)能被靜態(tài)識(shí)別的模式(編譯器),API定義只有在運(yùn)行時(shí)考慮進(jìn)來(lái)。但是ES6 模塊的API 是靜態(tài)的,必須被定義在獨(dú)立的文件中。
JavaScript 中的庫(kù)浩如煙海,這里僅對(duì)JQuery做簡(jiǎn)要說(shuō)明。JQuery壓縮后大約31k,輕巧靈活,通過(guò)鏈?zhǔn)秸Z(yǔ)法實(shí)現(xiàn)邏輯功能,通過(guò)CSS3選擇器及自定義選擇器獲取元素,支持插件,可擴(kuò)展性高。
JQuery中 的特色函數(shù)——$ ,可以抽取與選擇器匹配的元素,或者創(chuàng)建新的DOM元素,將已有的DOM元素轉(zhuǎn)換為jQuery對(duì)象,對(duì)DOM構(gòu)造完成后的事件監(jiān)聽(tīng)器進(jìn)行設(shè)定等等。JQuery 對(duì)DOM,樣式,AJAX 均可有效處理。
通過(guò)擴(kuò)展JQuery.fn 就可以創(chuàng)建JQuery的插件,code.google.com/apis/libraries 給出了很多JQuery 的插件信息。
利用JavaScript 的時(shí)空觀,可以對(duì)這一語(yǔ)言有一些基本的梳理。就語(yǔ)言本身而言,關(guān)鍵字是不能回避的,對(duì)JavaScript 關(guān)鍵字,在StackOverFlow中有人給出了如下詩(shī)一樣的總結(jié):
- Let this long package float,
- Goto private class if short。
- While protected with debug case,
- Continue volatile interface。
- Instanceof super synchronized throw,
- Extends final export throws.
- Try import double enum?
- -False, boolean, abstract function.
- Implements typeof transient break!
- Void static,default do,
- Switch int native new,
- else, delete null public var,
- In return for const, true, char,
- …… finally catch byte.
客戶端應(yīng)用
一門(mén)語(yǔ)言所被使用的廣泛程度取決于使用的場(chǎng)景,正如PHP被廣泛采用那樣,互聯(lián)網(wǎng)應(yīng)用不僅是JavaScript 的家鄉(xiāng),而且是它大展身手的最重要場(chǎng)所,沒(méi)有JavaScript 的Web應(yīng)用幾乎絕跡了。
web應(yīng)用中使用JavaScript有拖拽操作,異步讀取,鍵盤(pán)訪問(wèn) 和動(dòng)畫(huà)效果等基本功能。對(duì)于清晰地使用JavaScript實(shí)現(xiàn)Web應(yīng)用而言,理解瀏覽器網(wǎng)頁(yè)處理過(guò)程是必要的。一般地,瀏覽器先分析HTML,然后構(gòu)造DOM樹(shù),再載入外部Javascript 文件以及CSS文件,接下來(lái)載入圖像文件等外部資源,最后在分析Javascript后開(kāi)始執(zhí)行至全部完成。
在HTML中加載JavaScript的方式有多種:
- <script> 標(biāo)簽,在body 結(jié)束標(biāo)簽前寫(xiě)
- 讀取外部JavaScript 文件,讀取完就開(kāi)始執(zhí)行,瀏覽器可以緩存
- onload 事件加載
- DOMContentLoaded是在完成HTML解析后發(fā)生的事件,也可以用于加載JavaScript
- 動(dòng)態(tài)載入,這樣JS在載入時(shí)不會(huì)阻斷其他操作,如
- var script = document.createElement(‘script’);
- script.src = ‘my-javascript.js’;
- document.getElementsByTagName(‘head’)[0].appendChild(script)
window對(duì)象是JavaScript所能操作的最高層對(duì)象,其中的屬性包括navigator,location,history,screen,frames,document,parent,top,self 等。
DOM 是一種API,完成對(duì)HTML/XML 的樹(shù)形結(jié)構(gòu)訪問(wèn),如標(biāo)簽,元素,節(jié)點(diǎn)等。節(jié)點(diǎn)可以通過(guò)ID,標(biāo)簽名,名稱和類名進(jìn)行檢索,例如:
- var element = document.getElementById(“abel”)
- var allelements = document.getElementByTagName(‘*’)
由于返回的是NodeList對(duì)象,性能較差,可以通過(guò) var array = Array.prototye.slice.call(allelements)轉(zhuǎn)換為array 后處理。節(jié)點(diǎn)的訪問(wèn)可以通過(guò)XPath 進(jìn)行靈活的訪問(wèn),當(dāng)然,Selector API 比XPath更簡(jiǎn)單且同樣靈活,例如:
- var a_label = document.querySelector(‘#abel’)
- var b_all = document.querySelectorAll(‘div’)
如果先修改DocumentFragment,再對(duì)實(shí)際的document對(duì)象操作,DOM 的操作性能會(huì)較高一些。
事件偵聽(tīng)器的設(shè)定可以制定HTML元素的屬性,也可以指定DOM元素的屬性,還可以通過(guò)EventTarget.addEventListenser()進(jìn)行指定。事件的處理包括捕獲,目標(biāo)處理和事件冒泡三個(gè)階段,捕獲的過(guò)程是:
- window -> document -> html -> body -> div -> button
然后處理器執(zhí)行,冒泡向上傳播的過(guò)程是遍歷DOM樹(shù),需要注意的是 focus 不會(huì)冒泡。
DOM2中的標(biāo)準(zhǔn)事件有HTMLEvent,MouseEvent,UIEvent和MutationEvent。DOM3 中的事件更多:UIEvent,F(xiàn)ocusEvent,MouseEvent, WheelEvent, TextEvent,KeyboardEvent 和compositionEvent等,還可以通document.createEvent來(lái)自定義事件。
通過(guò)JavaScript 對(duì)CSS樣式變更的方法有通過(guò)className 屬性變更c(diǎn)lass名,通過(guò)classList屬性更改class名(其中classList 是H5對(duì)DOM TokenList接口的實(shí)現(xiàn)),還可以更改Style 屬性或者直接更改樣式表。通過(guò)JavaScript可以對(duì)屏幕位置(screenX,screenY),窗口位置(clientX,clientY),文檔坐標(biāo)(pageX,pageY,由瀏覽器自行實(shí)現(xiàn)的),特定元素內(nèi)的相對(duì)位置(layerX,layerY 或offsetX offsetY)進(jìn)行修改。通過(guò)JavaScript可以對(duì)表單中的元素,控件和內(nèi)容進(jìn)行驗(yàn)證,可用于驗(yàn)證的事件有submit,focus,blur,change,keydown/up/press,input。使用表單而不產(chǎn)生頁(yè)面跳轉(zhuǎn)的方式可以是指向到一個(gè) (0,0 )的空iframe。
對(duì)于動(dòng)畫(huà)而言,css的動(dòng)畫(huà)性能一般要更好一些。
AJAX 在Web應(yīng)用中是不可或缺的,簡(jiǎn)單地說(shuō),是一種不發(fā)生頁(yè)面跳轉(zhuǎn)就能異步載入內(nèi)容并改寫(xiě)頁(yè)面內(nèi)容的技術(shù),主要通過(guò) XMLHttpRequest 對(duì)象的創(chuàng)建,實(shí)現(xiàn)通/異步通信,處理超時(shí)和響應(yīng)。AJAX有著跨源限制,實(shí)現(xiàn)跨源通信的方式有:JSONP,iframe hack,window.postMessage() 以及 XMLHttpRequest Level 2。
HTML5+CSS3+JavaScript的綜合使用才可能成就一個(gè)Web應(yīng)用。H5中的 History API 使用了window屬性的history對(duì)象監(jiān)聽(tīng)popstate事件,用于恢復(fù)頁(yè)面狀態(tài)的處理。ApplicationCache 在html標(biāo)簽的manifest 屬性中指定了緩存清單文件的路徑,必須通過(guò)text/cache-manifest 這一MIME type 來(lái)發(fā)布緩存清單文件,注意清單中的CACHE,NETWORK,和FALLBACK 的區(qū)分。
通過(guò)navigator.onLine 可以獲知網(wǎng)絡(luò)狀態(tài),還可以通過(guò)online/offline事件來(lái)偵聽(tīng)連接狀態(tài)的切換時(shí)機(jī)。online/offline事件是document.body 觸發(fā)的,并傳給document對(duì)象和window對(duì)象。
- <p> network is : <span id = “indicator”> (state unknown) </span> </p>
- <script>
- {
- function updateIndicator = document.getElementById(‘indicator’);
- indicator.textContext = navigator.online?’online’:’offline’;
- }
- document.body.onload = updateIndicator;
- document.body.ononline= updateIndicator;
- document.body.onoffline = updateIndicator;
- </script>
DataTransfer 是Drag Drop API 的核心,在所有拖拽事件的事件對(duì)象中,都有該屬性,主要是接收數(shù)據(jù)。拖拽文件從瀏覽器保存到桌面:event.dataTransfer.setData(‘DownloadURL’,’MIMETYPE: 文件url’)例如:
- <a href=“http://a.b.c/abel.pdf”
- data-downloadurl = “application/pdf:abel.pdf:http://a.b.c/abel.pdf”
- class=“dragout” draggable = “true”>download </a>
- <script>
- var files = document.querySelectorAll(‘.dragout’);
- for (var i = 0,file; file =files[i];i++) {
- file.addEventListener(‘dragstart’,function(event){
- event.dataTransfer.setData(“DownloadURL”,this.dataset.downloadurl);
- },false);
- }
- </script>
FileAPI 通過(guò)FileReader 讀取文件,也可以讀取dataURL,F(xiàn)ileReaderSync 用于同步讀取文件內(nèi)容,可以在Web Worker 中使用。
Web Storage 為所有源共享5M空間,localStorage 和sessionStorage 的區(qū)別在于數(shù)據(jù)的生命周期。cookie 最大4k,發(fā)請(qǐng)求時(shí)一起發(fā)送,保存會(huì)話等重要信息。indexedDB 可以歸為文檔型數(shù)據(jù)庫(kù), 作為客戶端存儲(chǔ)又一選擇。
- var indexdb = window.indexDB||window.webkitIndexedDB||window.mozIndexedDB;
Web worker 是H5 的新特性,是宿主環(huán)境(瀏覽器)的功能,JavaScript 本身是不支持多線程的。專用的worker 與創(chuàng)建它的程序之間是一對(duì)一的關(guān)系。
Web worker 能在另外的線程中創(chuàng)建新的Javascript 運(yùn)行環(huán)境,使JavaScripts可以在后臺(tái)處理。主線程和工作線程分離,無(wú)法使用對(duì)方環(huán)境的變量。工作線程無(wú)法引用document對(duì)象,需要通過(guò)消息收發(fā)完成數(shù)據(jù)傳遞。 在主線程創(chuàng)建工作線程,大約向var worker = new Worker(‘work.js’)這樣 在主線程中停止worker的方式是worker.terminate(); worker 自身停止的方式是 self.close();worker 中 可以通個(gè) importScripts 方法,在工作線程內(nèi)讀取外部的文件。
了解了這些基礎(chǔ)方式和方法,僅僅是Web應(yīng)用中JavaScript開(kāi)發(fā)的第一步吧。
服務(wù)端應(yīng)用
技術(shù)系統(tǒng)總是又著向超系統(tǒng)進(jìn)化的趨勢(shì),JavaScript 也不例外。
JavaScript 應(yīng)用于服務(wù)端的開(kāi)發(fā)源于2009年初出現(xiàn)的CommonJS,后來(lái)成為為了服務(wù)器端javaScript的規(guī)范?;贘avaScript沒(méi)有模塊系統(tǒng)、標(biāo)準(zhǔn)庫(kù)較少、缺乏包管理工具等現(xiàn)狀,CommonJS規(guī)范希望JavaScript可以在任何地方運(yùn)行,以達(dá)到Java、C#、PHP這些后臺(tái)語(yǔ)言具備開(kāi)發(fā)大型應(yīng)用的能力。CommonJS是一種思想,它的終極目標(biāo)是使應(yīng)用程序開(kāi)發(fā)者根據(jù)CommonJS API編寫(xiě)的JavaScript應(yīng)用可以在不同的JavaScript解析器和HOST環(huán)境上運(yùn)行,例如編寫(xiě)服務(wù)端應(yīng)用,命令行工具,基于GUI的桌面應(yīng)用和混合應(yīng)用編程等,詳情參加 www.commonjs.org 。
NodeJS可以理解成CommonJS規(guī)范的一種實(shí)現(xiàn),而且是部分實(shí)現(xiàn)。NodeJS以V8作為JavaScript的實(shí)現(xiàn)引擎,通用的異步處理事件循環(huán),提供了一系列非阻塞函數(shù)庫(kù)來(lái)支持實(shí)踐循環(huán)特性。同時(shí),NodeJS提供了高度優(yōu)化的應(yīng)用庫(kù),來(lái)提高服務(wù)器效率,例如其http 模塊是為快速非阻塞式http服務(wù)而用C語(yǔ)言重寫(xiě)的。另外,NodeJS還有shell的命令行工具,通過(guò)包系統(tǒng)實(shí)現(xiàn)擴(kuò)展,擴(kuò)展列表可以詳情參見(jiàn): GitHub.com/node/wiki/modules。
JavaScript 中的主要實(shí)現(xiàn)引擎包括:IE采用的JScript,F(xiàn)irefox采用的SpiderMoneky,Chrome 采用的V8,Safari采用的webkit中的 javacriptcore燈。如果要對(duì)引擎有進(jìn)一步的了解,可以研讀一下javascriptcore等相關(guān)的源代碼。
V8 是NodeJS 中的核心引擎,NodeJS的系統(tǒng)架構(gòu)大致如下:
與瀏覽器相對(duì)應(yīng),Node 中的全局變量可以通過(guò) Object.keys(global); 獲得, 看一看NodeJS中的 “hello world” 程序:
- var http = require('http');
- http.createServer(function (req,res){
- res.writeHead(200,{'Content-type':'text/plain'});
- res.end('Hello Node.js \n');
- }).listen(1234,"127.0.0.1");
- console.log('Server running on http://127.0.0.1:1234/');
幾行代碼就實(shí)現(xiàn)一個(gè)簡(jiǎn)單web server, 使Pythoner 們聯(lián)想到了 Tornado, 它們都走在單線程異步IO的路上。
NodeJS 提供了對(duì)https 的支持,可以通過(guò)openssl 生成證書(shū)的方式大致是:
- openssl req -new -x509 -keyout key.pen -out cert.perm
使用證書(shū)的示例如下:
- var fs =require(‘fs’);
- var options = {
- key: fs.readFileSync(‘key.perm’);
- cert:fs.readFileSync(‘cert.perm’);
- }
NodeJS支持socket 和文件處理,配合系統(tǒng)擴(kuò)展可以使用各種模版語(yǔ)言。基于NodeJS的實(shí)際在業(yè)界非常廣泛,比如面向websocket的IM系統(tǒng),各種web應(yīng)用網(wǎng)站等等。
鑒于微服務(wù)架構(gòu)的興起,也誕生了基于Node的微服務(wù)架構(gòu)——Seneca,它使用完備的模式匹配接口來(lái)連接各個(gè)服務(wù),從代碼中將數(shù)據(jù)傳輸抽象出來(lái),使編寫(xiě)具有高擴(kuò)展性的軟件變得相當(dāng)容易。Seneca 沒(méi)有使用依賴注入,但是在處理控制反轉(zhuǎn)上相當(dāng)靈活,沒(méi)有關(guān)鍵字和強(qiáng)制的字段,只需一組鍵值對(duì),用于模式匹配的引擎中。具體參考實(shí)現(xiàn),可以參考《node.js 微服務(wù)》一書(shū)。
基于JavaScript的全棧
如果在整個(gè)應(yīng)用系統(tǒng)中主要使用JavaScript編程語(yǔ)言作為技術(shù)棧,那么也可以成為基于JavaScript 的全棧,關(guān)于全棧的論述可以參加《全棧的技術(shù)棧設(shè)想》和《再談< 全棧架構(gòu)師>》兩篇文字。例如MEAN架構(gòu),即MongoDB + Express + Angular + Node,MEAN 技術(shù)棧代表著一種完全現(xiàn)代的 Web 開(kāi)發(fā)方法:一種語(yǔ)言運(yùn)行在應(yīng)用程序的所有層次上,從客戶端到服務(wù)器,再到持久層。借助JavaScript的測(cè)試框架,比如MochaJS、JasmineJS 和 KarmaJS,可以為自己的 MEAN 應(yīng)用程序編寫(xiě)深入而又全面的測(cè)試套件,據(jù)說(shuō)MEAN有取代LAMP/LNMP的的趨勢(shì),但還需保持謹(jǐn)慎。
引擎的差異
正像Java 那樣,盡管又著虛擬機(jī)規(guī)范,但各個(gè)JVM的實(shí)現(xiàn)還是有著些許的不同,JavaScript 也是如此。JavaScript各引擎中同樣存在著少量的限制,例如:
- 字符串常量中允許的最大字符數(shù)
- 作為參數(shù)傳遞到函數(shù)中的數(shù)據(jù)大小(棧大小)
- 函數(shù)聲明中的參數(shù)個(gè)數(shù)
- 函數(shù)調(diào)用鏈的最大長(zhǎng)度
- 以阻塞方式在瀏覽器中運(yùn)行的最大時(shí)間
- 變量名的最大長(zhǎng)度
- 盡管如此,JavaScript 在瀏覽器中的表現(xiàn)還是基本上可信的。
從軟件到硬件
實(shí)際上,JavaScript已經(jīng)嵌入到了從機(jī)器人到各種家電等各種各樣的設(shè)備中。這里隆重推薦我非常敬佩的好友——周愛(ài)民老師,他在Ruff(南潮信息科技)做的事情就是JavaScript 在物聯(lián)網(wǎng)上的進(jìn)一步應(yīng)用。
Ruff 是一個(gè)可以讓開(kāi)發(fā)者實(shí)現(xiàn)敏捷開(kāi)發(fā)智能硬件的系統(tǒng)平臺(tái)。它包含了Ruff SDK、Ruff OS,Rap Registry等。從技術(shù)上講,Ruff 是一個(gè) JavaScript 運(yùn)行時(shí),專為硬件開(kāi)發(fā)而設(shè)計(jì)。Ruff 對(duì)硬件進(jìn)行了抽象,使用了基于事件驅(qū)動(dòng)、異步 I/O 的模型,使硬件開(kāi)發(fā)變得輕量而且高效。硬件抽象層,使得操作硬件猶如普通程序庫(kù),降低了硬件領(lǐng)域進(jìn)入門(mén)檻。
Ruff 為開(kāi)發(fā)者提供了完善的開(kāi)發(fā)服務(wù)。從項(xiàng)目生產(chǎn)、軟件包管理、應(yīng)用管理、外設(shè)管理到固件管理等一系列現(xiàn)代軟件開(kāi)發(fā)方式,PC 端完成開(kāi)發(fā),無(wú)需燒板子,提升開(kāi)發(fā)者的開(kāi)發(fā)效率。Ruff 還提供了完善的測(cè)試框架,支持 assert、test、mock 等模塊,在開(kāi)發(fā)機(jī)上測(cè)試邏輯,硬件測(cè)試也能 TDD。
官網(wǎng)(ruff.io) 上給出的示例如下:
- $.ready(function() {
- $('#led-0').turnOn();
- });
打開(kāi)電路板上的一個(gè)LED 燈,就是如此的簡(jiǎn)單。目前,一個(gè) Ruff 硬件同時(shí)只能運(yùn)行一款 Ruff 應(yīng)用,它將擁有自己獨(dú)立的進(jìn)程,著可能也受到JavaScript自身的限制吧。
關(guān)注性能
性能是全棧關(guān)注的一個(gè)重要維度,那句“過(guò)早優(yōu)化是萬(wàn)惡之源”實(shí)際上是我們對(duì)高德納先生的斷章取義,原文是這樣的:
我們應(yīng)該在例如97%的時(shí)間里,忘掉小處的效率;過(guò)早優(yōu)化是萬(wàn)惡之源。但我們不應(yīng)該錯(cuò)過(guò)關(guān)鍵的3%中的機(jī)會(huì)。
實(shí)際上是非關(guān)鍵路徑上的優(yōu)化是萬(wàn)惡之源,問(wèn)題在于如何確定我們的代碼是否在關(guān)鍵路徑上。不論節(jié)省的時(shí)間多么少,花費(fèi)在關(guān)鍵路徑上的性能優(yōu)化都是值得的。
對(duì)于性能優(yōu)化工具,用于JavaScript源代碼壓縮有 google Closure complier,packer,YUI compressor,JSmin等。頁(yè)面的性能優(yōu)化工具有YSlow 和Page Speed等。實(shí)際上,任何有意義且可靠的性能測(cè)試都是基于統(tǒng)計(jì)學(xué)上的合理實(shí)踐。 就JavaScript 代碼本身的性能而言,benchmarkjs 是一個(gè)很好的工具,而jsperf.com 提供了對(duì)JavaScript 執(zhí)行環(huán)境的性能測(cè)試。
總之,JavaScript 是一個(gè)具有強(qiáng)大生命力的語(yǔ)言,前端框架更是日新月異,從Angular,Vue,到React, 乃至React Native,給人以目不暇接的感覺(jué),但是,老碼農(nóng)覺(jué)得基礎(chǔ)認(rèn)識(shí)還是非常必要的,勿在浮沙筑高塔。
【本文來(lái)自51CTO專欄作者“老曹”的原創(chuàng)文章,作者微信公眾號(hào):喔家ArchiSelf,id:wrieless-com】