全棧必備 JavaScript基礎

weixin_34234823發表於2017-04-05

JavaScript 來了

1995年,誕生了JavaScript語言,那一年,我剛剛從大學畢業。在今年RedMonk 推出的2017 年第一季度程式語言排行榜中,JavaScript 排第一,Java第二,Python反超 PHP 排第三,PHP 第四,C# 和 C++ 並列第五。RedMonk 排名的主要依舊是各種程式語言在 Stack Overflow 和 GitHub 上的表現,比如程式語言在 Stack Overflow 上的討論數量,在 GitHub 上的程式碼量等。儘管有一定的片面性,還是說明了JavaScript 應用的廣泛性。從全棧的角度看,Javascript 是必備的一種程式語言。


73516-238c8a4ae3ae3e87

ECMAScript 和 JavaScript 的關係

JavaScript 誕生於Netscape,但是在1996年,微軟釋出了與JavaScript 相容的JScript,面對相容和發展的需要,網景公司的先賢們努力加入了 ECMA International 標準化組織,致力於JavaScript 的標準化,命名為ECMAScript。後來,由於歷史的原因, JavaScript標準的開發主體變成了Mozila基金會。關於ECMAScript 的最新版本可以參閱 https://tc39.github.io/ecma262/

簡單地,ECMAScript 是JavaScript語言的標準規範,就像C++的標準相對於C++語言那樣。

JavaScript 是怎樣的語言

在mozilla 開發者網站上是這樣描述JavaScript的:

JavaScript (JS) is a lightweight interpreted or JIT-compiled programming language with first-class functions. 

意思是說JavaScript 是一個輕量級解釋或即時編譯的函式式語言,裡面有很多的概念,輕量、解釋、編譯、即時編譯、函式式。在老碼農看來,簡單起見,理解為擴充套件語言較為方便。

一般的程式語言都有著自己相對獨立的執行環境,但是JavaScript的執行環境依賴在宿主環境中,宿主環境尤其是客戶端的宿主環境提供了更多統一的環境變數,比如瀏覽器中的window,document等。實際上,JavaScript 和DOM 是可分的,對於不同的執行環境,有著不同的內建宿主物件。JavaScript作為擴充套件語言在內建的宿主環境中執行,全域性物件在程式啟動前就已經存在了。

JavaScript的時空基礎

從空間觀的角度看,JavaScript包括資料結構 ,操作符,語句與表示式,函式;從時間的角度看,包括作用域,處理方式,模組與庫。關於技術系統的時空觀,可以參見《面向全棧的技術管理》一文。

資料結構

JavaScript 中包含的六種基本型別:

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol (ECMAScript 6)

其它全是物件。值是有型別的,變數是沒有型別的,型別定義了值的行為特徵,變數在沒有持有值的時候是undefined。 JavaScript對值和引用的賦值/傳遞在語法上沒有區別,完全根據值的型別來判定。
對於物件的屬性和方法而言,全域性變數和全域性函式是全域性物件的屬性,全域性物件相當於宿主物件的根物件。需要注意是屬性的屬性中那些不可變物件的實現方式:

  1. 物件常量: 結合writable和configurable:false 可以建立一個真正的常量屬性
  2. 禁止擴張:Object.preventExtensions(..)來禁止一個物件新增新屬性並保留已有屬性
  3. 密封: 在 Object.seal(..) 後不能增,刪,改 該屬性
  4. 凍結: Object.freeze(..) 會禁止對於物件本身及任意直接屬性的修改

資料型別的判定可以通過 contructor,instanceof, isPrototypeOf等方法實現,對於鴨子型別的判定還可以使用 in 的相關操作。符號並非物件,而是一種簡單標量基本型別。

JavaScript 中的強制型別轉換總是返回基本型別值,將物件強制轉換為String 是通過ToPrimitive抽象操作完成的,而toJSON()是返回一個能夠被字串化的安全的JSON值。

操作符

