JavaScript 來了
1995年,誕生了JavaScript語言,那一年,我剛剛從大學畢業。在今年RedMonk 推出的2017 年第一季度編程語言排行榜中,JavaScript 排第一,Java第二,Python反超 PHP 排第三,PHP 第四,C# 和 C++ 并列第五。RedMonk 排名的主要依舊是各種編程語言在 Stack Overflow 和 GitHub 上的表現,比如編程語言在 Stack Overflow 上的討論數量,在 GitHub 上的代碼量等。盡管有一定的片面性,還是說明了JavaScript 應用的廣泛性。從全棧的角度看,Javascript 是必備的一種編程語言。
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對值和引用的賦值/傳遞在語法上沒有區別,完全根據值的類型來判定。
對于對象的屬性和方法而言,全局變量和全局函數是全局對象的屬性,全局對象相當于宿主對象的根對象。需要注意是屬性的屬性中那些不可變對象的實現方式:
- 對象常量: 結合writable和configurable:false 可以創建一個真正的常量屬性
- 禁止擴張:Object.preventExtensions(..)來禁止一個對象添加新屬性并保留已有屬性
- 密封: 在 Object.seal(..) 后不能增,刪,改 該屬性
- 凍結: 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上關于各種相等性的矩陣:
語句與表達式
操作符與變量/常量等連接形成了語句和表達式,例如表達式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 的綁定規則大約是:
- 默認綁定:獨立的函數調用,嚴格模式不能將全局對象用于默認綁定
- 隱式綁定:把函數調用中的this 綁定到函數引用中的上下文對象
- 顯式綁定:通過call()和apply()方法可以直接指定this的綁定對象。其中,硬綁定是一種顯式的強制綁定,ES5中提供了內置方法Function.prototype.bind, API中調用的上下文和bind的作用一樣。
- new 綁定,構造函數只是一些使用new操作符調用的函, 使用new 來調用函數的操作過程大致如下:
- 創建一個全新的對象
- 這個新對象會被執行[[Prototype]]鏈接
- 這個新對象會綁定到函數調用的this
- 如果函數沒有返回其他對象,那么new表達式中的函數調用會自動返回這個新對象
如果同時存在多種綁定,那么綁定的優先級大致如下:
- 由new調用綁定到新創建的對象
- 由call 或者apply(或bind)調用綁定到指定的對象
- 由上下文對象調用綁定到那個上下文對象
- 默認在在嚴格模式下綁定到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的系統架構大致如下:
與瀏覽器相對應,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