我與高效能js的約會

三妹不是佩奇啊發表於2019-01-24

2019第一本書產出,紀念一波:

我與高效能js的約會

第一章:js載入

  1. js的阻塞特性:

當瀏覽器在執行js程式碼的時候,不能同時做其他事情。(介面ui執行緒和js執行緒用的是同一程式,所以js執行越久,網頁的響應時間越長。)

  1. 指令碼的位置

如果把指令碼<
script>
放在<
head>
中,頁面會等js檔案全部下載並執行完成後才開始渲染,在這些檔案下載和執行的過程中:會導致訪問網站的時候有明顯的延遲,表現為:頁面空白。

這點在這個專案就用到了!

我與高效能js的約會

特別是ie,說不定人家給你來個報錯呢~(你能拿我怎麼樣。略略略.ipg)

效能提升:推薦將所有的<
script>
標籤儘可能的放到<
body>
標籤的底部,優先渲染頁面,減少頁面空白時間。

  1. 元件指令碼。

每個<
script>
標籤初始下載的時候都會阻塞頁面的渲染。效能提升做法:減少內嵌指令碼:減少內嵌的<
script>
標籤,將程式碼寫在一個標籤中。

合併外鏈的js檔案:http請求會帶來額外的效能開銷,栗子:下載一個100KB的js檔案比下載4個25kb的js檔案更快。具體操作方法自行搜尋。

  1. 無阻塞指令碼的方法

script標籤的aync屬性:

async 屬性規定一旦指令碼可用,則會非同步執行。async 屬性僅適用於外部指令碼(只有在使用 src 屬性時)。如果 async=”async”:指令碼相對於頁面的其餘部分非同步地執行(當頁面繼續進行解析時,指令碼將被執行)

script標籤的defer屬性:

js檔案在頁面解析到script標籤的時候開始下載,但並不會執行,dom載入完成執行。這兩個屬性的區別在於執行時機。

動態指令碼元素。

js操作dom建立<
script>
標籤,自定義生成標籤的type、src屬性。檔案會在該元素被新增到頁面的時候開始下載。ps:如果檔案順序很重要的話,最好按照順序合成一個檔案。然後再新增到頁面中。這樣:無論何時啟動下載。檔案的下載和執行過程不會阻塞頁面的其他程式。

3. XHR ajax指令碼注入、

用get請求一個檔案,請求好了。然後建立動態指令碼,最後新增進去。缺陷:檔案要再請求頁面的同一個域。

第二章:資料存取

1.字面量和區域性變數速度快於陣列和物件。
2.三目運算運算更快原因:(cpu原理
  • CPU是通過流水線處理來獲得高效能的。所謂流水線,簡單來說就是當CPU在處理當前指令的時候,後面已經有N條指令在後面排隊等待你去執行了。這樣,當你要執行下一條指令的時候,你不用再去找那條指令,它已經乖乖跟在前一條指令的屁股後面等你去執行了。
2.2 if…else…處理模式 那問題就在於,後面的指令需要確認一個排隊順序。如果程式設計師也是簡單的編寫流水線式的程式碼,對於CPU來說指令排序很容易。但是if…else…就不一樣了。 if…else…簡單來說就是:當我滿足條件,那我就執行A,如果我不滿足條件,我就執行B。但是對於給指令排隊的CPU來說,它還沒執行到判斷條件這一步,不知道你滿不滿足呀!這樣它就不好給指令排隊了。 假設它按照你滿足條件,把A指令排在你後面。那麼當執行到最後發現你不滿足條件,那麼就得把之前排好的佇列清空,重新給你把B指令排到後面給你執行。這種預測錯誤的懲罰將會導致CPU處理時間更長。 假設預測準確的話,每次呼叫函式大概13個時鐘週期,而只要預測錯誤,可能就需要大約44個時鐘週期了。

2.3 三元運算處理模式 對於三元運算子,它的處理方法就不同了。 x=y>
0?A:B;
當CPU要給這條指令排隊時,它會將兩種結果都進行排隊,也就是表示式A和表示式B都會被CPU進行處理,算出結果。 計算對CPU來說反而是它更喜歡的事情,你只要能排隊排好了,讓它能流水線模式來執行,對於它來說總體速度反而更快。 CPU會將兩種結果A和B都給計算出來(這跟if…else…不同,其只會計算一種結果),最後再判斷y>
0?。如果y>
0,則選擇A,拋棄B;
否則就選擇B,拋棄A。
3.執行函式的作用域鏈:
在執行函式過程中,每遇到一個變數,都會經歷一次標誌符解析過程來決定從哪裡獲取或儲存資料。該過程搜尋執行環境的作用域鏈,從作用域鏈頭部開始,從頭到尾,如果沒找到就會未定義(undefined).
Function add(num1,num2){
return num1 + num2;
}
函式訪問sun1,sum2都會產生搜尋過程。這個搜尋過程影響效能。(名字相同的變數存在於作用域鏈不同部分,搜尋最先找到哪個,就用哪個。這個就會遮蔽其他的。)
3.解析標誌符是會消耗時間的。區域性的讀寫速度總是快於全域性的。因為全域性在執行環境是最慢的;在執行環境的作用域鏈的最末端。
4.減少跨作用域的值,把它儲存到區域性變數裡面。
比如:document.getElementById(“go-btn”).onclick = function(){
start();
}
====>
var doc = document;
====>
var bd = document.body;
document 是全域性物件,每次使用都要遍歷整個作用域鏈,直到在最後的全域性變數物件中找到。
還有在倉庫中取的值,var ss = a.b.c.d.i.d.state;
vm.$post(url, params, prompt, function(source) { 
var rawData = source.orgInfoDetailList.data.basicInfo.rawDatas[0];
if ( rawData &
&
rawData.companyName &
&
(rawData.orgNo || rawData.creditCode) &
&
rawData.companyIndustry &
&
rawData.industryCode ) {
// doSomethingInteresting
}) vm.searchShow = false;

} else {
prompt.other('客戶資訊不完整');

}
})複製程式碼
5.作用域鏈一般是不可變的,但是with可以做到。還有try catch;(反正我不用with)
6.var book = {
age:’78’,
sex:’女’
}
Console.log(book.hasOwnProperty(‘age’)) true
Console.log(book.hasOwnProperty(’toString’)) false
hasOwnProperty是用來判定是否包涵特定的例項成員。
要確定是否包涵特定的屬性,可以用in操作符。in會搜尋例項也會搜尋原型。
console.log(’title’ in book);
true
console.log(’toString’ in book);
true
7.巢狀成員越深,讀取速度就會越慢。location.href <
windows.location.href;如果這些屬性不是物件的例項屬性,那麼成員解析還需要搜尋原型鏈,花更多的時間。
8. . 和 [] 兩者操作並沒有明顯的區別。只有在safari中,點符號始終會更快。