操作符是空間元素連線的紐帶之一,JavaScript操作符包括算術,連線,相等,比較,邏輯,位,型別判斷,條件,new,delete, void,",", ".", "[]"等。

在JavaScript中以操作符進行操作往往都附帶著型別轉換。

一元運算子+ 是顯式強制型別轉換,而~是先轉換為32位數字,然後按位反轉。|| 和&& 更應該算是選擇器運算子,其返回值不一定是布林值,而是兩個運算元其中的一個值。一般先對第一個運算元進行toBoolean強制型別轉換,然後再執行條件判斷。例如:a||b 理解成a?a:b 更通俗。對&& 而言,如果第一個是真值,則把第二個作為返回值,a&&b 理解成a?b:a 。

== 和=== 都會對運算元進行型別檢查,並執行隱性型別轉換,需要注意的是:

  • 如果兩邊的值中有true或false,千萬不要使用==
  • 如果兩邊有[],””或者0,儘量不要使用==

這裡是Github上關於各種相等性的矩陣:


73516-e8206c7e048b3daa

語句與表示式

操作符與變數/常量等連線形成了語句和表示式,例如表示式a+1中的null 被強制轉換為0。 語句包括宣告與塊,控制語句有判斷,迴圈,break,continue,return,異常等。每個語句都有一個結果值,哪怕是undefined。

正規表示式是非常重要的一類表示式,主要使用RegExp類,執行方法test效率高,exec 會得到一個結果物件的陣列。

逗號運算子可以把多個獨立的表示式串聯成一個語句,{ }在不同情況下的意思不盡相同,作為語句塊,{ ..} 和for/while迴圈以及if條件語句中程式碼塊的作用基本相同。{a,b} 實際上是{a:a,b:b}的簡化版本。

try..catch..finally 中,如果finally中丟擲異常,函式會在此處終止。需要注意的是,如果此前try中已經有return設定了返回值,則該值會被丟棄。finally中的return也會覆蓋try和catch中的return的返回值。

函式與作用域

函式就是具有運算邏輯的物件,匿名函式不利於除錯,回撥函式是一種控制反轉。所有的函式(物件)都具有名為prototype的屬性,prototype屬性引用的物件是prototype物件;所有的物件都含有一個隱式連結,用以指向在物件生成過程中所使用的建構函式的prototype物件。

匿名函式沒有name 識別符號,具有如下缺陷:

  • 程式碼更難理解
  • 除錯棧更難追蹤
  • 自我引用(遞迴,事件(解除)繫結,等)更難

如果function是宣告的第一個詞,那就是函式宣告,否則就是函式表示式。立即執行函式表示式形如:(function …)( )

時空密不可分,作用域是時空連線的紐帶之一。作用域包括全域性,函式,塊級作用域。作用域是根據名稱查詢變數的一套規則,遍歷巢狀作用域鏈的規則簡單:引擎從當前執行作用域逐級向上查詢。閉包可以理解為具有狀態的函式。

函式作用域指屬於這個函式的全部變數都可以在整個函式的範圍內使用或複用。塊作用域形如 with, try/catch, ES6 引入了let,const等。

動態作用域並不關心函式和作用域是如何宣告以及在何處宣告的,只關心它們從何處呼叫的。詞法作用域是定義在詞法分析階段的作用域,詞法作用域查詢會在第一個匹配的識別符號時停止。作用域鏈是基於呼叫棧的,而不是程式碼中的作用域巢狀。ReferenceError 是與作用域判別失敗相關,而TypeError則是作用域判別成功,但是對結果的操作非法或不合理。

this 提供了一種優雅方式來隱式“傳遞”一個物件引用。 this 即沒有指向函式的自身,也沒有指向函式的作用域,是在函式被呼叫時發生的繫結,它指向什麼完全取決於函式在哪裡被呼叫。如果分析this繫結的話,可以使用除錯工具得到呼叫棧,然後找到棧中的第二個元素,就是真正的呼叫位置。

