亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

高性能的網(wǎng)頁開發(fā)概要

bovenson / 3061人閱讀

摘要:或者說一直以來我是缺乏開發(fā)高性能網(wǎng)頁的意識的,但是想做一個好的前端開發(fā)者,是需要在當(dāng)自己編寫的程序慢慢復(fù)雜以后還能繼續(xù)保持網(wǎng)頁的高性能的。

不知道有多少人和我一樣,在以前的開發(fā)過程中很少在乎自己編寫的網(wǎng)頁的性能?;蛘哒f一直以來我是缺乏開發(fā)高性能網(wǎng)頁的意識的,但是想做一個好的前端開發(fā)者,是需要在當(dāng)自己編寫的程序慢慢復(fù)雜以后還能繼續(xù)保持網(wǎng)頁的高性能的。這需要我們對JavaScript語句,對其運(yùn)行的宿主環(huán)境(瀏覽器等),對它的操作對象(DOM等)有更深入的理解。

什么樣的網(wǎng)頁是高性能的網(wǎng)頁?
我想一個網(wǎng)頁是否高性能主要體現(xiàn)在兩個方面,一是網(wǎng)頁中代碼真實(shí)的運(yùn)行速度,二是用戶在使用時感受到的速度,我們一項項的來討論。

提高代碼執(zhí)行的效率

想要提高代碼的執(zhí)行效率,我們首先得知道我們使用JS做不同的事情時,其執(zhí)行效率各是如何。一般說來,web前端開發(fā)中我們常做的操作主要是數(shù)據(jù)獲取和存儲,操作DOM,除此之外,我們知道JS中達(dá)到同一目的可能會有多種途徑,但其實(shí)各種途徑執(zhí)行效率并不相同,我們應(yīng)該選擇最合適的途徑

數(shù)據(jù)存儲和訪問

先說數(shù)據(jù)存儲,計算機(jī)中,數(shù)據(jù)肯定是存在于內(nèi)存之中,但是訪問到具體內(nèi)存所在的位置卻有不同的方法,從這個角度看JS中有四種基本的數(shù)據(jù)存取位置

- 字面量:只代表自身,不存儲在特定位置
* 本地變量:使用關(guān)鍵字var 存儲的數(shù)據(jù)存儲單元
* 數(shù)組元素:存儲在JavaScript的數(shù)組對象內(nèi)部,以數(shù)字為索引
* 對象成員:存儲在JavaScript對象內(nèi)部,以字符串為索引

不同存儲方式的訪問速度
其實(shí)很容易就可以理解,訪問一個數(shù)據(jù)所經(jīng)歷的層級越少,其訪問速度越快,這樣看來訪問字面量和局部變量速度最快,而訪問數(shù)組元素和對象成員相對較慢;

從數(shù)據(jù)存儲和訪問角度來看,提升效率的核心在于存儲和訪問要直接,不要拐彎抹角。我們以原型鏈作用域為例來說明如何優(yōu)化。

原型鏈
對象的原型決定了實(shí)例的類型,原型鏈可以很長,實(shí)例所調(diào)用的方法在原型鏈層級中越深則效率越低。因此也許需要我們保證原型鏈不要太長,對于經(jīng)常需要使用到的方法或?qū)傩?,盡量保證它在原型鏈的淺層。

作用域(鏈)
作用域也是JavaScript中一個重要的概念,一般說來變量在作用域中的位置越深,訪問所需的時間就越長。
局部變量存在于作用域鏈的起始位置,其訪問速度比訪問跨作用域變量快,而全局變量處于作用域鏈的最末端,其訪問速度也是最慢的。
一般說來JavaScript的詞法作用域在代碼編譯階段就已經(jīng)確定,這種確定性帶來的好處是,代碼在執(zhí)行過程中,能夠預(yù)測如何對變量進(jìn)行查找,從而提高代碼運(yùn)行階段的執(zhí)行效率。我們也知道JavaScript中有一些語句是會臨時改變作用域的,比如說with,eval,try...catch...中的catch子句,使用它們會破壞已有的確定性,從而降低代碼執(zhí)行效率,因此這些語句應(yīng)該小心使用。

從數(shù)據(jù)的存儲和訪問角度,可以從以下角度進(jìn)行優(yōu)化:

- 我們應(yīng)該盡量少用嵌套的對象成員,多層嵌套時會明顯影響性能,如果實(shí)在要使用(尤其是多次使用);
- 我們可以通過把常用的對象成員,數(shù)組元素,跨域變量保存在局部變量中,再使用來改善JavaScript性能。
DOM編程

通過DOM API,利用JavaScript我們有了操作網(wǎng)頁上的元素的能力,這也使得我們的網(wǎng)頁活靈活現(xiàn)??墒沁z憾的是DOM編程是性能消耗大戶,它天生就慢,究其原因,是因?yàn)樵跒g覽器中DOM渲染和JavaScript引擎是獨(dú)立實(shí)現(xiàn)的,不同的瀏覽器實(shí)現(xiàn)機(jī)制不同,具體可見下表。

類別 IE Safari Chrome Firefox
JS引擎 jscript.dll JavaScriptCore V8 SpiderMonkey(TraceMonkey)
DOM和渲染 mshtml.dll (Trident) Webkit中的WebCore Webkit中的WebCore Gecko

DOM渲染和JavaScript引擎的獨(dú)立實(shí)現(xiàn)意味著這兩個功能好像位于一條大河的兩岸,二者的每次交互都要渡過這條河,這很明顯會產(chǎn)生不少額外的性能消耗,這也是我們常說操作DOM是非常消耗性能的原因。
從這個角度我們想到,想要減少DOM操作帶來的性能的消耗,核心我們要做的是減少訪問和修改等DOM操作。
我們可以從以下幾方面著手DOM編程的優(yōu)化:

把運(yùn)算盡量留在ECMAScript這一端處理,在實(shí)際開發(fā)過程中我們應(yīng)該對DOM不做非必要的操作,在獲得必要的值以后,可以把這些值存儲在局部變量之中,純使用JS對該值進(jìn)行相關(guān)運(yùn)算,直接用最后的結(jié)果來修改DOM元素,可能這么說來并不是很直觀,但是回頭看看我們的項目,我們有沒有過在循環(huán)或者會多次使用的函數(shù)中,每次都重新獲取某個不變的DOM相關(guān)的值;

小心處理HTML集合:

HTML集合是包含了 DOM節(jié)點(diǎn)引用的類數(shù)組對象,類似于以下方法,獲取的都是這種HTML集合:
document.getElementsByName();
document.getElementByClassName();
document.getElementByTagName();
這種類數(shù)組對象具備普通數(shù)組的一些特點(diǎn),比如擁有length屬性,可以通過數(shù)字索引的方式訪問到對應(yīng)的對象,但是卻并沒有數(shù)組的pushslice等方法,重點(diǎn)在于在使用過程中這個集合實(shí)時連系著底層的文檔,每次訪問這種HTML集合,都會重復(fù)執(zhí)行查詢的過程,造成較大的性能消耗;下面是一個典型的多次訪問的例子:

    var alldivs = document.getElementsByTagName("div");
    for(var i = 0;i

上述代碼是一個死循環(huán),在每次循環(huán)的時候alldivs都會重新遍歷DOM,獲得更新,產(chǎn)生了不必要的性能消耗;
合理的使用方式應(yīng)該是,把集合的長度緩存到一個變量中,在迭代中使用這個局部變量。如果需要經(jīng)常操作集合元素,我們可以把這個集合拷貝到一個數(shù)組中,操作數(shù)組比操作HTML集合性能高;使用下述方法可以把一個HTML集合拷貝為普通的數(shù)組:

// 拷貝函數(shù)
function toArray(coll){
    for(var i=0,a=[],len=coll.length;i

JS代碼的運(yùn)行需要宿主環(huán)境,就web前端來說,這個環(huán)境就是我們的瀏覽器,一般說來瀏覽器會對一些常見操作進(jìn)行一定的優(yōu)化,使用優(yōu)化后的API,性能更高,我們應(yīng)該盡量使用那這些優(yōu)化過的API,對現(xiàn)代瀏覽器中的DOM操作來說,有以下一些優(yōu)化后的API:

優(yōu)化后 原始
children childnodes
childElementCount children.length
firstElementChild firstChild
lastElementChild lastChild
nextElementSibling nextSibling
previousElementSibling previousSibling
document.querySelector() document.getElemnetByClassName…

減少重繪與重排
重排和重繪也是大家經(jīng)常聽到的概念:

重排:DOM變化影響了元素的幾何屬性(比如改變邊框?qū)挾龋?,引起其它元素的位置也因此受到影響,瀏覽器會使得渲染樹中受到影響的部分失效,并重新構(gòu)建渲染樹;
重繪:重排完成后,瀏覽器會重新繪制受影響的部分到屏幕中(并不是所有的DOM變化都會影響幾何屬性,例如改變一個元素的背景色并不會影響它的寬和高);

重排和重繪都是代價很高的操作,會直接導(dǎo)致Web應(yīng)用程序的UI反應(yīng)遲鈍,應(yīng)該盡量減少這類過程的發(fā)生。一般說來下面的操作會導(dǎo)致重排:

- 添加刪除可見的DOM元素;
* 元素位置改變;
* 元素尺寸改變(`padding,margin,border,width,height...`)
* 內(nèi)容改變;(文本內(nèi)容,或圖片被另外一個不同尺寸的圖片替代);
* 頁面渲染器初始化;
* 瀏覽器窗口尺寸改變;

每次重排都會產(chǎn)生計算消耗,大多數(shù)瀏覽器會通過隊列化修改,批量執(zhí)行來優(yōu)化重排過程,減少性能消耗。但是也有部分操作會強(qiáng)制刷新隊列,要求重排任務(wù)立即執(zhí)行。如下:

- `offsrtTop`,`offsetLeft`,`offsetWidth`,`offsetHeight`;
* `scrollTop`,`scrollLeft`,`scrollWidth`,`scrollHeight`;
* `clientTop`,`clientLeft`,`clientWidth`,`clientHeight`;
* `getComputedStyle()`

上述操作都要求返回最新的布局信息,瀏覽器不得不執(zhí)行渲染隊列中待處理的任務(wù),觸發(fā)重排返回正確的值。通過緩存布局信息可以減少重排次數(shù),這一點(diǎn)是我一直忽略的一點(diǎn),曾經(jīng)多次在循環(huán)或多次調(diào)用的函數(shù)中直接使用上述操作獲取距離;

總的來說最小化重繪和重排的核心是合并多次對DOM和樣式的修改,然后一次處理掉,主要有以下幾種方法:

- 使用cssText屬性(適用于動態(tài)改變)
var el = document.getElementById(‘mydiv’);
// 下面這種寫法可以覆蓋已存在的樣式信息
el.style.cssText = ‘border-left:1px;border-right:2px;padding:5px’;
// 下面這種寫法可以把新屬性附加在cssTest字符串后面
el.style.cssText += ‘;border-left:1px;’;
- 對于不依賴于運(yùn)行邏輯和計算的情況,直接改變CSS的`class`名稱是一種比較好的選擇
- 批量修改DOM:操作如下
    - 使元素脫離文檔流;
        * 隱藏元素,應(yīng)用修改,重新顯示;
        * 使用文檔片段,在當(dāng)前DOM外構(gòu)建一個子樹,再把它拷貝到文檔;
        * 將原始元素拷貝到一個脫離文檔的節(jié)點(diǎn)中,修改副本,完成后替換原始元素;
    * 對其應(yīng)用多重改變;
    * 把元素帶回文檔中;
在這個過程中只有第一步和第三步會觸發(fā)重排,可參加下述代碼。
var fragment = document.createDocumentFragment();//一個輕量級的document對象
// 組裝fragment...(省略)
// 加入文檔中
document.getElementById(‘mylist’).appendChild(fragment);

var old = document.getElementById(‘mylist’);
var clone = old.cloneNode(true);
// 對clone進(jìn)行處理
old.parentNode.replaceChild(clone,old);
- 對動畫元素可進(jìn)行如下優(yōu)化
    - 對動畫元素使用絕對定位,將其脫離文檔流;
    * 讓元素動起來,懂的時候會臨時覆蓋部分頁面(不會產(chǎn)生重排并繪制頁面的大部分內(nèi)容);
    * 當(dāng)動畫結(jié)束時恢復(fù)定位,從而只會下移一次文檔的其它元素;

此外如果頁面中對多個元素綁定事件處理器,也是會影響性能的,事件綁定會占用處理時間,瀏覽器也需要跟蹤每個事件處理器,會占用更多的內(nèi)存。針對這種情況我們可以采用使用事件委托機(jī)制:也就是說把事件綁定在頂層父元素,通過e.target獲取點(diǎn)擊的元素,判斷是否是希望點(diǎn)擊的對象,執(zhí)行對應(yīng)的操作,幸運(yùn)的是現(xiàn)在的很多框架已經(jīng)幫我們做了這一點(diǎn)(React中的事件系統(tǒng)就用到了事件委托機(jī)制)。

選擇合適的JavaScript語句

殊途同歸付出的代價卻不一樣,和任何編程語言一樣,代碼的寫法和算法會影響運(yùn)行時間。而我們寫的最多的語句是迭代判斷。

選擇合適的迭代語句

JavaScript提供了多種迭代方法:

四種循環(huán)類型:

for;

while;

do…while循環(huán);

for…in…循環(huán);

就四種循環(huán)類型而言,前三種循環(huán)性能相當(dāng),第四種for...in...用以枚舉任意對象的屬性名,所枚舉的屬性包括對象實(shí)例屬性及從原型鏈中繼承來的屬性,由于涉及到了原型鏈,其執(zhí)行效率明顯慢于前三種循環(huán)。就前三種而言影響性能的主要因素不在于選擇哪種循環(huán)類型,而在于每次迭代處理的事務(wù)迭代的次數(shù)

減少每次迭代的工作量
很明顯,達(dá)到同樣的目的,每次迭代處理的事務(wù)越少,效率越高。舉例來說

// 普通循環(huán)體
for(var i=0;i

普通循環(huán)體每次循環(huán)都會有以下操作:

1. 在控制條件中查找一次屬性(`items.length`);
2. 在控制條件中執(zhí)行一次數(shù)值比較(`i

優(yōu)化后的循環(huán)體每次迭代會有以下操作:

1. 一次控制條件中的比較(`i==true`);
2. 一次減法操作(`i—`);
3. 一次數(shù)組查找(`item[i]`);
4. 一次函數(shù)調(diào)用(`process(item[i])`);

明顯優(yōu)化后的迭代操作步驟更少,如果迭代次數(shù)很多,多次這樣小的優(yōu)化就能節(jié)省不錯的性能,如果process()函數(shù)本身也充滿了各種小優(yōu)化,性能的提升還是很可觀的;

減少迭代次數(shù)
每次迭代其實(shí)是需要付出額外的性能消耗的,可以使用Duff’s Device方法減少迭代次數(shù),方法如下;

    // credit:Jeff Greenberg
var i = items.length%8;
whild(i){
    process(items[i--]);
}

i = Math.floor(items.length/8);

// 如果循環(huán)次數(shù)超過1000,與常規(guī)循環(huán)結(jié)構(gòu)相比,其效率會大幅度提高
while(i){
    process(items[i--]);
    process(items[i--]);
    process(items[i--]);
    process(items[i--]);
    process(items[i--]);
    process(items[i--]);
    process(items[i--]);
    process(items[i--]);
}

‘Duff’s Device’循環(huán)體展開技術(shù),使得一次迭代實(shí)際上執(zhí)行了多次迭代的操作。如果迭代次數(shù)大于1000次,與常規(guī)循環(huán)結(jié)構(gòu)相比,就會有可見的性能提升。

基于函數(shù)的迭代

forEach();

map();

every();

等…

基于函數(shù)的迭代是一個非常便利的迭代方法,但它比基于循環(huán)的迭代要慢一些。每個數(shù)組項都需要調(diào)用外部方法所帶來的開銷是速度慢的主要原因。經(jīng)測試基于函數(shù)的迭代比基于循環(huán)的迭代慢八倍,選擇便利還是性能這就是我們自己的選擇了;

選擇合適的條件語句

條件表達(dá)式?jīng)Q定了JavaScript運(yùn)行流的走向,常見的條件語句有if…else...,switch...case...兩種:
switch語句的實(shí)現(xiàn)采用了branch table(分支表)索引進(jìn)行優(yōu)化,這使得switch...case是比if-else快的,但是只有條件數(shù)量很大時才快的明顯。這兩個語句的主要性能區(qū)別是,當(dāng)條件增加時,if...else性能負(fù)擔(dān)增加的程度比switch大。具體選用那個,還是應(yīng)該依據(jù)條件數(shù)量來判斷。

如果選用if...else,也有優(yōu)化方法,一是把最可能出現(xiàn)的條件放在最前面,二是可以通過嵌套的if...else語句減少查詢時間;

在JavaScript中還可以通過查找表的方式來獲取滿足某條件的結(jié)果。當(dāng)有大量離散值需要測試時,if...elseswitch比使用查找表慢很多。查找表可以通過數(shù)組模擬使用方法如下:

var results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9];

return results[value];

從語言本身的角度來看,做到上述各點(diǎn),我們的代碼性能就會有比較大的提升了,但是代碼性能的提升,只是代碼執(zhí)行本身變快了,并不一定是用戶體驗(yàn)到的時間變短了,一個好的web網(wǎng)頁,還應(yīng)該讓用戶感覺到我們的網(wǎng)頁很快。

更好的交互體驗(yàn)

人類對時間的感覺其實(shí)是不準(zhǔn)確的,考慮兩種場景,

一種場景是,一段代碼執(zhí)行10s,10s后所有內(nèi)容一下子全部顯示出來;
另外一種場景是,總共需要執(zhí)行12s,但是每秒都在頁面上添加一些內(nèi)容,主體內(nèi)容先添加,次要內(nèi)容后添加;

上述兩種場景給人的感覺完全不一樣。大部分人會覺得后面那個12s時間更短。

用戶感覺到的時間的長短取決于用戶與網(wǎng)頁交互時收到的反饋,一般說來100ms是個時間節(jié)點(diǎn),如果界面沒在100ms內(nèi)對用戶的行為作出反饋,用戶就會有卡頓的感覺,開發(fā)過手機(jī)版的網(wǎng)頁的童鞋都知道,當(dāng)你觸發(fā)手機(jī)端默認(rèn)的onClick事件時,有些瀏覽器出于要確認(rèn)你是否想要雙擊,會在單擊事件觸發(fā)300ms后,才執(zhí)行相應(yīng)的操作,用戶在這時候會有明顯卡頓的感覺的。為了達(dá)到更好的交互體驗(yàn),我們可以從腳本加載時機(jī),不阻塞瀏覽器的UI線程,高效使用Ajax,合理構(gòu)建和部署JavaScript應(yīng)用等方面進(jìn)行優(yōu)化:

加載和執(zhí)行

我們都知道多數(shù)瀏覽器使用單一進(jìn)程來處理UI刷新和JavaScript腳本執(zhí)行,當(dāng)執(zhí)行較大的JavaScript腳本時會阻塞UI的刷新。這是一種非常不好的體驗(yàn)。所有在頁面初始化時期,我們都會把JavaScript放在標(biāo)簽之前。我們也可以采用動態(tài)加載的方式,無論在何時啟動,文件的下載和執(zhí)行過程都不會阻塞頁面其他進(jìn)程,一種常見的動態(tài)加載方式如下,當(dāng)然我們也可以使用AJAX進(jìn)行動態(tài)加載。

    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src="file1.js";
    document.getElementsByTagName("head")[0].appendChild(script);
    // 無論在何時啟動下載,文件的下載和執(zhí)行過程不會阻塞頁面其他進(jìn)程
    // 通過檢測load事件可以獲得腳本加載完成時的狀態(tài)
構(gòu)建快速響應(yīng)的用戶界面

我們已經(jīng)知道,當(dāng)JavaScript代碼執(zhí)行時,用戶界面其實(shí)是屬于鎖定狀態(tài)的,管理好JavaScript的運(yùn)行時間對Web應(yīng)用的性能至關(guān)重要。

用來執(zhí)行JavaScript和更新用戶界面的進(jìn)程通常稱為“瀏覽器UI線程”。其工作基于一個簡單的任務(wù)隊列系統(tǒng),具體原理是所有的前端任務(wù)會被保存在任務(wù)隊列中直到進(jìn)程空閑,一旦空閑,隊列中的下一個任務(wù)就會被提取出來并運(yùn)行。這些任務(wù)可能是一段待執(zhí)行的JavaScript代碼,UI更新(重排重繪等)。
一般瀏覽器有自己的JavaScript長時間運(yùn)行限制,不同瀏覽器限制的時間不同,有的是5~10s,有的是運(yùn)行超過500萬行語句時提醒;達(dá)到限制時間后對頁面的處理方式也不同,有的直接導(dǎo)致瀏覽器崩潰,有的會發(fā)出明顯可見的警告信息。

前面我們已經(jīng)討論過如果等待時間不超過100ms,一般人就會覺得操作是連貫的。這給我們提供了一個思路,我們的一段JavaScript代碼的執(zhí)行時間如果不超過100ms,操作起來感覺就是連貫的。

通過分隔JavaScript代碼的執(zhí)行,我們可以讓JavaScript的執(zhí)行時間不超過100ms,主要途徑主要有兩種:
使用定時器讓出時間片段,使用web worker

通過定時器是分割UI線程的時間片段

JavaScript中有兩種不同的定時器setTimeout()setInterval(),它們都接受兩個參數(shù),第一個參數(shù)是一個函數(shù),第二個參數(shù)是一段時間。當(dāng)定時器被調(diào)用時,它們會告訴JavaScript引擎在等待我們所定的時間后,添加一個JavaScript任務(wù)到UI線程中。
定時器的意義在于,每當(dāng)我們創(chuàng)建一個定時器,UI線程就會暫停,并從一個任務(wù)切換到下一個任務(wù),定時器也會重置所有相關(guān)的瀏覽器限制,包括長時間運(yùn)行腳本限制,調(diào)用棧的限制。

我們也應(yīng)該明確,定時器的延遲時間并非總是精確的,每次相差大約幾毫秒,所以定時器不太適合拿來計時(比如說在windows系統(tǒng)中定時器分辨率為15ms),一般建議的延遲的最小值為25ms,以確保至少有15ms的延遲。

我們通過下面的代碼看看如何利用定時器無阻塞的處理大數(shù)組

var todo = items.concat();//items是原始的大數(shù)組

setTimeout(function(){
    // 取出數(shù)組的下個元素并進(jìn)行處理
    process(todo.shift());

    // 如果還有需要處理的元素,創(chuàng)建另一個定時器,再25ms執(zhí)行一次
    if(todo.length>0){
        // arguments.callee指向當(dāng)前正在運(yùn)行的匿名函數(shù)
        setTimeout(arguments.callee,25);
    }else{
        // 如果所有的數(shù)組都被處理了,執(zhí)行callback()函數(shù)
        callback(items);
    }
},25);

利用定時器可以按照下述方法分割運(yùn)行時間過長的函數(shù)

//原函數(shù)
function saveDocument(id){
    //保存文檔
    openDocument(id);
    writeText(id);
    closeDocument(id);

    //將信息更新到界面
    updateUI(id);
}

// 使用定時器分割任務(wù)的方法
function saveDocument(id){
    var tasks=[openDocument,writeText,closeDocument,updateUI];

    setTimeout(function(){
        var task = tasks.shift();
        task(id);

        if(tasks.length>0){
            setTimeout(arguments.callee,25);
        }
    },25);
}

定時器把我們的任務(wù)分解成了一個個的不致于阻塞的片段,雖然總的執(zhí)行時間可能是變長了,但是會讓我們的交互體驗(yàn)發(fā)生翻天覆地的變化,使用得當(dāng),我們可以避免大部分完全卡住的情況了。不過定時器的使用也有一個限制,我們應(yīng)該保證同一時間只有一個定時器的存在,防止因?yàn)槎〞r器使用過度導(dǎo)致的性能問題。

WebWorker給Web應(yīng)用帶來的巨大性能潛力提升

WebWorker是HTML5新提供的一組API,它引入了一個接口,能使代碼運(yùn)行且不占用瀏覽器UI線程的時間,這里只做簡單介紹,具體使用請查看相關(guān)文檔:
一個WebWorker其實(shí)是JavaScript特性的一個子集,其運(yùn)行環(huán)境由以下部分組成:

- 一個`navigator`對象,包含四個屬性:`appName`,`appVeision`,`userAgent,platform`;
* 一個`location`對象(與`window.location`相同,不過所有屬性都是只讀的);
* 一個`self`對象,指向全局`worker`對象;
* 一個`importScript()`方法,用來加載`Worker`所用到的外部JavaScript文件;
* 所有的ECMAScript對象,諸如:`Object,Array,Date`等;
* `XMLHttpRequest`構(gòu)造器;
* `setTimeout()`和`setInterval()`方法;
* 一個`close()`方法,它可以立即停止Worker運(yùn)行;

主網(wǎng)頁與Worker相互通信的方法
Worker與網(wǎng)頁代碼通過事件接口進(jìn)行通信。主網(wǎng)頁和Worker都會通過postMessage()傳遞數(shù)據(jù)給對方;使用onmessage事件處理器來接收來自對方的信息;

// 主頁面
var worker = new Worker("code.js");
worker.onmessage = function(event){
    alert(event.data);
};
worker.postMessage("Nicholas");

//code.js內(nèi)部的代碼
self.onmessage = function(event){
    self.postMessage("Hello,"+ event.data+"!");
}

相互傳遞的原始值可以是字符串,數(shù)字,布爾值,null,undefined,也可以ObjectArray的實(shí)例。

加載外部文件的方法
在一個web worker中可以使用importScript()方法加載外部JavaScript文件,該方法接收一個或多個url作為參數(shù),調(diào)用過程是阻塞式的,所有文件加載并執(zhí)行完成以后,腳本才會繼續(xù)運(yùn)行。但并不會影響UI響應(yīng)。
加載允許異步發(fā)送和接收數(shù)據(jù)??梢栽谡埱笾刑砑尤魏晤^信息和參數(shù),并讀取服務(wù)器返回的所有頭信息,以及響應(yīng)文本。
importScripts(‘file1.js’,’file2.js’);

WebWorker的實(shí)際應(yīng)用

- 適合處理純數(shù)據(jù),或者與UI無關(guān)的長時間運(yùn)行腳本;
* 編碼,解碼大字符串;
* 復(fù)雜數(shù)學(xué)運(yùn)算;
* 大數(shù)組排序;

總的說來,Web應(yīng)用越復(fù)雜,積極主動管理UI線程越重要,復(fù)雜的前端應(yīng)用更應(yīng)該合理使用定時器分割任務(wù)或使用WebWorker進(jìn)行額外計算從而不影響用戶體驗(yàn)。

使用Ajax

Ajax是高性能JavaScript的基礎(chǔ),它通過異步的方式在客戶端和服務(wù)器端之間傳輸數(shù)據(jù),避免頁面資源一窩蜂的下載,從而起到防止阻塞,提高用戶體驗(yàn)的效果,在使用時我們應(yīng)該選擇最合適的傳輸方式和最有效的數(shù)據(jù)格式。

傳輸方式

數(shù)據(jù)請求方式
異步從服務(wù)器端獲取數(shù)據(jù)有多種方法,常用的請求有以下三種:

XHR
這是目前最常用的技術(shù),可以在請求中添加任何頭信息和參數(shù),讀取服務(wù)器返回的所有頭信息以及響應(yīng)文本。

這種方法的缺點(diǎn)在于不能使用XHR從外域請求數(shù)據(jù),從服務(wù)器傳回的數(shù)據(jù)會被當(dāng)作字符串或者XML對象,處理大量數(shù)據(jù)時會很慢。經(jīng)GET請求的數(shù)據(jù)會被緩存起來,如果需要多次請求同一數(shù)據(jù)的話,使用GET請求有助于提升性能,當(dāng)請求的URL加上參數(shù)的長度接近或超過2048個字符時,才應(yīng)該用POST獲取數(shù)據(jù)。

XHR的使用可見以下示例:

var url = ‘/data.php’;
var params = [
    ‘id=123123’,
    ‘limit = 20’,
];

var req = new XMLHttpRequest();

req.onreadystatechange = function(){
    if(req.readyState===4){
        var responseHeader = req.getAllResponseHeaders(); // 獲取響應(yīng)頭信息
        var data = req.responseText; // 獲取數(shù)據(jù)
        // 數(shù)據(jù)處理相關(guān)程序
    }
}

req.open(‘GET’,url+’?’+params.join(‘&’),true);
req.setRequestHeader(‘X-Requested-With’,’XMLHttpRequest’);// 設(shè)置請求頭信息
req.send(null); //發(fā)送一個請求

動態(tài)腳本注入
這種技術(shù)克服了XHR的最大限制,它能跨域請求數(shù)據(jù)。但是這種方法也有自己的限制,那就是提供的控制是有限的,

不能設(shè)置請求的頭信息;

只能使用GET,不能POST;

不能設(shè)置請求的超時處理和重試;

不能訪問請求的頭信息。
這種請求的請求結(jié)果必須是可執(zhí)行的JavaScript代碼。所以無論傳輸來的什么數(shù)據(jù),都必須封裝在一個回調(diào)函數(shù)中。盡管限制諸多,但是這項技術(shù)的速度非常快。動態(tài)腳本注入的使用方法如下:

var scriptElement = document.createElement("script");
scriptElement.src = "http://.../lib.js";
document.getElementsByTagName("head")[0].appendChild(scriptElement);

function jsonCallback(jsonString){
    var data = eval("("+jsonString+")")
}

jsonCallback({"state":1,"colors":["#fff","#000","#ff0000"]});

multipart XHR
這種方法允許客戶端只用一個HTTP請求就從服務(wù)器端向客戶端傳送多個資源,他通過在服務(wù)器端將資源(CSS文件,HTML片段,JavaScript代碼或base64編碼的圖片)打包為一個由雙方約定的字符串分割的長字符串并發(fā)送給客戶端;然后用JavaScript代碼處理這個長字符串,并根據(jù)它的mime-type類型和傳入的其它“頭信息”解析出每個資源。

基于這種方法,我們可以通過監(jiān)聽readyState為3的狀態(tài)來實(shí)現(xiàn)在每個資源收到時就立即處理,而不是等待整個響應(yīng)消息完成。

此技術(shù)最大的缺點(diǎn)是以這種方式獲得的資源不能被瀏覽器緩存。不過在某些情況下MXHR依然能顯著提升頁面的整體性能:

- 頁面中包含了大量其它地方用不到的資源比如圖片;
* 網(wǎng)站在每個頁面中使用一個獨(dú)立打包的JavaScript或CSS文件用以減少HTTP請求;

HTTP請求是Ajax中最大的瓶頸之一,因此減少HTTP請求的數(shù)量也許明顯的提升整個頁面的性能。

數(shù)據(jù)發(fā)送方式
主要有兩種數(shù)據(jù)發(fā)送方法

-     `XHR`
- 信標(biāo)`beacons`

XHR我們較熟悉,使用示例如下:

var url = ‘/data/php’;
var parms = [
    ‘id=934345’,
    ‘limit=20’
]

var req = new XMLHttpRequest();

req.onerror = function(){
    //出錯
};

req.onreadystatechange = function(){
    if(req.readyState==4){
        // 成功
    }
}

req.open(‘POST’,url,true);
req.setRequestHeader(‘Content-Type’,’application/x-www-form-urlencoded’);
req.setRequestHeader(‘Content-Length’,params.length);
req.send(params.join(‘&’));

需要注意的是,使用XHR發(fā)送數(shù)據(jù)時,GET方式更快,對少量數(shù)據(jù)而言,一個GRT請求往服務(wù)器只發(fā)送一個數(shù)據(jù)包。而一個POST請求至少發(fā)送兩個數(shù)據(jù)包,一個裝載頭信息,另外一個裝載POST正文,POST適合發(fā)送大量數(shù)據(jù)到服務(wù)器的情況。

Beacons技術(shù)類似于動態(tài)腳本注入。
Beacons技術(shù)具體做法為使用JavaScript創(chuàng)建一個新的Image對象,并把src屬性設(shè)置為服務(wù)器上腳本的URL,該URL包含了我們要通過GET傳回的鍵值對數(shù)據(jù)。實(shí)際上并沒有創(chuàng)建img元素或把它傳入DOM。

var url = ‘/status_tracker.php’;
var params = [
    ‘step=2’,
    ‘time=1248027314’
];

(new Image()).src = url + ‘?’ + params.join(‘&’);

beacon.onload = function(){
    if(this.width==1){
        // 成功
    }else if(this.width==2){
        // 失敗,請重試并創(chuàng)建另一個信標(biāo)
    }
};

beacon.onerror = function(){
    // 出錯,稍后重試并創(chuàng)建另一個信標(biāo)
}

這種方法最大的優(yōu)勢是可以發(fā)送的數(shù)據(jù)的長度被限制得非常小。Beacons技術(shù)是向服務(wù)器回傳數(shù)據(jù)最快且最有效的方式。唯一的缺點(diǎn)是能接收到的響應(yīng)類似是有限的。

數(shù)據(jù)格式

說完傳輸方式,我們再討論一下傳輸?shù)臄?shù)據(jù)格式:
考慮數(shù)據(jù)格式時,唯一需要比較的標(biāo)準(zhǔn)是速度。
沒有那種數(shù)據(jù)格式會始終比其它格式更好。優(yōu)劣取決于要傳輸?shù)臄?shù)據(jù)以及它在頁面上的用途,有的數(shù)據(jù)格式可能下載速度更快,有的數(shù)據(jù)格式可能解析更快。常見的數(shù)據(jù)格式有以下四種:

XML
優(yōu)勢:極佳的通用性(服務(wù)端和客戶端都能完美支持),格式嚴(yán)格,易于驗(yàn)證;

缺點(diǎn):極其冗長,每個多帶帶的數(shù)據(jù)片段都依賴大量結(jié)構(gòu),有效數(shù)據(jù)比例非常低,XML語法有些模糊,解析XML要占用JavaScript程序員相當(dāng)部分的精力。

JSON
優(yōu)勢:體積更小,在響應(yīng)信息中結(jié)構(gòu)所占的比例小,JSON有著極好的通用性,大多數(shù)服務(wù)器端編程語言都提供了編碼和解碼的類庫。對于web開發(fā)而言,它是性能表現(xiàn)最好的數(shù)據(jù)格式;

使用注意:

- 使用`eval()`或盡量使用JSON.parse()方法解析字符串本身。
- 數(shù)組JSON的解析速度最快,但是可識別性最低。
- 使用XHR時,JSON數(shù)據(jù)被當(dāng)做字符串返回,該字符串緊接著被eval()裝換為原生對象;
- JSON-P(JSON with padding):JSON-P因?yàn)榛卣{(diào)包裝的原因略微增大了文件尺寸,但是其被當(dāng)做原生js處理,解析速度快了10倍。

HTML
優(yōu)勢:獲取后可以直接插入到DOM中,適用于前端CPU是瓶頸而帶寬非瓶頸的情況;

缺點(diǎn):作為數(shù)據(jù)結(jié)構(gòu),它即緩慢又臃腫。

自定義格式
一般我們采用字符分隔的形式。并用split()對字符串進(jìn)行分割,split()對字符串操作其實(shí)也是非??斓模ǔK茉跀?shù)毫秒內(nèi)處理包含10000+個元素的‘分隔符分隔’列表。

對數(shù)據(jù)格式的總結(jié)

- 使用JSON-P數(shù)據(jù),通過動態(tài)腳本注入使用這種數(shù)據(jù),這種方法把數(shù)據(jù)當(dāng)做可執(zhí)行JavaScript而不是字符串,解析速度極快,能跨域使用,但是設(shè)計敏感數(shù)據(jù)時不應(yīng)該使用它;
* 我們也可以采用通過字符分隔的自定義格式,可以通過使用XHR或動態(tài)腳本注入獲取對于數(shù)據(jù),并用`split()`解析。這項技術(shù)解析速度比JSON-P略快,通常文件尺寸更小。
針對Ajax的數(shù)據(jù)緩存
- 在服務(wù)器端,設(shè)置HTTP頭信息以確保響應(yīng)會被瀏覽器緩存;
- 設(shè)置頭信息后,瀏覽器只會在文件不存在緩存時才會向服務(wù)器發(fā)送Ajax請求;
* 在客戶端,將獲取到的信息存儲到本地,從而避免再次請求;

總的來說,對XHR創(chuàng)造性的使用是一個反應(yīng)遲鈍且平淡無奇的頁面與響應(yīng)快速且高效的頁面的區(qū)別所在,是一個用戶痛恨的站點(diǎn)與用戶迷戀的站點(diǎn)的區(qū)別所在,合理使用Ajax意義非凡。

合理構(gòu)建和部署JavaScript應(yīng)用

上文所述的都是在編碼過程中應(yīng)該注意的一些事項,除此之外,在代碼上線時,我們也可以做一些相關(guān)的優(yōu)化。

1. 合并多個JavaScript文件用以減少頁面渲染所需的HTTP請求數(shù);
2. JavaScript壓縮:通過剝離注釋和不必要的空白字符等,可以將文件大小減半,有多種工具可以完成壓縮:比如在線的YUI Compressor,在webpack中使用UglifyJsPlugin插件等;徹底的壓縮常做以下事情:
    * 將局部變量變?yōu)楦痰男问剑?    * 盡可能用雙括號表示法替換點(diǎn)表示法;
    * 盡可能去掉直接量屬性名的引號;
    * 替換字符串的中的轉(zhuǎn)義字符;’aaabb’被替換為”aaa’bbb”
    * 合并常量;
3. 除了常規(guī)的JavaScript壓縮,我們還可以對代碼進(jìn)行Gzip壓縮,Gzip是目前最流行的編碼方式,它通常能減少70%的下載量,其主要適用于文本包括JS文件,其它類型諸如圖片或PDF文件,不應(yīng)該使用Gzip;
4. 基于不同的瀏覽器環(huán)境,我們應(yīng)該選擇發(fā)送最合適的代碼,比如說目前iPhone版的微信內(nèi)置瀏覽器是支持解壓Gzip的而安卓端默認(rèn)不支持,那對iPhone端就可以發(fā)送xxx.js,gz文件而對安卓端發(fā)送xxx.js文件,這樣是可以提高iPhone端的webApp的加載效率的;
5. 合并,預(yù)處理,壓縮等都既可以在構(gòu)建時完成,也可以在項目運(yùn)行時完成,但是推薦能在構(gòu)建時完成的就盡量在構(gòu)建時完成;
6. 合理緩存JavaScript文件也能提高之后打開相同網(wǎng)頁的效率:
    - Web服務(wù)器通過“Expires HTTP響應(yīng)頭”來告訴客戶端一個資源應(yīng)當(dāng)緩存多長時間;
    * 移動端瀏覽器大多有緩存限制(iPhone Safari 25k),這種情況下應(yīng)該權(quán)衡HTTP組件數(shù)量和它們的可緩存性,考慮將它們分為更小的塊;
    * 合理利用HTML5 離線應(yīng)用緩存
    - 應(yīng)用更新時有緩存的網(wǎng)頁可能會來不及更新(這種情況可以通過更新版本號或開發(fā)編號來解決);
7. 使用內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN);
 CDN是在互聯(lián)網(wǎng)上按地理位置分布的計算機(jī)網(wǎng)絡(luò),它負(fù)責(zé)傳遞內(nèi)容給終端用戶。使用CDN的主要原因是增強(qiáng)Web應(yīng)用的可靠性,可拓展性,更重要的是提升性能;
8. 值得注意的是,上述很多操作是可以通過自動化處理完成的,學(xué)習(xí)相關(guān)自動化處理工具可以大大提高我們的開發(fā)效率
總結(jié)

本文從多方面敘述了web前端的優(yōu)化思路,謝謝你讀到這里,希望你有所收獲,很多知識通過多次刻意的重復(fù)就能成為自己的潛意識,希望我們在今后都能在自己的實(shí)際開發(fā)過程中,都能以效率更高的方式寫JS語句,操作DOM,我們的應(yīng)用都非常流暢,UI都不會阻塞,如果你有別的關(guān)于優(yōu)化的具體建議,歡迎一起討論。

后記

本文其實(shí)算是我讀Nicbolas C.Zakas的《高性能JavaScript》的讀書筆記,針對某個話題系統(tǒng)的讀書對我來說,是非常有好處的。系統(tǒng)的讀前端方面,計算機(jī)方面的經(jīng)典書籍也是我給自己安排的2017年最主要的任務(wù)之一,預(yù)計每月針對某本書或某幾本書關(guān)于某一個方面,寫一篇讀書筆記,本文是2017年的第一篇。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/81256.html

相關(guān)文章

  • 練習(xí)項目備選清單

    摘要:練習(xí)項目備選清單文件下載器功能概要設(shè)計實(shí)現(xiàn)新建下載功能以為基礎(chǔ)給出下載鏈接可以啟動下載任務(wù)實(shí)現(xiàn)局域網(wǎng)內(nèi)下載傳輸文件以單線程下載方式實(shí)現(xiàn)附加功能支持?jǐn)帱c(diǎn)續(xù)傳實(shí)現(xiàn)多線程下載實(shí)現(xiàn)下載參考技術(shù)套接字編程多線程編程音視頻播放器功能概要設(shè)計實(shí)現(xiàn)播放常見 練習(xí)項目備選清單 Utilities 1. 文件下載器 功能概要設(shè)計: 實(shí)現(xiàn)新建下載功能(以ftp為基礎(chǔ)) 給出下載鏈接可以啟動下載任務(wù) 實(shí)現(xiàn)局...

    guyan0319 評論0 收藏0
  • 練習(xí)項目備選清單

    摘要:練習(xí)項目備選清單文件下載器功能概要設(shè)計實(shí)現(xiàn)新建下載功能以為基礎(chǔ)給出下載鏈接可以啟動下載任務(wù)實(shí)現(xiàn)局域網(wǎng)內(nèi)下載傳輸文件以單線程下載方式實(shí)現(xiàn)附加功能支持?jǐn)帱c(diǎn)續(xù)傳實(shí)現(xiàn)多線程下載實(shí)現(xiàn)下載參考技術(shù)套接字編程多線程編程音視頻播放器功能概要設(shè)計實(shí)現(xiàn)播放常見 練習(xí)項目備選清單 Utilities 1. 文件下載器 功能概要設(shè)計: 實(shí)現(xiàn)新建下載功能(以ftp為基礎(chǔ)) 給出下載鏈接可以啟動下載任務(wù) 實(shí)現(xiàn)局...

    peixn 評論0 收藏0
  • 前端修煉の道 | 如何成為一名合格前端開發(fā)工程師?

    摘要:上期回顧在上一節(jié)我們已了解前端開發(fā)是做什么的,現(xiàn)在的問題是,如何才能成為一名合格的前端開發(fā)工程師相信這個問題是大家比較關(guān)心的。 showImg(https://segmentfault.com/img/bVbi9ks?w=900&h=383);上期回顧 在上一節(jié)我們已了解前端開發(fā)是做什么的,現(xiàn)在的問題是,如何才能成為一名合格的前端開發(fā)工程師? 相信這個問題是大家比較關(guān)心的。 前端開發(fā)工...

    Jackwoo 評論0 收藏0
  • 使用Chrome DevToolsTimeline分析頁面性能

    摘要:如果網(wǎng)頁動畫能夠做到每秒幀,就會跟顯示器同步刷新,達(dá)到最佳的視覺效果。下面的一條是,低于這條線,可以達(dá)到每秒幀上面的一條是,低于這條線,可以達(dá)到每秒次渲染。圖中幀柱的高度表示了該幀的總耗時,幀柱中的顏色分別對應(yīng)該幀中包含的不停類型的事件。 原文地址:http://horve.github.io/2015/10/26/timeli... 隨著webpage可以承載的表現(xiàn)形式更加多樣化,通...

    v1 評論0 收藏0
  • 前端每周清單第 29 期:Web 現(xiàn)狀分析與優(yōu)化策略、Vue 單元測試、Headless Chrom

    摘要:前端每周清單第期現(xiàn)狀分析與優(yōu)化策略單元測試爬蟲作者王下邀月熊編輯徐川前端每周清單專注前端領(lǐng)域內(nèi)容,以對外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點(diǎn)分為新聞熱點(diǎn)開發(fā)教程工程實(shí)踐深度閱讀開源項目巔峰人生等欄目。 showImg(https://segmentfault.com/img/remote/1460000011008022); 前端每周清單第 29 期:Web 現(xiàn)狀分析與優(yōu)化策略...

    HackerShell 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<