第三章:dom程式設計

1.訪問dom元素是有代價的——過橋費。修改元素更貴,導致瀏覽器重新計算頁面的幾何變化。
迴圈訪問修改DOM元素絕逼是“大佬”!!!!!好吧,我是諷刺。

我與高效能js的約會

2.獲取某個節點,程式碼更快更簡潔的方式:
Var elements = document.querySelectorAll(‘#menu a’);
代替 document.getElementByTagName……
3.
DOM樹:表示頁面結構。
渲染樹:表示DOM節點如何顯示。(css)
一旦長和寬變化,或增加文字,元素的幾何屬性和位置受到影響。
就會重新構造渲染樹———>
>
>
重排
接著瀏覽器重新繪製受影響的部分到螢幕中———>
>
>
重繪
並不是所有的DOM變化都會影響幾何。改變顏色的話只會來一次重繪,沒有重排。
4.重排何時發生:
1.新增或刪除可見的DOM元素。
2.元素位置改變。
3.元素尺寸改變。
4.內容改變,文字改變或者圖片被另一個圖片替代。
5.頁面渲染器初始化。
6.瀏覽器視窗尺寸改變。
5.offsetTop,scrollTop,clientTop……等等,需要返回最新的佈局資訊,因此瀏覽器不得不執行渲染列隊中的“待處理變化”並觸發重排以返回正確的值。就算以上屬性沒有任何改變。
6.
cssText 可以對某個節點進行多個樣式修改。
ele.style.cssText = ‘border-left:1px;
border-right:2px;
border-bottom:9px’;
如果不想改變以前的,加一些渲染:ele.style.cssText+=‘border-top:20px’;
7.批量修改dom:通過一下步驟來減少重繪和重排的次數。
1.使元素脫離文件流:absolute,display…(隱藏元素,應用修改,重新顯示)
2.對其應用多重改變:對要修改的文件拷貝拿出去修改,再放進來。
3.把元素帶回文件中:在文件之外建立並更新一個文件片段,然後把它附加到原始列表中。
8.
一般來說,重排隻影響渲染樹中的一小部分。
減少使用:hover
9.事件委託:
瀏覽器需要跟蹤每個事件處理器,這也會佔用更多的記憶體。還有並不是100%的按鈕或連結會被使用者點選。因此,使用事件代理,只需要給外層元素繫結一個處理器,就可以處理在其子元素上觸發的所有事件。
捕獲——>
到達目標——>
冒泡
還可以用來判斷事件物件來源作相應處理。

第四章:演算法和流程控制

1.
for(var prop in object){
//遍歷所有屬性名
}
不要使用for in 遍歷陣列成員
2.
for(var i=0;
i<
items.length;
i++) 這樣不好,因為每次迴圈都會再算一次item.length長度。
for(var i=0;
len=items.length;
i<
len;
i++) 這樣有時能提升25%,甚至50%在ie裡面。
使用倒序迴圈可以優化50%~~60%.因為少了比較步驟和判斷兩步。
我與高效能js的約會i- -操作本身會影響CPSR(當前程式狀態暫存器),CPSR常見的標誌有N(結果為負), Z(結果為0),C(有進位),O(有溢位)。i >
0,可以直接通過Z標誌判斷出來。i++操作也會影響CPSR(當前程式狀態暫存器),但隻影響O(有溢位)標誌,這對於i <
n的判斷沒有任何幫助。所以還需要一條額外的比較指令,也就是說每個迴圈要多執行一條指令。

3.for 1139 forEach 1086. 其實差不多。

4.大多數情況下,switch優於if-else;
但是如果條件不多還是建議if-else,易讀。
5.優化if-else:把可能性最大的放第一位。
If(value <
5){
//…
} else if (value >
5 &
&
value <
10) {
//…
} else {
//…
}
6.
If (value === 0) {
//…
} else if (value === 1) {
//…
}else if (value === 2) {
//…
}else if (value === 3) {
//…
}else if (value === 4) {
//…
}else if (value === 5) {
//…
}else if (value === 6) {
//…
}else if (value === 7) {
//…
}else if (value === 8) {
//…
}else if (value === 9) {
//…
}else {
//…
}
這種情況最多可能要10次;所以換一種不穩定,但是提升效能的寫法。
if(value <
6){
if (value <
3) {
If (value === 0) {
//…
} else if (value === 1) {
//…
}else {
//…
}
} else {
If (value === 4) {
//…
} else if (value === 5) {
//…
}else {
//…
}
}
} else {
不想寫了。。。。。。。。。。你懂的,我懂的。
}
查詢表—是個不錯的選擇。
當條件在1-4:三者都可以。但還是不如查詢表
 
 
 
 
 
 
5-8:switch 優於 if,但是二者都不如查詢表
 
 
 
 
 
 
9以上switch 和 if 一樣爛,都不如查詢表
var results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10];
return results[value];