this 的繫結規則大約是:

  1. 預設繫結:獨立的函式呼叫,嚴格模式不能將全域性物件用於預設繫結
  2. 隱式繫結:把函式呼叫中的this 繫結到函式引用中的上下文物件
  3. 顯式繫結:通過call()和apply()方法可以直接指定this的繫結物件。其中,硬繫結是一種顯式的強制繫結,ES5中提供了內建方法Function.prototype.bind, API中呼叫的上下文和bind的作用一樣。
  4. new 繫結,建構函式只是一些使用new操作符呼叫的函, 使用new 來呼叫函式的操作過程大致如下:
  • 建立一個全新的物件
  • 這個新物件會被執行[[Prototype]]連結
  • 這個新物件會繫結到函式呼叫的this
  • 如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件

如果同時存在多種繫結,那麼繫結的優先順序大致如下:

  1. 由new呼叫繫結到新建立的物件
  2. 由call 或者apply(或bind)呼叫繫結到指定的物件
  3. 由上下文物件呼叫繫結到那個上下文物件
  4. 預設在在嚴格模式下繫結到undefined,否則繫結到全域性物件

更安全地使用this 繫結的做法是傳入一個特殊的物件,把this 繫結到這個物件。需要注意的是,箭頭函式不使用this的4種規則,而是根據外層(函式或全域性)作用域來決定this。

還要注意一點,eval 和 with 會導致作用域變化而引起效能下降,儘量不要使用。eval() 函式中的字串是程式碼,用來執行動態建立的程式碼,嚴格模式有自己的作用域,還存在安全隱患;with 是重複引用一個物件中的多個屬性的快捷方式,通過將一個物件的引用當作作用域來處理,會改變作用域範圍。

處理和執行方式

JavaScript引擎本身沒有時間概念,只是一個按需執行任意程式碼片段的環境,事件排程總是由包含它的宿主環境來執行。一旦有事件需要執行,事件迴圈佇列就會執行,直到佇列清空,使用者互動、IO和定時器等事件源會向事件佇列加入事件。

由於JavaScript的單執行緒特性,很多函式的程式碼具有原子性。

回撥函式封裝了程式的延續性,常見設計是分離回撥(一個用於成功通知,一個用於出錯通知)。另一種回撥模式是“error-first”,可能受到防禦式程式設計的影響,NodeJS API 採用了此類的風格,如果成功的話,這個引數就會被清空。需要注意的是,回撥函式的巢狀往往稱為回撥地獄。

Deferred是一種將非同步處理串聯書寫並執行的機制,Deferred物件是一種具有unresolved,resolved,rejected 中某一種狀態的物件。Deferred內部機制是先註冊回撥函式,Deferred物件狀態發生變化時執行該函式,是一種提高程式碼可讀性的機制。Deferred物件的狀態遷移只能發生一次,以then(),done(),fail(),always(),pipe()指定後續函式的方法,通過when()來並行處理,將Deferred 物件中的一部分方法刪除後得到是Promise物件,對狀態的管理由最初建立該Deferred物件的所有者來執行。

Promise 封裝了依賴於時間的狀態,從而使得本身與時間無關,Promise 可以按照可預測的方式進行,而不用關心時序或底層的結果。一旦Promise決議完成,就成為了不變值,可以安全地吧這個值傳遞給第三方,並確保不會改變。

Promise 是一種在非同步任務中作為兩個或更多步驟的流程控制機制,時序上的this-then-that。 不僅表達了多步非同步序列的流程控制,還是一個從一個步驟到下一個步驟傳遞訊息的訊息通道。事件監聽物件可以當成是對promise 的一種模擬,對控制反轉的恢復實現了更好的關注點分離。

判斷是否是Promise 值的示例程式碼如下:

if(    p !==null &&    
    ( typeof p ===“object” || typeof p ===“function”) && typeof       p.then===“function”)    
{        
  console.log(“thenable”);    
}else
{   
 console.log(“not thenable”);
}

