
這次的內(nèi)容太多沒有研究過了,只用過很少的一部分,所以僅僅作為初步的了解來進行總結(jié)。
1. 性能提升
1.1 WebWorker
WebWorker是HTML5提供的方案,是基于多線程的Javascript。如我們所知,Javascript是單線程運行的,自身并不具備多線程能力,但是Javascript的宿主環(huán)境,卻可以包含多個Javascript引擎,多個Javascript引擎的并行執(zhí)行,就可以實現(xiàn)多線程的能力。一般情況下會有一個主UI線程以及一個或多個Worker線程。
但是多線程運行可能導(dǎo)致數(shù)據(jù)共享會存在問題,所以WebWorker中主UI線程的數(shù)據(jù)是不能和Worker線程直接進行共享,他們之間的數(shù)據(jù)傳遞的方式進行數(shù)據(jù)溝通。
1.1.1 Worker
用一個例子來說明如何創(chuàng)建和使用一個Worker
main.js
// 創(chuàng)建一個Worker
var w1 = new Worker('w1.js');
// 添加message事件監(jiān)聽
w1.addEventListener('message', function(evt){
// 獲取到子線程處理后返回的結(jié)果
})
// 發(fā)送數(shù)據(jù)到Worker中
w1.postMessage('patrick');
w1.js
// 添加message事件監(jiān)聽
addEventListener('message', function(evt){
// 獲取主線程中post的數(shù)據(jù),這里可以理解為拿到'patrick'
// 調(diào)用方法處理數(shù)據(jù)后使用postMessage返回給主線程
postMessage('Hello, ' + evt.data);
})
Worker.js中可以通過importScripts()引入第三方的js文件,只是導(dǎo)入過程是同步阻塞的。
使用worker.terminate()方法可以終止worker線程
1.1.2 SharedWorker
使用Worker創(chuàng)建的子線程,在不同的瀏覽器tab中并不是公用的。每一個tab都會獨立創(chuàng)建一個子線程,子線程中的變量在不同tab間并不通用,可以使用SharedWorker來使子線程在多個tab間的數(shù)據(jù)共享
main.js
// 創(chuàng)建一個Worker
var w2 = new SharedWorker('w2.js');
// 添加message事件監(jiān)聽,但是是綁定在port屬性上
w2.port.addEventListener('message', function(evt){
// 獲取到子線程處理后返回的結(jié)果
})
// 發(fā)送數(shù)據(jù)到Worker中,也需要綁定在port屬性上
w2.port.postMessage('patrick');
// 開啟port
w2.port.start();
w2.js
var i = 0;
// 增加connect事件監(jiān)聽
addEventListener('connect', function(evt) {
// 獲取到port信息
var port = evt.ports[0]
// 添加message事件監(jiān)聽
port.addEventListener('message', function(evt){
// 變量i在每一個tab中共享
port.postMessage(`Hello, ${evt.data} ${i} times`);
})
// 啟動port
port.start()
})
相比于普通的Worker,SharedWorker需要綁定到某個port,并對port進行事件監(jiān)聽,同時調(diào)用port.start()啟動port
Web Worker要在Server上運行,否則Chrome會報錯給予警告,雖然網(wǎng)上有說Safari可以不用運行在Server環(huán)境中,實際嘗試過也無法運行
1.2 SIMD
SIMD(Single Instruction/Multiple Data) 單結(jié)構(gòu)多數(shù)據(jù),是一種放棄掉多線程,直接利用CPU提供的API進行位運算的一種提案,目前根據(jù)MDN網(wǎng)上的說法,這個提案已經(jīng)被廢棄,被WebAssembly取代,有興趣的可移步MDN SIMD
1.3 asm.js
一種Javascript的編程規(guī)范,主要是通過代碼規(guī)范,創(chuàng)建數(shù)據(jù)操作緩存堆,減少Javascript引擎進行的多余的操作,例如強制類型轉(zhuǎn)換,垃圾回收等,從而提高Javascript性能,可以通過插件來將現(xiàn)有js代碼進行轉(zhuǎn)換,具體信息請移步MDN asm.js
1.4 WebAssembly
Javascript中引用其他類型語言并執(zhí)行的解決方案,使用相關(guān)的API,可以在Javascript中引入C/C++等其他類型語言執(zhí)行,并不影響現(xiàn)有Javascript代碼,是目前嘗試性質(zhì)的解決方案MDN WebAssembly
關(guān)于WebAssembly會在自己學(xué)習(xí)研究之后專門寫一篇總結(jié),歡迎持續(xù)關(guān)注。
以上的1.1-1.4方案實際上均是在利用Javascript處理一些耗時或者較大數(shù)據(jù)量大問題:大數(shù)據(jù)分析,圖形解析時提供的可參考方案
1.5 尾調(diào)用
尾調(diào)用是指函數(shù)運行的最后是一個函數(shù)調(diào)用,在ES6規(guī)范中需要各Javascript引擎強制去實現(xiàn)相關(guān)的優(yōu)化。尾調(diào)用是屬于函數(shù)式編式編程的范疇。
// 尾調(diào)用
function f1() {
return f1();
}
// 非尾調(diào)用
function f2() {
return f2() + 1;
}
Javascript引擎在運行過程中,會為每一個執(zhí)行的函數(shù)創(chuàng)建一個棧幀,而對于尾調(diào)用來說,由于上一個函數(shù)已經(jīng)結(jié)束,沒有其他任何操作,所以當(dāng)前函數(shù)的棧幀會自動分配給尾調(diào)用的函數(shù),不會去創(chuàng)建新的棧幀,減小了內(nèi)存的消耗,尤其是在我們使用遞歸的時候,避免遞歸帶來的內(nèi)存溢出,使用尾調(diào)用的遞歸也叫做尾遞歸。
更詳細的了解可以參考阮一峰大神的尾調(diào)調(diào)用優(yōu)化,里面有更詳細的圖文描述和擴展。
2. 性能測試
2.1 雜談
我們當(dāng)然是希望代碼性能越優(yōu)秀越好,同樣的操作耗時越少,肯定方案是更好的,從表面上看這一點毋庸置疑,但是很多時候?qū)ψ顑?yōu)代碼追求的時候需要考慮很多因素,并不是所有的時候都要去追求最佳性能
- Javascript宿主環(huán)境不同,導(dǎo)致Javascript引擎對同一段代碼的優(yōu)化規(guī)則存在差異,所以不同環(huán)境下,同一段代碼并不能始終保持最優(yōu)
- 對于微觀代碼0.1ms和0.01ms的之間的導(dǎo)致的性能差距可能并不明顯,所以拘泥于微觀性能并沒有太多用處
- 對于性能的優(yōu)化和評定最好基于一個比較大的上下文中進行,這樣的優(yōu)化結(jié)果才更有意義
2.2 benchmark.js
通常我們?nèi)绻容^兩段代碼之間的性能,會在每段代碼運行前添加獲取一個時間startTime,在代碼運行結(jié)束后再獲取一個時間endTime,然后兩者進行差值,再比較兩段代碼之間的差值,從而得出哪一段性能更佳
var startTime = Date.now();
// Todo 一些邏輯
var endTime = Date.now();
console.log(startTime - endTime);
這樣的做法表面上來說是沒有問題的,但是考慮的因素太少,一方面由于獲取時間的過程存在消耗和精度問題,可能會影響測試結(jié)果;另一方面單次執(zhí)行結(jié)果并不能很好的說明一段代碼比另一段性能更好,可能多次運行之后結(jié)果會存在不同,雖然可以考慮添加循環(huán)來執(zhí)行多次,并使用平均值來獲取平均時間判斷,但是在統(tǒng)計學(xué)上這樣結(jié)果的可信度也不夠高。
很慶幸有大牛深諳統(tǒng)計學(xué)知識,并幫助我們創(chuàng)建了一套可信的性能統(tǒng)計代碼來幫助我們進行性能比較,也就是benchmark.js,官方有很詳細的介紹。
2.3 jsPerf.com
基于benchmark.js提供了多宿主環(huán)境,多條件下的執(zhí)行比較,相關(guān)資料也可以通過訪問官方網(wǎng)站jsPerf.com
3. 參考
《你不知道的Javascript(中卷)》
使用benchmark.js和jsPerf.com進行性能分析
阮一峰——尾調(diào)用優(yōu)化