摘要:然后執行環境會創建一個活動對象,活動對象作為函數運行的變量對象,包含所有局部變量命名參數參數集合和,當執行環境銷毀,活動對象也被銷毀。
高性能JavaScript整理總結
關于前端性能優化:首先想到的是雅虎軍規34條
然后最近看了《高性能JavaScript》
大概的把書中提到大部分知識梳理了下并加上部分個人理解
這本書有參考雅虎特別性能小組的研究成果,所以跟34 軍規有很多相似之處
有不當之處請在評論區指正,感謝~
約定:很多單詞語法都是簡寫比如doc指document,點點點代表不重要代碼省略,碼字不易(/雙手合十)
1. 加載和執行JavaScript是單線程,所以JavaScript的加載和執行是從上至下加載執行完一個再繼續加載執行下一個文件,會阻塞頁面資源的加載,所以一般情況下JavaScript文件放在body標簽內底部,很多后端開發人員放在body標簽外下面,這樣做不好的地方有兩處:1、不規范 2、可能會造成js獲取不到頁面元素而導致報錯。而放在body標簽內底部可以確保js執行前頁面渲染完成
js... //正確 js... //錯誤
合并腳本,每個
三種無阻塞下載JavaScript的方法:
1.使用
閉包的[[scope]]屬性引用了assignEvents執行環境作用域鏈的對象(這個對象包含id屬性),當執行結束時,執行環境銷毀,理應活動對象也被銷毀,但是因為閉包的引入,導致這個活動對象處于激活狀態,就無法銷毀,這就需要更多的內存空間。
由于IE使用非原生JavaScript對象實現DOM對象,所以閉包可能導致內存泄漏。
由于saveDom方法跨作用域訪問量變量id,所以閉包會帶來性能消耗,解決辦法是:常用的跨作用域變量存儲在局部變量中使用
對象成員
大部分JavaScript代碼是面向對象編寫的(自定義對象、BOM/DOM),所以會導致非常頻繁的訪問對象成員。所以訪問對象也有可優化的地方
嵌套成員:對象可以嵌套其他成員
嵌套深度與讀取時間成正比
原型鏈
推薦一個回答,第一個 蘇墨橘的回答,相比于之前看的千篇一律的解答,這個更容易理解
關于原型鏈
三個問題:
1.訪問和修改DOM元素
2.修改DOM元素樣式導致的重繪和重排
3.通過DOM事件處理與用戶交互
DOM
DOM:document object module 文檔對象模型,可以理解為操作文檔的程序接口
為什么說DOM慢,操作DOM代價昂貴,簡單理解就是兩個獨立的功能只要通過接口彼此連接,就會產生消耗。比如:中國人買iPhone,美國人買衛龍,需要交稅的,這個稅就是消耗,同樣,從DOM到JavaScript或從JavaScript到DOM都有類似的消耗。
所以盡量的減少這種交稅的次數來達到一定的性能優化,
最壞的方式就是在循環中操作或者訪問DOM,非常消耗性能。
//bad for(var i = 0; i < 10000; i++){ document.querySelectorAll("#aaa").innerHTML += "a"; } //good var aaaHtml = ""; for(var i = 0; i < 10000; i++){ aaaHtml += "a"; } document.querySelectorAll("#aaa").innerHTML += aaaHtml;
關于innerHTML和DOM方法(doc.createElement())誰更快
不考慮Web標準的情況下,差不多。
除了最新版的WebKit內核之外的瀏覽器中,innerHTML更快,舊版本瀏覽器效率更高
新版的WebKit內核的瀏覽器DOM方法更快
克隆節點帶來的優化效果不是很明顯、略過
訪問集合元素時使用局部變量(跟操作一個元素多次是一個道理,不贅述)
遍歷DOM
一般來說,querySelectorAll()是獲取元素最快的API 返回的是一個NodeList
querySelector() 返回的是element,
querySelectorAll()還有一點就是可以同時獲取兩類元素
var two = doc.querySelectorAll("div.aaa,div.bbb");
重繪和重排
瀏覽器下載完頁面中的所有組件--HTML標記、JavaScript、CSS、圖片之后會解析生成兩個內部數據結構:
1.DOM樹 表示頁面結構 比如操場上早操,小紅你站這,小綠你站那,小明...(滾出去),哈,開個玩笑,這種位置的結構就像DOM樹
2.渲染樹
表示DOM節點如何顯示 比如 小紅穿綠衣服,小綠穿紅衣服,小明穿毛呢大衣 小紅長頭發 小綠綠頭發等等
DOM樹中每一個需要顯示的節點在渲染樹種至少存在一個對應的節點(隱藏元素沒有對應節點,所以可以利用這一點,先把元素隱藏然后處理然后顯示來優化消耗的性能),渲染樹中的節點被稱為‘幀’或者‘盒’,符合CSS模型定義。(盒子模型不是落地成盒)當DOM和渲染樹構建完成,瀏覽器開始顯示頁面元素。
那什么時候開始重繪和重排呢:
當DOM變化影響了幾何屬性,瀏覽器會讓渲染樹中受到影響的部分失效,重新構造渲染樹。這個過程稱為重排; 比如班級座位正常,某段時間后小明狂胖200斤,本來小明坐一個位置,現在需要兩個位置,其他同學就需要往兩邊坐或者往后坐,當然,小明會不會滾出去取決于小明的成績好壞。
完成重排后,,瀏覽器會把受影響的部分重新繪制到屏幕上,這個過程稱為重繪;
當改變DOM的非幾何屬性時,只會發生重繪,不會重排;
重繪和重排都是代價昂貴;盡量減少
重排何時發生:
1.添加或刪除可見DOM元素
2.元素位置改變
3.元素尺寸改變(內外邊距、邊框厚寬高等)
4.內容改變 (內容導致尺寸變化的時候)
5.頁面渲染器初始化
6.瀏覽器窗口尺寸變化
減少重繪和重排
//三次重繪 el.style.borderLeft = "1px"; el.style.borderRight = "2px"; el.style.padding = "5px"; //一次重繪 el.style.cssText = "border-left: 1px;border-right: 2px; padding: 5px";
批量修改DOM時如何減少重繪和重排: 步驟:
1.使元素脫離文檔流
2.對其應用多重改變
3.把元素帶回文檔中 //步驟1 3 兩次重排
三種方法使DOM脫離文檔:
1.隱藏元素--應用修改--顯示
2.使用文檔片斷,在當前DOM之外構建一個子樹,再拷回文檔
3.拷貝到一個脫離文檔的節點中,修改副本,副本替換原始元素
讓元素脫離動畫流
一般來說,重排只會影響一小部分渲染樹,但是也有可能影響很大一部分甚至全部。一次大規模的重排可能會讓用戶覺得頁面一頓一頓的,影響用戶體驗
避免大部分重排:元素使用絕對定位讓其脫離文檔流--動畫--恢復定位
IE和:hover
從IE7開始,IE可以在任何元素上使用:hover這個偽選擇器,但是當你大量元素使用時 會降低響應速度 IE8更明顯
事件委托:事件逐成冒泡被父級捕獲
每綁定一個事件處理器都是有代價的
事件三階段:捕獲--到達目標--冒泡
事件委托的兼容性問題:訪問事件對象、判斷事件源、取消冒泡(可選)、阻止默認動作(可選)
使用事件委托來減少事件處理器的數量
循環
大多數編程語言中,代碼執行時間大部分消耗在循環中,所以循環也是提升性能的重要環節之一
JavaScript四種循環:
1.for循環
Tips:for循環初始化會創建一個函數級變量而不是循環級,因為JavaScript只有函數級作用域(ES6存在塊級作用域if(){let n = ...}let定義的n只作用于if塊內部,執行完就會釋放不會導致變量提升),所以在for循環中定義一個變量和在循環體外定義一個變量時一樣的 var i = 100; for(var i = 0; i < 10; i++){ console.log(i) //0,1,2,3...9 }
2.while循環
3.do-while循環
4.for in 循環
Tips:for in循環可以枚舉任何對象的屬性名(不是值),但是for in比其他三個循環明顯要慢,所以除非要迭代一個屬性數量未知的對象,否則避免使用for in循環,如果遍歷一個屬性數量已知屬性列表,其他循環比for in快,比如:
var arr = ["name","age"], i = 0; while(i < arr.length){ process(object[arr[i]]); }
假設以上四種循環類型性能一樣,可以從兩個方面去優化循環的性能:
(當循環體復雜度為X時,優化方案優先減少循環體的復雜度,循環體復雜度大于X時,優化方案優先減少迭代次數 )
1.每次迭代的事務(減少循環體的復雜度)
2.迭代的次數(減少循環的次數,百度‘達夫設備’),可以這么理解,達夫設備就是拆解循環,比如遍歷一個長度為100的數組,普通情況下循環體執行100次,達夫設備的思想是把100次拆為每次循環執行多次(n表示)100對n取余,執行取余次數,再執行100除以n(下舍)次循環,這個循環體執行n次普通循環體的操作
達夫設備代碼:(這個8就是我說的n)
var i = items.length % 8; //先循環余數次數 while(i){ process(items[i--]); } i = Math.floor(items.length / 8); //再循環8的整數倍次數 循環體是普通循環的8倍 可以寫成函數傳參調用 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--]); }
最小化屬性查找:
for(var i = 0, len = arr.length; i < len; i++){ ... }
基于函數的迭代:forEach()
forEach遍歷一個數組的所有成員,并執行一個函數
arr.forEach(function(value, index, array){ ... })
但是所有情況下。基于循環的迭代比基于函數的迭代快8倍,在運行速度要求嚴格時,基于循環的迭代優先于基于函數的迭代
條件語句
if-else對比switch:
當條件較少時 使用if-else更易讀,而當條件較多時if-else性能負擔比switch大,易讀性也沒switch好。
優化if-else的方法是:盡可能的把可能出現的條件放在首位,比如:
var i = Math.random(1); if(i <= 0.8){ //i小于0.8是幾率最大的,如果i的值滿足i <= 0.8 后面的條件就不會再判斷了 ... }else if(i > 0.8 && i <= 0.9){ ... }else{ ... }
當條件很多的時候:(比如10個和10個以上),避免使用條件語句if-else、switch是最佳方式是使用hash表
Memoization
減少工作量就是最好的性能優化技術(你可以理解為,砍需求是為了性能優化,這是魯迅說的--魯迅:這句話我還真說過)
Memoization避免重復工作,緩存前一個計算的結果為后面的計算所用
比如分別求4、5、6的階乘
求6的階乘的時候,因為我緩存了5的階乘結果,那么6的階乘就是5的階乘結果乘以6
function memoizeA(n) { if(!memoizeA.cache){ memoizeA.cache = { "0": 1, "1": 1 } } if(!memoizeA.cache.hasOwnProperty(n)){ memoizeA.cache[n] = n * memoizeA(n-1) } return memoizeA.cache[n] } var a1 = memoizeA(4) console.log(a1) //24 var a2 = memoizeA(5) console.log(a2) //120 var a3 = memoizeA(6) console.log(a3) //720 function memoize(func, cache) { cache = cache || {}; var shell = function (arg) { if(!cache.hasOwnProperty(arg)){ cache[arg] = func(arg); } return cache[arg]; } return shell; } var funCcc = function ccc(n){ if(n == 0){ return 1; }else{ return n*ccc(n-1) } } var a4 = memoize(funCcc,{"0":1,"1":1}); console.log(a4(6)); //720
說明:正則表達式我不會,這里就不說了
字符串
比較下四中字符串拼接方法的性能:
A:str = str + "a"+"b"
B:str += "a" + "b"
C: arr.join("")
D:str.concat("b","c")
對于A與B比較:B會在內存中創建一個臨時字符串,字符串拼接為"ab"后賦給臨時字符串,臨時字符串賦給str;大多數瀏覽器下A優于B,但在IE8及更早的版本中,B優于A
關于join、concat加前兩種拼接的效率:
//+= (function () { var startTime = new Date().getTime(); var str = ""; var addStr = "hello world~, hello xiaojiejie"; for(var i = 0; i < 100000; i++){ str += addStr; } var endTime = new Date().getTime(); console.log("字符串str += a:"); console.log(endTime-startTime); })(); // + (function () { var startTime = new Date().getTime(); var str = ""; var addStr = "hello world~, hello xiaojiejie"; for(var i = 0; i < 100000; i++){ str = str + addStr; } var endTime = new Date().getTime(); console.log("字符串str = str + a:"); console.log(endTime-startTime); })(); //concat (function () { var startTime = new Date().getTime(); var str = ""; var addStr = "hello world~, hello xiaojiejie"; for(var i = 0; i < 100000; i++){ str = str.concat(addStr); } var endTime = new Date().getTime(); console.log("字符串str.concat:"); console.log(endTime-startTime); })(); //join (function () { var startTime = new Date().getTime(); var str = ""; var arr = []; var addStr = "hello world~, hello xiaojiejie"; for(var i = 0; i < 100000; i++){ arr.push(addStr); } str = arr.join(""); var endTime = new Date().getTime(); console.log("字符串join:"); console.log(endTime-startTime); })();
我用這段代碼簡單在chrome65上測試了下,平均下來A>B>C>D,未統計取平均,也沒測試其他瀏覽器
書上說在IE老版本join是比較快的,也是大量字符串拼接的唯一高效方式
詳細參考 幾種字符串拼接性能
瀏覽器UI線程
用于執行JavaScript和更新用戶界面的進程被稱為"瀏覽器UI線程",UI線程的工作基于一個隊列系統,當進程空閑時,就會從改隊列提取任務去執行,該任務可能是JavaScript代碼也可能是UI更新(重繪、重排)。
UI:用戶界面 GUI:圖形用戶界面 這張圖來自 鏈接
瀏覽器限制JavaScript任務的運行時間,限制兩分鐘,可以防止惡意代碼不斷執行來鎖定你的瀏覽器
單個JavaScript操作的花費總時間應該小于等于100ms,這就意味著在100ms內響應用戶的操作,不然就會讓用戶感受到遲鈍感
定時器讓出時間片斷
如果代碼復雜100ms運營不完,可以使用定時器讓出時間片斷,從而使UI獲得控制權進行更新。
這個例子只是說明JavaScript單線程,定時器可以把任務放到后面執行,方便理解 console.log(111); setTimeout(func(){console.log(222)},0); console.log(333); //111 333 222
JavaScript是單線程,所以定時器可以把JavaScript任務放到后面,控制權先交給UI線程
定時器精度有幾毫秒的偏差,,Windows系統中定時器的分辨率為25ms,所以建議延遲最小值設置為25ms
把一個任務分解成一系列子任務
把一個運行時間長的函數分解為一個個短時間運行的子函數
使用時間戳計算獲得程序運行時間,以便快速找到運行時間較長的代碼部分進行優化
重復的定時器會搶奪UI線程的運行時間,1秒及以上的低頻定時器不會有什么影響,當使用高頻100ms-200ms之前的定時器時響應會變慢,所以高頻重復定時器使用要注意
Web Workers (HTML5新特性)
在UI線程外運行,不占用UI線程的時間
來自W3C的worker demo
Web Workers不能修改DOM
運行環境組成:
一個navigator對象
一個location對象(與window.location相同 屬性-只讀)
一個self對象,指向worker對象
可以引入需要用到的外部文件importScripts()方法
可以使用js對象 Object、Array、Date等
XHR
定時器
close() 立刻停止Worker運行
W3C介紹Web Worker
博文:Web Worker原理和應用介紹
實際應用場景:處理純數據或者與UI線程無關的長時間運行腳本,個人覺得大量的純計算可以考慮使用
前面說到數據存取會影響性能,理所應當的,數據的傳輸同樣影響性能
Ajax通過異步的方式在客戶端和服務端之間傳輸數據。
數據傳輸
請求數據的五種方式:
A:XMLHTTPRequest(簡稱XHR)
最常用異步異步發送和接收數據,包括GET和POST兩種方式
不能跨域
GET--參數放在url后面,請求得到的數據會被緩存,當url加參數超過2048,可以使用POST方式
POST--參數在頭信息,數據不會被緩存
XHR工作原理及優缺點參考選我選我
B:動態腳本注入
其實就是創建一個script元素這個元素的src不受當前域限制,但是不能設置請求頭信息,也就是只能用GET方式
C.Multipart XHR
MXHR荀彧一個HTTP請求就可以傳輸多個數據
通過在服務端講資源打包成一個雙方約定的字符串分割的長字符串發送到客戶端,然后根據mime-typed類型和傳入的其他頭信息解析出資源
缺點:資源不能被緩存
D.iframe
E.comet
發送數據:XHR、Beacons、
數據格式
A.XML
優點:通用、格式嚴格、易于驗證
缺點:冗長、結構復雜有效數據比例低
B.JSON
JSON.parse():JSON-->對象
JSON.stringify():js值-->JSON字符串
文件小、下載快、解析快
C.JSON-P
在客戶端注冊一個callback, 然后把callback的名字傳給服務器。此時,服務器先生成 json 數據。 然后以 javascript 語法的方式,生成一個function , function 名字就是傳遞上來的參數 jsonp。最后將 json 數據直接以入參的方式,放置到 function 中,這樣就生成了一段 js 語法的文檔,返回給客戶端。
D.HTML
E.自定義數據格式
Ajax性能
最快的Ajax請求就是沒有請求(貧一句:最快的寫程序方式就是天天跟產品拌嘴,砍需求,那啥,我先跑了,產品拿著刀追來了)
避免不必要的請求:
服務端設置HTTP頭信息確保響應會被瀏覽器緩存
客戶端講獲取的信息存到本地避免再次請求(localstorage sessionstorage cookice)
設置HTTP頭信息,expiresgaosu告訴瀏覽器緩存多久
減少HTTP請求,合并css、js、圖片資源文件等或使用MXHR
通過次要文件用Ajax獲取可縮短頁面加載時間
避免雙重求值
eval()、Function慎用,定時器第一個參數建議函數而不是字符串都能避免字符串雙重求值
使用對象或者數組直接量
直接量:
var obj = { name:... age:... }
非直接量:
var obj = new Object() obj.name = ... ...
運行時直接量比非直接量快
避免重復工作
A:延遲加載(懶加載)
進入函數-->判斷條件-->重寫函數
B:條件預加載
函數調用前提前進行條件檢測
var addEvent = doc.addEventListener ? funcA : funcB
使用JavaScript速度快的部分
A.位操作
B.原生方法,首先原生方法是最快的,而且瀏覽器會緩存部分原生方法
C.復雜計算時多使用Math對象
D.querySelector和querySelectorAll是查詢最快的
當用Document類型調用querySelector()方法時,會在文檔元素范圍內查找匹配的元素;而當用Element類型調用querySelector()方法時,只會在這個元素的后代元素中去查找匹配的元素。若不存在匹配的元素,則這兩種類型調用該方法時,均返回null。
這一章講的都是其他章節的優化原理的實踐,主要有:
1.合并多個js文件
2.預處理js文件
3.js壓縮
4.js的HTTP壓縮
5.緩存js文件
6.處理緩存問題
7.使用內容分發網絡(CDN)這個有點效果顯著的感覺,前年第一次用的時候感覺快了很多,打個比方就是:
京東網上水果蔬菜超市,假設你在上海買了一個榴蓮,京東可以在上海的倉庫給你發貨,如果上海沒有他們的倉庫,就在離你最近的一個倉庫發貨,以保證最快速度送到你手上(吃什么不好,吃榴蓮,別人會說食屎拉你)。這個倉庫放的就是靜態資源文件,根據請求發出的位置找到最近的CDN節點把資源返回給請求端,大概是這個意思,具體原理參考CDN原理
現在很多方式都在gulp、webpack工具里進行了,方便省事
JavaScript性能分析
使用Date對象實例減去另一個實例獲得任務運行時間毫秒數
匿名函數
測量分析匿名函數的方法就是給匿名函數加上名字
調試工具
個人比較喜歡chrome調試工具
貢獻幾個比較全的教程
基礎篇
優化篇
實戰1
實戰2
英文使用介紹
腳本阻塞
Safari4、IE8、Firefox3.5、chrome及以上允許腳本并行下載,但阻塞運行,雖然文件下載快了,但是頁面渲染任會阻塞直到腳本運行完
對運行慢的腳本進行優化或重構,不必要的腳本等到等到頁面渲染完成再加載
Page Speed
顯示解析和運行JavaScript消耗的時間,指明可以延長加載的腳本,并報告沒被使用的函數
Fiddler
Fiddler是一個HTTP調試代理工具,能檢測到網絡中所有資源,以定位加載瓶頸
YSlow
YSlow工具可以深入觀察頁面初始加載和運行過程的整體性能
WebPagetest
WebPagetest:根據用戶瀏覽器真實的連接速度,在全球范圍內進行網頁速度測試,并提供詳細的優化建議。
WebPagetest
Google PageSpeed
PageSpeed 根據網頁最佳實踐分析和優化測試的網頁。
Pingdom 網站速度測試
輸入 URL 地址,即可測試頁面加載速度,分析并找出性能瓶頸。
Pingdom 網站速度測試
還有很多類似工具:參考前端性能優化和測試工具總結
本文檔主干內容來自于《高性能JavaScript》及其他其他博客并注明出處,如有侵權請聯系作者刪除~
后續會通過舉證說明更多方案的效果,不斷完善此文檔
注:內容有不當或者錯誤處請指正~轉載請注明出處~謝謝合作!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/54525.html
摘要:然后執行環境會創建一個活動對象,活動對象作為函數運行的變量對象,包含所有局部變量命名參數參數集合和,當執行環境銷毀,活動對象也被銷毀。 高性能JavaScript整理總結 關于前端性能優化:首先想到的是雅虎軍規34條然后最近看了《高性能JavaScript》大概的把書中提到大部分知識梳理了下并加上部分個人理解這本書有參考雅虎特別性能小組的研究成果,所以跟34 軍規有很多相似之處有不當之...
摘要:前端每周清單年度總結與盤點在過去的八個月中,我幾乎只做了兩件事,工作與整理前端每周清單。本文末尾我會附上清單線索來源與目前共期清單的地址,感謝每一位閱讀鼓勵過的朋友,希望你們能夠繼續支持未來的每周清單。 showImg(https://segmentfault.com/img/remote/1460000010890043); 前端每周清單年度總結與盤點 在過去的八個月中,我幾乎只做了...
閱讀 1883·2021-11-22 09:34
閱讀 3010·2021-09-28 09:35
閱讀 13374·2021-09-09 11:34
閱讀 3594·2019-08-29 16:25
閱讀 2820·2019-08-29 15:23
閱讀 2035·2019-08-28 17:55
閱讀 2424·2019-08-26 17:04
閱讀 3044·2019-08-26 12:21