生成器是一類特殊的函式,可以一次或多次啟動和停止,並不非的一定要完成,生成器把while true 帶回了Javascript的世界。其中,yield 委託的主要目的是程式碼組織,以達到與普通函式呼叫的對稱。從生成器yield出一個Promise, 並且讓這個Promise 通過一個輔助函式恢復這個生成器,這是通過生成器管理非同步的好方法之一。

需要注意的是,如果在Promise.all([..]) 中傳入空陣列,會立即完成, 而Promise.race([..]) 則會掛住。 在各種Promise庫中,finally ( .. ) 還是會建立並返回一個新Promise的。

模組與庫

模組和庫是JavaScript 時空中的另一紐帶,提高了程式碼的複用性和開發效率。

模組充分利用了閉包的強大能力,從模組中返回一個實際的物件並不是必須的,也可以直接返回一個內部函式,例如:jQauery 和 $識別符號就是jQuery 模組的公共API。
模組有兩個必要條件:

  • 必須有外部的封閉函式,該函式必須至少被呼叫一次
  • 封閉函式必須返回至少一個內部函式,這樣內部函式才能在私有作用域中形成閉包,並且可以訪問或修改私有的狀態

import 可以將一個模組的一個或多個API匯入到當前作用域中,並分別繫結在一個變數上;module 則將整個模組的API 匯入並繫結到一個變數上, export 將當前模組的一個識別符號匯出為公共API。

大多數模組所依賴的載入器/管理器本質上是將這種模組定義封裝進一個API。基於函式的模組並不是一個能被靜態識別的模式(編譯器),API定義只有在執行時考慮進來。但是ES6 模組的API 是靜態的,必須被定義在獨立的檔案中。

JavaScript 中的庫浩如煙海,這裡僅對JQuery做簡要說明。JQuery壓縮後大約31k,輕巧靈活,通過鏈式語法實現邏輯功能,通過CSS3選擇器及自定義選擇器獲取元素,支援外掛,可擴充套件性高。

JQuery中 的特色函式——$ ,可以抽取與選擇器匹配的元素,或者建立新的DOM元素,將已有的DOM元素轉換為jQuery物件,對DOM構造完成後的事件監聽器進行設定等等。JQuery 對DOM,樣式,AJAX 均可有效處理。通過擴充套件JQuery.fn 就可以建立JQuery的外掛,code.google.com/apis/libraries 給出了很多JQuery 的外掛資訊。

利用JavaScript 的時空觀,可以對這一語言有一些基本的梳理。就語言本身而言,關鍵字是不能迴避的,對JavaScript 關鍵字,在StackOverFlow中有人給出瞭如下詩一樣的總結:

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.

客戶端應用

一門語言所被使用的廣泛程度取決於使用的場景,正如PHP被廣泛採用那樣,網際網路應用不僅是JavaScript 的家鄉,而且是它大展身手的最重要場所,沒有JavaScript 的Web應用幾乎絕跡了。

web應用中使用JavaScript有拖拽操作,非同步讀取,鍵盤訪問 和動畫效果等基本功能。對於清晰地使用JavaScript實現Web應用而言,理解瀏覽器網頁處理過程是必要的。一般地,瀏覽器先分析HTML,然後構造DOM樹,再載入外部Javascript 檔案以及CSS檔案,接下來載入影像檔案等外部資源,最後在分析Javascript後開始執行至全部完成。

在HTML中載入JavaScript的方式有多種:

  • <script> 標籤,在body 結束標籤前寫
  • 讀取外部JavaScript 檔案,讀取完就開始執行,瀏覽器可以快取
  • onload 事件載入
  • DOMContentLoaded是在完成HTML解析後發生的事件,也可以用於載入JavaScript
  • 動態載入,這樣JS在載入時不會阻斷其他操作,如
var script = document.createElement(‘script’);
script.src = ‘my-javascript.js’;
document.getElementsByTagName(‘head’)[0].appendChild(script);

window物件是JavaScript所能操作的最高層物件,其中的屬性包括navigator,location,history,screen,frames,document,parent,top,self 等。