第五章:字串和正規表示式

1.字串連結:
array.Join() string.concat(),都要比+ +=效率高。
2.str = str + “one” + “two” 效率比 str += “one” + “two”;
因為後者需要建立臨時變數去存“one” + “two”的值。
3.在正規表示式中,如果用^這個符號效率會更高,因為只要匹配到第一個失敗的地方就會停止後面的繼續匹配。(大知識)
4.去除字串首尾空白:
s = ‘ oo ‘;
console.log(s.trim());
原生js有自帶trim.
可以自己實現trim()方法,
If (!String.prototype.trim) {
String.prototype.trim = function(){
return this.replace(/^s+/,””).replace(/\s+$/,””)
}
}

第六章: 快速響應的使用者介面

1.瀏覽器限制:是必要的防止惡意程式碼永不停止的密集操作鎖住使用者的瀏覽器或計算機。分為呼叫棧大小限制和長時間執行指令碼限制。100毫秒的響應時間讓以使用者體驗良好。
2.定時器程式碼只有在建立它的函式執行完成之後,才有可能被執行。
3.如果延時時間到了,主程式還沒有執行結束,那麼延時程式碼就會在onclick程式碼執行期間就加入佇列,那麼onclick一旦執行完,就會立馬執行延時程式碼,給人造成沒有延時的錯覺。一般至少25ms。
4.定時器可用來安排程式碼延遲執行,它使得你可以長時間執行指令碼分解成一系列的小任務。

第七章: ajax

1.url長度超過2048個字元,用post.不然如果是get,請求的url被截斷。
2.動態指令碼注入:
1.優勢:克服了XHR的最大限制:跨域請求資料。這是一個hack。
2.缺點:不能設定請求的頭資訊,引數傳遞也只能是get。
3.MXHR對於上傳較多圖片時效能提升4-10倍。
4.使用XHR傳送資料到伺服器時,get要比post更快,這是因為對於少量資料而言,一個get請求往伺服器只傳送一個資料包。一個post請求要傳送兩個資料包。一個裝載頭資訊,另一個裝載post正文。post更適合傳送大量資料到伺服器,因為它不關心額外資料包的量,另一個原因是ie對url有限制,它不可能用過長的get請求。

第八章:程式設計實踐

1.當在一段js程式碼中,執行執行另一段js程式碼,都會導致雙重求值的效能消耗。
比如:eval setTimeout setinterval
給 setTimeout setinterval傳遞函式而不是字串作為引數。
2.使用object/array直接量:
var myobject = new Object();
myobject.name = ’selena’;
myobject.age = ‘12’;
myobject.flag = true;
var myobject = {
name:’selena’,
age:’12’,
flag:true
}
第二段比第一段更快。特別是數量越多,越明顯。
3.原生方法總是比自己寫的方法更快的,因為是用c++寫的存在瀏覽器中的。這意味著這些方法會被編譯成機器碼,成為瀏覽器的一部分,不會像自己寫的那樣受限制。
我一直記得以前ACM大賽,朋友說要自己造輪子,要比原生更快。

我與高效能js的約會

這是以上比較簡潔的總結啦,具體demo還是夭折在我的小mac本里啦~

但是個人還是覺得,優化效能也要考慮程式碼的可讀性,如果過於誇張的追求技術show,而忽略了的專案的可維護性,還是不太好的~

來源:https://juejin.im/post/5c493041f265da613d7c63ba

相關文章