高性能JavaScript整理總結
關於前端性能優化:首先想到的是雅虎軍規34條
然後最近看了《高性能JavaScript》
大概的把書中提到大部分知識梳理了下並加上部分個人理解
這本書有參考雅虎特別性能小組的研究成果,所以跟34 軍規有很多相似之處
有不當之處請在評論區指正,感謝~
約定:很多單詞語法都是簡寫比如doc指document,點點點代表不重要代碼省略,碼字不易(/雙手合十)
1. 加載和執行
- JavaScript是單線程,所以JavaScript的加載和執行是從上至下加載執行完一個再繼續加載執行下一個文件,會阻塞頁面資源的加載,所以一般情況下JavaScript文件放在body標籤內底部,很多後端開發人員放在body標籤外下面,這樣做不好的地方有兩處:1、不規範 2、可能會造成js獲取不到頁面元素而導致報錯。而放在body標籤內底部可以確保js執行前頁面渲染完成
<body>
js... //正確
</body>
<!-----------------------分界線---------------------------->
<body>
</body>
js... //錯誤
- 合併腳本,每個<script>標籤初始化下載都會阻塞頁面渲染,所以減少頁面的<script>標籤數量可以起到優化作用,內嵌腳本外鏈腳本通用,另外HTTP會帶來的額外的性能消耗,下載一個100KB的文件比下載4個25KB的文件更快,所以可以通過進行腳本的合併去1、減少<script>標籤數量 2、減少HTTP請求帶來的消耗(針對外鏈腳本),雖然現在很多工具幫我們完成了合併工作,但是原理還是需要了解下的。
特殊情況:把一段內嵌的腳本緊跟在外鏈css標籤後面,會導致:內嵌腳本為了確保執行的時候獲得的是最精準的樣式信息,會去先等待外鏈樣式表的下載,這樣會導致頁面阻塞去等待該樣式表下載。如下:
//錯誤示範
<link ... href='...'>
<script>
內嵌腳本...
</script>
- 三種無阻塞下載JavaScript的方法:
1.使用<script>標籤的defer屬性,defer描述:規定是否對腳本執行進行延遲,直到頁面加載為止。
2.使用動態創建的<script>元素來下載並執行代碼,俗稱動態腳本注入,建議:動態加載的腳本放在head標籤中比body標籤中更保險,尤其是在頁面加載中執行腳本,當body的內容沒有全部加載完成時,IE可能會報錯
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'a.js';
document.querySelectorAll('head')[0].appendChild(script);
3.使用XHR對象下載JavaScript代碼並注入頁面,常用的工具有比如LazyLoad類庫(懶加載)LAB.js等
2. 數據存取
- JavaScript四種基本數據存取位置:
1.字面量:代表自身,無特定位置,包括:字符串、數字、布爾值、對象、數組、函數、正則表達式及null和undefined
2.本地變量:var/let/const關鍵字定義的數據存儲單元
3.數組元素:存儲在JavaScript數組對象內部,以數字為索引,下標從0開始
4.對象成員:存儲在JavaScript對象內部,以字符串為索引
從一個字面量和本地變量中存取數據時的性能消耗極小(可忽略),數組和對象則稍高一些。
建議:儘量使用字面量和局部變量(局部變量在方法運行過後會自行釋放,用完手動置為null或undefined也行),減少使用對象和數組,比如某作用域內的值唄函數引用一次以上,就可以把它存儲到局部變量中來使用
var doc = document.querySelectorAll... ,
a = doc.getElement... ,
b = doc.getElement... ,
c = doc.getElement... ;
- 作用域
每一個函數表示為一個對象,是Function的一個實例,Function對象提供一個內部屬性[[scope]]該屬性僅js引擎讀取(題外話:想起來css的scoped屬性,在vue組件中使用時,改樣式文件只作用於該組件)[[scope]]包含一個函數被創建的作用域中對象的集合,這個集合就是函數的作用域鏈。當執行環境創建時,作用域鏈初始化為[[scope]]屬性中的對象,按照出現順序,複製到執行環境的作用域鏈中。然後執行環境會創建一個‘活動對象’,‘活動對象’作為函數運行的變量對象,包含所有局部變量、命名參數、參數集合和this,當執行環境銷燬,活動對象也被銷燬。
在函數執行時,每遇到一個變量,都會經歷一次標識符解析過程(標識符位置越深,對應變量讀寫越慢)去決定從哪獲取或者存儲數據。
不太好理解,打個比方,A作用域包含B作用域,B作用域包含C作用域,在C作用域中根據一個標識符去找對應的變量,如果在C作用域中沒找到,就會再去B作用域搜索,B作用域沒找到,再去A作用域搜索,搜索的過程消耗了性能,依然是上面的例子,document是全局變量,搜索該變量時是在最後全局變量中找到的,所以放在局部變量中會更快找到。
TIPS:命名相同的兩個變量存在於作用域鏈的不同部分,則標識符是遍歷作用域時先找到的那個,先找到的屏蔽後面的。各瀏覽器標識符解析速度有差異。
建議:儘可能使用局部變量
with語句和try catch語句可以改變作用域鏈,儘量避免使用with語句來改變作用域鏈,因為它使函數的所有局部變量出現在第二個作用域鏈對象中,
function aa() {
with(document){ //訪問document變快了,訪問其他局部變量變慢了
var bd = body,
links = getElementsByTagName('a'),
i = 0,
len = links.length;
...
}
}
然後try catch是把try塊裏面的錯誤拋到了catch塊中處理,也是改變了作用域鏈,當catch塊語句執行完畢後,作用域鏈又會恢復到之前狀態
- 動態作用域
with、try catch、還是包含eval()的函數,都可以認為是動態的作用域,但都是執行過程中才有動態作用域,無法通過查看代碼結構檢測出來,只有在必要時才推薦使用動態作用域
- 閉包
function assignEvents() {
var id = '2341321';
document.querySelectorAll('#aaa').onclick = function (event) {
saveDom(id);
}
}
var idNew = id; //undefined
作為局部變量,id只能在當前作用域訪問,出了作用域就訪問不到了,但是這個事件處理函數處於該作用域內,可以獲取局部變量id,就變成了assignEvents外的saveDom方法可以獲得assignEvents的局部變量id。為了實現這一功能
閉包的[[scope]]屬性引用了assignEvents執行環境作用域鏈的對象(這個對象包含id屬性),當執行結束時,執行環境銷燬,理應活動對象也被銷燬,但是因為閉包的引入,導致這個活動對象處於激活狀態,就無法銷燬,這就需要更多的內存空間。
由於IE使用非原生JavaScript對象實現DOM對象,所以閉包可能導致內存泄漏。
由於saveDom方法跨作用域訪問量變量id,所以閉包會帶來性能消耗,解決辦法是:常用的跨作用域變量存儲在局部變量中使用
- 對象成員
大部分JavaScript代碼是面向對象編寫的(自定義對象、BOM/DOM),所以會導致非常頻繁的訪問對象成員。所以訪問對象也有可優化的地方
嵌套成員:對象可以嵌套其他成員
嵌套深度與讀取時間成正比
- 原型鏈
推薦一個回答,第一個 蘇墨橘的回答,相比於之前看的千篇一律的解答,這個更容易理解
關於原型鏈
3. 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更明顯
- 事件委託:事件逐成冒泡被父級捕獲
每綁定一個事件處理器都是有代價的
事件三階段:捕獲--到達目標--冒泡
事件委託的兼容性問題:訪問事件對象、判斷事件源、取消冒泡(可選)、阻止默認動作(可選)
使用事件委託來減少事件處理器的數量
4. 算法和流程控制
- 循環
大多數編程語言中,代碼執行時間大部分消耗在循環中,所以循環也是提升性能的重要環節之一
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
<!--Memoization緩存重複運算的值-->
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
5. 字符串和正則表達式
説明:正則表達式我不會,這裏就不説了
- 字符串
比較下四中字符串拼接方法的性能:
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是比較快的,也是大量字符串拼接的唯一高效方式
詳細參考 幾種字符串拼接性能
6. 快速相應的用户界面
- 瀏覽器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線程無關的長時間運行腳本,個人覺得大量的純計算可以考慮使用
7. Ajax(阿炸克斯)
前面説到數據存取會影響性能,理所應當的,數據的傳輸同樣影響性能
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獲取可縮短頁面加載時間
8. 編程實踐
- 避免雙重求值
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。
9. 構建並部署高性能JavaScript應用
這一章講的都是其他章節的優化原理的實踐,主要有:
1.合併多個js文件
2.預處理js文件
3.js壓縮
4.js的HTTP壓縮
5.緩存js文件
6.處理緩存問題
7.使用內容分發網絡(CDN)這個有點效果顯著的感覺,前年第一次用的時候感覺快了很多,打個比方就是:
京東網上水果蔬菜超市,假設你在上海買了一個榴蓮,京東可以在上海的倉庫給你發貨,如果上海沒有他們的倉庫,就在離你最近的一個倉庫發貨,以保證最快速度送到你手上(吃什麼不好,吃榴蓮,別人會説食屎拉你)。這個倉庫放的就是靜態資源文件,根據請求發出的位置找到最近的CDN節點把資源返回給請求端,大概是這個意思,具體原理參考CDN原理
現在很多方式都在gulp、webpack工具裏進行了,方便省事
10. 工具
- 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》及其他其他博客並註明出處,如有侵權請聯繫作者刪除~
後續會通過舉證説明更多方案的效果,不斷完善此文檔
注:內容有不當或者錯誤處請指正~轉載請註明出處~謝謝合作!