DOM 是一種API,完成對HTML/XML 的樹形結構訪問,如標籤,元素,節點等。節點可以通過ID,標籤名,名稱和類名進行檢索,例如:
var element = document.getElementById(“abel”)var allelements = document.getElementByTagName(‘*’)

由於返回的是NodeList物件,效能較差,可以通過 var array = Array.prototye.slice.call(allelements)
轉換為array 後處理。節點的訪問可以通過XPath 進行靈活的訪問,當然,Selector API 比XPath更簡單且同樣靈活,例如:

var a_label = document.querySelector(‘#abel’)
var b_all = document.querySelectorAll(‘div’)

如果先修改DocumentFragment,再對實際的document物件操作,DOM 的操作效能會較高一些。

事件偵聽器的設定可以制定HTML元素的屬性,也可以指定DOM元素的屬性,還可以通過EventTarget.addEventListenser()進行指定。事件的處理包括捕獲,目標處理和事件冒泡三個階段,捕獲的過程是:

window -> document -> html -> body -> div -> button

然後處理器執行,冒泡向上傳播的過程是遍歷DOM樹,需要注意的是 focus 不會冒泡。

DOM2中的標準事件有HTMLEvent,MouseEvent,UIEvent和MutationEvent。DOM3 中的事件更多:UIEvent,FocusEvent,MouseEvent, WheelEvent, TextEvent,KeyboardEvent 和compositionEvent等,還可以通document.createEvent來自定義事件。

通過JavaScript 對CSS樣式變更的方法有通過className 屬性變更class名,通過classList屬性更改class名(其中classList 是H5對DOM TokenList介面的實現),還可以更改Style 屬性或者直接更改樣式表。通過JavaScript可以對螢幕位置(screenX,screenY),視窗位置(clientX,clientY),文件座標(pageX,pageY,由瀏覽器自行實現的),特定元素內的相對位置(layerX,layerY 或offsetX offsetY)進行修改。通過JavaScript可以對錶單中的元素,控制元件和內容進行驗證,可用於驗證的事件有submit,focus,blur,change,keydown/up/press,input。使用表單而不產生頁面跳轉的方式可以是指向到一個 (0,0 )的空iframe。

對於動畫而言,css的動畫效能一般要更好一些。

AJAX 在Web應用中是不可或缺的,簡單地說,是一種不發生頁面跳轉就能非同步載入內容並改寫頁面內容的技術,主要通過 XMLHttpRequest 物件的建立,實現通/非同步通訊,處理超時和響應。

AJAX有著跨源限制,實現跨源通訊的方式有JSONP,iframe hack,window.postMessage() 以及 XMLHttpRequest Level 2。
HTML5+CSS3+JavaScript的綜合使用才可能成就一個Web應用。

H5中的 History API 使用了window屬性的history物件監聽popstate事件,用於恢復頁面狀態的處理。ApplicationCache 在html標籤的manifest 屬性中指定了快取清單檔案的路徑,必須通過text/cache-manifest 這一MIME type 來發布快取清單檔案,注意清單中的CACHE,NETWORK,和FALLBACK 的區分。

通過navigator.onLine 可以獲知網路狀態,還可以通過online/offline事件來偵聽連線狀態的切換時機。online/offline事件是document.body 觸發的,並傳給document物件和window物件。

<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 的核心,在所有拖拽事件的事件物件中,都有該屬性,主要是接收資料。拖拽檔案從瀏覽器儲存到桌面:
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 通過FileReader 讀取檔案,也可以讀取dataURL,FileReaderSync 用於同步讀取檔案內容,可以在Web Worker 中使用。

Web Storage 為所有源共享5M空間,localStorage 和sessionStorage 的區別在於資料的生命週期。cookie 最大4k,發請求時一起傳送,儲存會話等重要資訊。indexedDB 可以歸為文件型資料庫, 作為客戶端儲存又一選擇。

var indexdb = window.indexDB||window.webkitIndexedDB||window.mozIndexedDB;

Web worker 是H5 的新特性,是宿主環境(瀏覽器)的功能,JavaScript 本身是不支援多執行緒的。專用的worker 與建立它的程式之間是一對一的關係。

Web worker 能在另外的執行緒中建立新的Javascript 執行環境,使JavaScripts可以在後臺處理。主執行緒和工作執行緒分離,無法使用對方環境的變數。工作執行緒無法引用document物件,需要通過訊息收發完成資料傳遞。 在主執行緒建立工作執行緒,大約向var worker = new Worker(‘work.js’)這樣在主執行緒中停止worker的方式是worker.terminate();worker 自身停止的方式是 self.close();
worker 中 可以通個 importScripts 方法,在工作執行緒內讀取外部的檔案。

瞭解了這些基礎方式和方法,僅僅是Web應用中JavaScript開發的第一步吧。

服務端應用

技術系統總是又著向超系統進化的趨勢,JavaScript 也不例外。
JavaScript 應用於服務端的開發源於2009年初出現的CommonJS,後來成為為了伺服器端javaScript的規範。基於JavaScript沒有模組系統、標準庫較少、缺乏包管理工具等現狀,CommonJS規範希望JavaScript可以在任何地方執行,以達到Java、C#、PHP這些後臺語言具備開發大型應用的能力。

CommonJS是一種思想,它的終極目標是使應用程式開發者根據CommonJS API編寫的JavaScript應用可以在不同的JavaScript解析器和HOST環境上執行,例如編寫服務端應用,命令列工具,基於GUI的桌面應用和混合應用程式設計等,詳情參加 www.commonjs.org

NodeJS可以理解成CommonJS規範的一種實現,而且是部分實現。NodeJS以V8作為JavaScript的實現引擎,通用的非同步處理事件迴圈,提供了一系列非阻塞函式庫來支援實踐迴圈特性。同時,NodeJS提供了高度優化的應用庫,來提高伺服器效率,例如其http 模組是為快速非阻塞式http服務而用C語言重寫的。另外,NodeJS還有shell的命令列工具,通過包系統實現擴充套件,擴充套件列表可以詳情參見: GitHub.com/node/wiki/modules。

JavaScript 中的主要實現引擎包括:IE採用的JScript,Firefox採用的SpiderMoneky,Chrome 採用的V8,Safari採用的webkit中的 javacriptcore燈。如果要對引擎有進一步的瞭解,可以研讀一下javascriptcore等相關的原始碼。

V8 是NodeJS 中的核心引擎,NodeJS的系統架構大致如下:


73516-43c77d2bfed61309

與瀏覽器相對應,Node 中的全域性變數可以通過 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/');

幾行程式碼就實現一個簡單web server, 使Pythoner 們聯想到了 Tornado, 它們都走在單執行緒非同步IO的路上。

NodeJS 提供了對https 的支援,可以通過openssl 生成證照的方式大致是:

openssl req -new -x509 -keyout key.pen -out cert.perm

使用證照的示例如下:

var fs  =require(‘fs’);
var options = {    
        key: fs.readFileSync(‘key.perm’);   
        cert:fs.readFileSync(‘cert.perm’);
}

NodeJS支援socket 和檔案處理,配合系統擴充套件可以使用各種模版語言。基於NodeJS的實際在業界非常廣泛,比如面向websocket的IM系統,各種web應用網站等等。

鑑於微服務架構的興起,也誕生了基於Node的微服務架構——Seneca,它使用完備的模式匹配介面來連線各個服務,從程式碼中將資料傳輸抽象出來,使編寫具有高擴充套件性的軟體變得相當容易。Seneca 沒有使用依賴注入,但是在處理控制反轉上相當靈活,沒有關鍵字和強制的欄位,只需一組鍵值對,用於模式匹配的引擎中。具體參考實現,可以參考《Node.js微服務》一書。

基於JavaScript的全棧

如果在整個應用系統中主要使用JavaScript程式語言作為技術棧,那麼也可以成為基於JavaScript 的全棧,關於全棧的論述可以參加《全棧的技術棧設想》和《再談< 全棧架構師>》兩篇文字。例如MEAN架構,即MongoDB+ Express + Angular + Node,MEAN 技術棧代表著一種完全現代的 Web 開發方法:一種語言執行在應用程式的所有層次上,從客戶端到伺服器,再到持久層。藉助JavaScript的測試框架,比如MochaJS、JasmineJS 和 KarmaJS,可以為自己的 MEAN 應用程式編寫深入而又全面的測試套件,據說MEAN有取代LAMP/LNMP的的趨勢,但還需保持謹慎。

引擎的差異

正像Java 那樣,儘管又著虛擬機器規範,但各個JVM的實現還是有著些許的不同,JavaScript 也是如此。JavaScript各引擎中同樣存在著少量的限制,例如:

  • 字串常量中允許的最大字元數
  • 作為引數傳遞到函式中的資料大小(棧大小)
  • 函式宣告中的引數個數
  • 函式呼叫鏈的最大長度
  • 以阻塞方式在瀏覽器中執行的最大時間
  • 變數名的最大長度

儘管如此,JavaScript 在瀏覽器中的表現還是基本上可信的。

從軟體到硬體

實際上,JavaScript已經嵌入到了從機器人到各種家電等各種各樣的裝置中。這裡隆重推薦我非常敬佩的好友——周愛民老師,他在Ruff(南潮資訊科技)做的事情就是JavaScript 在物聯網上的進一步應用。

Ruff 是一個可以讓開發者實現敏捷開發智慧硬體的系統平臺。它包含了Ruff SDK、Ruff OS,Rap Registry等。從技術上講,Ruff 是一個 JavaScript 執行時,專為硬體開發而設計。Ruff 對硬體進行了抽象,使用了基於事件驅動、非同步 I/O 的模型,使硬體開發變得輕量而且高效。硬體抽象層,使得操作硬體猶如普通程式庫,降低了硬體領域進入門檻。

Ruff 為開發者提供了完善的開發服務。從專案生產、軟體包管理、應用管理、外設管理到韌體管理等一系列現代軟體開發方式,PC 端完成開發,無需燒板子,提升開發者的開發效率。Ruff 還提供了完善的測試框架,支援 assert、test、mock 等模組,在開發機上測試邏輯,硬體測試也能 TDD。

官網(ruff.io) 上給出的示例如下:

$.ready(function() { $('#led-0').turnOn();});

開啟電路板上的一個LED 燈,就是如此的簡單。目前,一個 Ruff 硬體同時只能執行一款 Ruff 應用,它將擁有自己獨立的程式,著可能也受到JavaScript自身的限制吧。

關注效能

效能是全棧關注的一個重要維度,那句“過早優化是萬惡之源”實際上是我們對高德納先生的斷章取義,原文是這樣的:

我們應該在例如97%的時間裡,忘掉小處的效率;過早優化是萬惡之源。但我們不應該錯過關鍵的3%中的機會。

實際上是非關鍵路徑上的優化是萬惡之源,問題在於如何確定我們的程式碼是否在關鍵路徑上。不論節省的時間多麼少,花費在關鍵路徑上的效能優化都是值得的。

對於效能優化工具,用於JavaScript原始碼壓縮有 google Closure complier,packer,YUI compressor,JSmin等。頁面的效能優化工具有YSlow 和Page Speed等。實際上,任何有意義且可靠的效能測試都是基於統計學上的合理實踐。 就JavaScript 程式碼本身的效能而言,benchmarkjs 是一個很好的工具,而jsperf.com 提供了對JavaScript 執行環境的效能測試。

總之,JavaScript 是一個具有強大生命力的語言,前端框架更是日新月異,從Angular,Vue,到React, 乃至React Native,給人以目不暇接的感覺,但是,老碼農覺得基礎認識還是非常必要的,勿在浮沙築高塔。

參考閱讀

https://developer.mozilla.org/en-US/docs/Web/JavaScript
https://tc39.github.io/ecma262/#sec-global-object

相關文章