成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

前端性能優(yōu)化之 JavaScript

Coding01 / 1483人閱讀

摘要:大多數(shù)情況下,對一個直接量和一個局部變量數(shù)據(jù)訪問的性能差異是微不足道的。

前端性能優(yōu)化之 JavaScript 前言

本文為 《高性能 JavaScript》 讀書筆記,是利用中午休息時間、下班時間以及周末整理出來的,此書雖有點老舊,但談論的性能優(yōu)化話題是每位同學必須理解和掌握的,業(yè)務響應速度直接影響用戶體驗。

一、加載和運行
大多數(shù)瀏覽器使用單進程處理 UI 更新和 JavaScript 運行等多個任務,而同一時間只能有一個任務被執(zhí)行
腳本位置

將所有script標簽放在頁面底部,緊靠上方,以保證頁面腳本運行之前完成解析


   
  
    

Hello World

defer & async

常規(guī)script腳本瀏覽器會立即加載并執(zhí)行,異步加載使用asyncdefer
二者區(qū)別在于aysnc為無序,defer會異步根據(jù)腳本位置先后依次加載執(zhí)行





動態(tài)腳本

無論在何處啟動下載,文件的下載和運行都不會阻塞其他頁面處理過程。你甚至可以將這些代碼放在部分而不會對其余部分的頁面代碼造成影響(除了用于下載文件的 HTTP 連接)

var script = document.createElement("script");
script.type = "text/javascript";
script.src = "file1.js";
document.getElementsByTagName("head")[0].appendChild(script);
監(jiān)聽加載函數(shù)
function loadScript(url, callback) {
  var script = document.createElement("script");
  script.type = "text/javascript";
  if (script.readyState) {
    //IE
    script.onreadystatechange = function() {
      if (script.readyState == "loaded" || script.readyState == "complete") {
        script.onreadystatechange = null;
        callback();
      }
    };
  } else {
    //Others
    script.onload = function() {
      callback();
    };
  }
  script.src = url;
  document.getElementsByTagName("head")[0].appendChild(script);
}
XHR 注入

前提條件為同域,此處與異步加載一樣,只不過使用的是 XMLHttpRequest

總結(jié)

將所有script標簽放在頁面底部,緊靠 body 關閉標簽上方,以保證頁面腳本運行之前完成解析

將腳本成組打包,頁面 script 標簽越少加載越快,響應也就更迅速。不論外部腳本文件或者內(nèi)聯(lián)代碼都是如此

二、數(shù)據(jù)訪問
數(shù)據(jù)存儲在哪里,關系到代碼運行期間數(shù)據(jù)被檢索到的速度.每一種數(shù)據(jù)存儲位置都具有特定的讀寫操作負擔。大多數(shù)情況下,對一個直接量和一個局部變量數(shù)據(jù)訪問的性能差異是微不足道的。
在 JavaScript 中有四種基本的數(shù)據(jù)訪問位置:

直接量
直接量僅僅代表自己,而不存儲于特定位置。 JavaScript 的直接量包括:字符串,數(shù)字,布爾值,對象,數(shù)組,函數(shù),正則表達式,具有特殊意義的空值,以及未定義

變量
使用 var / let 關鍵字創(chuàng)建用于存儲數(shù)據(jù)值

數(shù)組項
具有數(shù)字索引,存儲一個 JavaScript 數(shù)組對象

對象成員
具有字符串索引,存儲一個 JavaScript 對象

總結(jié)

直接量與局部變量訪問速度非常快,數(shù)組項和對象成員需要更長時間

局部變量比域外變量訪問速度快,因為它位于作用域鏈的第一個對象中。變量在作用域鏈的位置越深,訪問所需要的時間越長。全局變量總是最慢的,因為它們總位于作用域鏈的最后一環(huán)。

避免使用 with 表達式,因為它改變了運行期上下文的作用域鏈,謹慎對待 try-catch 表達式中 catch 子句,因為它具有同樣的效果

嵌套對象成員會造成重大性能影響,盡量少用

屬性在原型鏈中的位置越深,訪問速度越慢

將對象成員、數(shù)組項、域外變量存入局部變量能提高 js 代碼的性能

三、dom 編程

對 DOM 操作代價昂貴,在富網(wǎng)頁應用中通常是一個性能瓶頸。通常處理以下三點

訪問和修改 DOM 元素

修改 DOM 元素的樣式,造成重繪和重新排版

通過 DOM 事件處理用戶響應

一個很形象的比喻是把 DOM 看成一個島嶼,把 JavaScript(ECMAScript)看成另一個島嶼,兩者之間以一座收費橋連接(參見 John Hrvatin,微軟,MIX09,http://videos.visitmix.com/MI...)。每次 ECMAScript 需要訪問 DOM 時,你需要過橋,交一次“過橋費”。你操作 DOM 次數(shù)越多,費用就越高。一般的建議是盡量減少過橋次數(shù),努力停留在 ECMAScript 島上。

DOM 訪問和修改

訪問或修改元素最壞的情況是使用循環(huán)執(zhí)行此操作,特別是在 HTML 集合中使用循環(huán)

function innerHTMLLoop() {
  for (var count = 0; count < 15000; count++) {
    document.getElementById("here").innerHTML += "a";
  }
}

此函數(shù)在循環(huán)中更新頁面內(nèi)容。這段代碼的問題是,在每次循環(huán)單元中都對 DOM 元素訪問兩次:一次
讀取 innerHTML 屬性能容,另一次寫入它

優(yōu)化如下

function innerHTMLLoop2() {
  var content = "";
  for (var count = 0; count < 15000; count++) {
    content += "a";
  }
  document.getElementById("here").innerHTML += content;
}

你訪問 DOM 越多,代碼的執(zhí)行速度就越慢。因此,一般經(jīng)驗法則是:輕輕地觸摸 DOM,并盡量保持在 ECMAScript 范圍內(nèi)

節(jié)點克隆

使用 DOM 方法更新頁面內(nèi)容的另一個途徑是克隆已有 DOM 元素,而不是創(chuàng)建新的——即使用 element.cloneNode()(element 是一個已存在的節(jié)點)代替 document.createElement();

當布局和幾何改變時發(fā)生重排版,下述情況會發(fā)生:

添加或刪除可見的 DOM 元素

元素位置改變

元素尺寸改變(邊距、填充、邊框?qū)挾?、寬、高等屬性?/p>

內(nèi)容改變(文本或者圖片被另一個不同尺寸的所替代)

最初的頁面渲染

瀏覽器窗口尺寸改變

減少重排次數(shù)

改變 display 屬性,臨時從文檔上移除然后再恢復

在文檔之外創(chuàng)建并更新一個文檔片段,然后將它進行附加

先創(chuàng)建更新節(jié)點的副本,再操作副本,最后用副本更新老節(jié)點

總結(jié)

最小化 DOM 訪問,在 JavaScript 端做盡可能多的事情

在反復訪問的地方使用局部變量存放 dom 引用

謹慎處理 HTML 集合,因為它們表現(xiàn)‘存在性’,總對底層文檔重新查詢。將 length 屬性緩存到一個變量中,在迭代中使用這個變量。如果經(jīng)常操作這個集合,可以將集合拷貝到數(shù)組中

如果可以,使用速度更快的 API,比如 document.querySelectorAll()和 firstElementChild()

注意重繪和重排,批量修改風格,離線操作 DOM,緩存或減少對布局信息的訪問

動畫中使用絕對坐標,使用拖放代理

使用事件托管技術(shù)中的最小化事件句柄數(shù)量

四、算法與流程控制

代碼整體結(jié)構(gòu)是執(zhí)行速度的決定因素之一。代碼量少不一定執(zhí)行快,代碼量多,也不一定執(zhí)行慢,性能損失與代碼組織方式和具體問題解決辦法直接相關。

Loops

在大多數(shù)編程語言中,代碼執(zhí)行時間多數(shù)在循環(huán)中度過。在一系列編程模式中,循環(huán)是最常見的模式之一,提高性能必須控制好循環(huán),死循環(huán)和長時間循環(huán)會嚴重影響用戶體驗。

Types of Loops

for

while

do while

for in

前三種循環(huán)幾乎所有編程語言都能通用,for in 循環(huán)遍歷對象命名屬性(包括自有屬性和原型屬性)

Loop Performance
循環(huán)性能爭論的源頭是應當選用哪種循環(huán),在 JS 中 for-in 比其他循環(huán)明顯要慢(每次迭代都要搜索實例或原型屬性),除非對數(shù)目不詳?shù)膶ο髮傩赃M行操作,否則避免使用 for-in。除開 for-in,選擇循環(huán)應當基于需求而不是性能

減少每次迭代的操作總數(shù)可以大幅提高循環(huán)的整體性能

優(yōu)化循環(huán):

減少對象成員和數(shù)組項的查找,比如緩存數(shù)組長度,避免每次查找數(shù)組 length 屬性

倒序循環(huán)是編程語言中常用的性能優(yōu)化方法

編程中經(jīng)常會聽到此說法,現(xiàn)在來驗證一下,測試樣例

var arr = [];
for (var i = 0; i < 100000000; i++) {
  arr[i] = i;
}
var start = +new Date();
for (var j = arr.length; j > -1; j--) {
  arr[j] = j;
}
console.log("倒序循環(huán)耗時:%s ms", Date.now() - start); //約180 ms
var start = +new Date();
for (var j = 0; j < arr.length; j++) {
  arr[j] = j;
}
console.log("正序序循環(huán)耗時:%s ms", Date.now() - start); //約788 ms

基于函數(shù)的迭代

盡管基于函數(shù)的迭代顯得更加便利,它還是比基于循環(huán)的迭代要慢一些。每個數(shù)組項要關聯(lián)額外的函數(shù)調(diào)用是造成速度慢的原因。在所有情況下,基于函數(shù)的迭代占用時間是基于循環(huán)的迭代的八倍,因此在關注執(zhí)行時間的情況下它并不是一個合適的辦法。

條件表達式 if-else VS switch

使用 if-else 或者 switch 的流行理論是基于測試條件的數(shù)量:條件數(shù)量較大,傾向使用 switch,更易于閱讀
當條件體增加時,if-else 性能負擔增加的程度比 switch 更多。
一般來說,if-else 適用于判斷兩個離散的值或者幾個不同的值域,如果判斷條件較多 switch 表達式將是更理想的選擇

優(yōu)化 if-else

最小化找到正確分支:將最常見的條件放在首位

查表法 當使用查表法時,必須完全消除所有條件判斷,操作轉(zhuǎn)換成一個數(shù)組項查詢或者一個對象成員查詢。

遞歸

會受瀏覽器調(diào)用棧大小的限制

迭代

任何可以用遞歸實現(xiàn)的算法可以用迭代實現(xiàn)。使用優(yōu)化的循環(huán)替代長時間運行的遞歸函數(shù)可以提高性能,因為運行一個循環(huán)比反復調(diào)用一個函數(shù)的開銷要低

斐波那契

function fibonacci(n) {
  if (n === 1) return 1;
  if (n === 2) return 2;
  return fibonacci(n - 1) + fibonacci(n - 2);
}
制表
//制表
function memorize(fundamental, cache) {
  cache = cache || {};
  var shell = function(args) {
    if (!cache.hasOwnProperty(args)) {
      cache[args] = fundamental(args);
    }
    return cache[args];
  };
  return shell;
}
//動態(tài)規(guī)劃
function fibonacciOptimize(n) {
  if (n === 1) return 1;
  if (n === 2) return 2;
  var current = 2;
  var previous = 1;
  for (var i = 3; i <= n; i++) {
    var temp = current;
    current = previous + current;
    previous = temp;
  }
  return current;
}
//計算階乘
var res1 = fibonacci(40);
var res2 = memorize(fibonacci)(40);
var res3 = fibonacciOptimize(40);
//計算出來的res3優(yōu)于res2,res2優(yōu)于res1
總結(jié)
運行代碼的總量越大,優(yōu)化帶來的性能提升越明顯
正如其他編程語言,代碼的寫法與算法選用影響 JS 的運行時間,與其他編程語言不同,JS 可用資源有限,所以優(yōu)化固然重要

for, while, do while 循環(huán)的性能特性相似,誰也不比誰更快或更慢

除非要迭代遍歷一個屬性未知的對象,否則不要使用 for-in 循環(huán)

改善循環(huán)的最佳方式減少每次迭代中的運算量,并減少循環(huán)迭代次數(shù)

一般來說 switch 總比 if-else 更快,但總不是最好的解決方法

當判斷條件較多,查表法優(yōu)于 if-else 和 switch

瀏覽器的調(diào)用棧大小限制了遞歸算法在 js 中的應用,棧溢出導致其他代碼不能正常執(zhí)行

如果遇到棧溢出,將方法修改為制表法,可以避免重復工作

五、字符串和正則表達式 String And Regular Expression

在 JS 中,正則是必不可少的東西,它的重要性遠遠超過煩瑣的字符串處理

字符串鏈接 Stirng Concatenation

字符串連接表現(xiàn)出驚人的性能緊張。通常一個任務通過一個循環(huán),向字符串末尾不斷地添加內(nèi)容,來創(chuàng)建一個字符串(例如,創(chuàng)建一個 HTML 表或者一個 XML 文檔),但此類處理在一些瀏覽器上表現(xiàn)糟糕而遭人痛恨

Method Example
+ str = "a" + "b" + "c";
+= str = "a";
str += "b";
str += "c";
array.join() str = ["a","b","c"].join("");
string.concat() str = "a";
str = str.concat("b", "c");

當連接少量的字符串,上述的方式都很快,可根據(jù)自己的習慣使用;
當合并字符串的長度和數(shù)量增加之后,有些函數(shù)就開始發(fā)揮其作用了

+ & +=
str += "a" + "b";

此代碼執(zhí)行時,發(fā)生四個步驟

內(nèi)存中創(chuàng)建了一個臨時字符串

臨時字符串的值被賦予"ab"

臨時串與 str 進行連接

將結(jié)果賦予 str

下面的代碼通過兩個離散的表達式直接將內(nèi)容附加在 str 上避免了臨時字符串

str += "a";
str += "b";

事實上用一行代碼就可以解決

str = str + "a" + "b";

賦值表達式以 str 開頭,一次追加一個字符串,從左至右依次連接。如果改變了連接順序(例如:str = "a" + str + "b"),你會失去這種優(yōu)化,這與瀏覽器合并字符串時分配內(nèi)存的方法有關。除 IE 外,瀏覽器嘗試擴展表達式左端字符串的內(nèi)存,然后簡單地將第二個字符串拷貝到它的尾部。如果在一個循環(huán)中,基本字符串在左端,可以避免多次復制一個越來越大的基本字符串。

Array.prototype.join

Array.prototype.join 將數(shù)組的所有元素合并成一個字符串,并在每個元素之間插入一個分隔符字符串。若傳遞一個空字符串,可將數(shù)組的所有元素簡單的拼接起來

var start = Date.now();
var str = "I"m a thirty-five character string.",
  newStr = "",
  appends = 5000000;
while (appends--) {
  newStr += str;
}
var time = Date.now() - start;
console.log("耗時:" + time + "ms"); //耗時:1360ms
var start = Date.now();
var str = "I"m a thirty-five character string.",
  strs = [],
  newStr = "",
  appends = 5000000;
while (appends--) {
  strs[strs.length] = str;
}
newStr = strs.join("");
var time = Date.now() - start;
console.log("耗時:" + time + "ms"); //耗時:414ms

這一難以置信的改進結(jié)果是因為避免了重復的內(nèi)存分配和拷貝越來越大的字符串。

String.prototype.concat

原生字符串連接函數(shù)接受任意數(shù)目的參數(shù),并將每一個參數(shù)都追加在調(diào)用函數(shù)的字符串上

var str = str.concat(s1);
var str = str.concat(s1, s2, s3);
var str = String.prototype.concat.apply(str, array);

大多數(shù)情況下 concat 比簡單的+或+=慢一些

Regular Expression Optimization 正則表達式優(yōu)化

許多因素影響正則表達式的效率,首先,正則適配的文本千差萬別,部分匹配時比完全不匹配所用的時間要長,每種瀏覽器的正則引擎也有不同的內(nèi)部優(yōu)化

正則表達式工作原理

編譯
當你創(chuàng)建了一個正則表達式對象之后(使用一個正則表達式直接量或者 RegExp 構(gòu)造器),瀏覽器檢查你的模板有沒有錯誤,然后將它轉(zhuǎn)換成一個本機代碼例程,用執(zhí)行匹配工作。如果你將正則表達式賦給一個變量,你可以避免重復執(zhí)行此步驟。

設置起始位置
當一個正則表達式投入使用時,首先要確定目標字符串中開始搜索的位置。它是字符串的起始位置,或者由正則表達式的 lastIndex 屬性指定,但是當它從第四步返回到這里的時候(因為嘗試匹配失?。?,此位置將位于最后一次嘗試起始位置推后一個字符的位置上

匹配每個正則表達式的字元
正則表達式一旦找好起始位置,它將一個一個地掃描目標文本和正則表達式模板。當一個特定字元匹配失敗時,正則表達式將試圖回溯到掃描之前的位置上,然后進入正則表達式其他可能的路徑上

匹配成功或失敗
如果在字符串的當前位置上發(fā)現(xiàn)一個完全匹配,那么正則表達式宣布成功。如果正則表達式的所有可能路徑都嘗試過了,但是沒有成功地匹配,那么正則表達式引擎回到第二步,從字符串的下一個字符重新嘗試。只有字符串中的每個字符(以及最后一個字符后面的位置)都經(jīng)歷了這樣的過程之后,還沒有成功匹配,那么正則表達式就宣布徹底失敗。

理解回溯

在大多數(shù)現(xiàn)代正則表達式實現(xiàn)中(包括 JavaScript 所需的),回溯是匹配過程的基本組成部分。它很大程度上也是正則表達式如此美好和強大的根源。然而,回溯計算代價昂貴,如果你不夠小心的話容易失控。雖然回溯是整體性能的唯一因素,理解它的工作原理,以及如何減少使用頻率,可能是編寫高效正則表達式最重要的關鍵點。

正則表達式匹配過程

當一個正則表達式掃描目標字符串時,它從左到右逐個掃描正則表達式的組成部分,在每個位置上測試能不能找到一個匹配。對于每一個量詞和分支,都必須決定如何繼續(xù)進行。如果是一個量詞(諸如*,+?,或者{2,}),正則表達式必須決定何時嘗試匹配更多的字符;如果遇到分支(通過|操作符),它必須從這些選項中選擇一個進行嘗試。

每當正則表達式做出這樣的決定,如果有必要的話,它會記住另一個選項,以備將來返回后使用。如果所選方案匹配成功,正則表達式將繼續(xù)掃描正則表達式模板,如果其余部分匹配也成功了,那么匹配就結(jié)束了。但是如果所選擇的方案未能發(fā)現(xiàn)相應匹配,或者后來的匹配也失敗了,正則表達式將回溯到最后一個決策點,然后在剩余的選項中選擇一個。它繼續(xù)這樣下去,直到找到一個匹配,或者量詞和分支選項的所有可能的排列組合都嘗試失敗了,那么它將放棄這一過程,然后移動到此過程開始位置的下一個字符上,重復此過程。

示例分析

/h(ello|appy) hippo/.test("hello there, happy hippo");

此正則表達式匹配“hello hippo”或“happy hippo”。測試一開始,它要查找一個 h,目標字符串的第一個字母恰好就是 h,它立刻就被找到了。接下來,子表達式(ello|appy)提供了兩個處理選項。正則表達式選擇最左邊的選項(分支選擇總是從左到右進行),檢查 ello 是否匹配字符串的下一個字符。確實匹配,然后正則表達式又匹配了后面的空格。然而在這一點上它走進了死胡同,因為 hippo 中的 h 不能匹配字符串中的下一個字母 t。此時正則表達式還不能放棄,因為它還沒有嘗試過所有的選擇,隨后它回溯到最后一個檢查點(在它匹配了首字母 h 之后的那個位置上)并嘗試匹配第二個分支選項。但是沒有成功,而且也沒有更多的選項了,所以正則表達式認為從字符串的第一個字符開始匹配是不能成功的,因此它從第二個字符開始,重新進行查找。它沒有找到 h,所以就繼續(xù)向后找,直到第 14 個字母才找到,它匹配 happy 的那個 h。然后它再次進入分支過程。這次 ello 未能匹配,但是回溯之后第二次分支過程中,它匹配了整個字符串“happy hippo”(如圖 5-4)。匹配成功了。

回溯失控

當一個正則表達式占用瀏覽器上秒,上分鐘或者更長時間時,問題原因很可能是回溯失控。正則表達式處理慢往往是因為匹配失敗過程慢,而不是匹配成功過程慢。

var reg = /[sS]*?[sS]*?[sS]*?[sS]*?[sS]*?[sS]*?[sS]*?/;
//優(yōu)化如下
var regOptimize = /(?=([sS]*?))1(?=([sS]*?))2(?=([sS]*?))3(?=([sS]*?))4(?=([sS]*?))5(?=([sS]*?))6[sS]*?/;

現(xiàn)在如果沒有尾隨的那么最后一個[sS]*?將擴展至字符串結(jié)束,正則表達式將立刻失敗因為沒有回溯點可以返回

提高正則表達式效率的更多方法

關注如何讓匹配更快失敗

正則表達式以簡單的,必需的字元開始

編寫量詞模板,使它們后面的字元互相排斥

減少分支的數(shù)量,縮小它們的范圍

使用非捕獲組

捕獲感興趣的文字,減少后處理

暴露所需的字元

使用適當?shù)牧吭~

將正則表達式賦給變量,以重用它們

將復雜的正則表達式拆分為簡單的片斷

什么時候不應該使用正則表達式
var endsWithSemicolon = /;$/.test(str);

你可能覺得很奇怪,雖說當前沒有哪個瀏覽器聰明到這個程度,能夠意識到這個正則表達式只能匹配字符串的末尾。最終它們所做的將是一個一個地測試了整個字符串。字符串的長度越長(包含的分號越多),它占用的時間也越長

var endsWithSemicolon = str.charAt(str.length - 1) == ";";

這種情況下,更好的辦法是跳過正則表達式所需的所有中間步驟,簡單地檢查最后一個字符是不是分號:

這個例子使用 charAt 函數(shù)在特定位置上讀取字符。字符串函數(shù) slice,substr,和 substring 可用于在特定位置上提取并檢查字符串的值

所有這些字符串操作函數(shù)速度都很快,當您搜索那些不依賴正則表達式復雜特性的文本字符串時,它們有助于您避免正則表達式帶來的性能開銷

字符串修剪

正則表達式允許你用很少的代碼實現(xiàn)一個修剪函數(shù),這對 JavaScript 關心文件大小的庫來說十分重要??赡茏詈玫娜娼鉀Q方案是使用兩個子表達式:一個用于去除頭部空格,另一個用于去除尾部空格。這樣處理簡單而迅速,特別是處理長字符串時。

//方法 用正則表達式修剪
// trim1
String.prototype.trim = function() {
  return this.replace(/^s+/, "").replace(/s+$/, "");
};
//trim2
String.prototype.trim = function() {
  return this.replace(/^s+|s+$/g, "");
};
// trim 3
String.prototype.trim = function() {
  return this.replace(/^s*([sS]*?)s*$/, "$1");
};
// trim 4
String.prototype.trim = function() {
  return this.replace(/^s*([sS]*S)?s*$/, "$1");
};
// trim 5
String.prototype.trim = function() {
  return this.replace(/^s*(S*(s+S+)*)s*$/, "$1");
};
//方法二 不使用正則表達式修剪
String.prototype.trim = function() {
  var start = 0;
  var end = this.length - 1;
  //ws 變量包括 ECMAScript 5 中定義的所有空白字符
  var ws =
    "

	fx0bxa0u1680u180eu2000u2001u2002u2003u2004u2005u2006u2007u2008u2009u200au200bu2028u2029u202fu205fu3000ufeff";
  while (ws.indexOf(this.charAt(start)) > -1) {
    start++;
  }
  while (end > start && ws.indexOf(this.charAt(end)) > -1) {
    end--;
  }
  return this.slice(start, end + 1);
};
//方法三 混合解決方案
String.prototype.trim = function() {
  var str = this.replace(/^s+/, ""),
    end = str.length - 1,
    ws = /s/;
  while (ws.test(str.charAt(end))) {
    end--;
  }
  return str.slice(0, end + 1);
};

簡單地使用兩個子正則表達式在所有瀏覽器上處理不同內(nèi)容和長度的字符串時,均表現(xiàn)出穩(wěn)定的性能。因此它可以說是最全面的解決方案?;旌辖鉀Q方案在處理長字符串時特別快,其代價是代碼稍長,在某些瀏覽器上處理尾部長空格時存在弱點

總結(jié)

使用簡單的+和+=取代數(shù)組聯(lián)合,可避免(產(chǎn)生)不必要的中間字符串

當連接數(shù)量巨大或尺寸巨大的字符串時,使用數(shù)組聯(lián)合

使相鄰字元互斥,避免嵌套量詞對一個字符串的相同部分多次匹配,通過重復利用前瞻操作的原子特性去除不必要的回溯

六、響應接口

用戶傾向于重復嘗試這些不發(fā)生明顯變化的動作,所以確保網(wǎng)頁應用程序的響應速度也是一個重要的性能關注點

瀏覽器 UI 線程

JavaScript 和 UI 更新共享的進程通常被稱作瀏覽器 UI 線程, UI 線程圍繞著一個簡單的隊列系統(tǒng)工作,任務被保存到隊列中直至進程空閑。一旦空閑,隊列中的下一個任務將被檢索和運行。這些任務不是運行 JavaScript 代碼,就是執(zhí)行 UI 更新,包括重繪和重排版.
大多數(shù)瀏覽器在 JavaScript 運行時停止 UI 線程隊列中的任務,也就是說 JavaScript 任務必須盡快結(jié)束,以免對用戶體驗造成不良影響

Brendan Eich,JavaScript 的創(chuàng)造者,引用他的話說,“[JavaScript]運行了整整幾秒鐘很可能是做錯了什么……”
定時器基礎

定時器與 UI 線程交互的方式有助于分解長運行腳本成為較短的片斷

定時器精度

所有瀏覽器試圖盡可能準確,但通常會發(fā)生幾毫秒滑移,或快或慢。正因為這個原因,定時器不可用于測量實際時間

總結(jié)

JavaScript 運行時間不應該超過 100 毫秒。過長的運行時間導致 UI 更新出現(xiàn)可察覺的延遲,從而對整體用戶體驗產(chǎn)生負面影響

JavaScript 運行期間,瀏覽器響應用戶交互的行為存在差異。無論如何,JavaScript 長時間運行將導致用戶體驗混亂和脫節(jié)。

同一時間只有一個定時器存在,只有當這個定時器結(jié)束時才創(chuàng)建一個新的定時器。以這種方式使用定時器不會帶來性能問題

定時器可用于安排代碼推遲執(zhí)行,它使得你可以將長運行腳本分解成一系列較小的任務

七、Ajax

目前最常用的方法中,XMLHttpRequest(XHR)用來異步收發(fā)數(shù)據(jù)。所有現(xiàn)代瀏覽器都能夠很好地支持它,而且能夠精細地控制發(fā)送請求和數(shù)據(jù)接收。你可以向請求報文中添加任意的頭信息和參數(shù)(包括 GET 和 POST),并讀取從服務器返回的頭信息,以及響應文本自身

請求數(shù)據(jù)

五種常用技術(shù)用于向服務器請求數(shù)據(jù)

XMLHttpRequest (XHR)

Dynamic script tag insertion 動態(tài)腳本標簽插入

iframes

Comet

Multipart XHR 多部分的 XHR

XMLHttpRequest
//封裝ajax
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status >= 200) {
    //
  }
};
xhr.open(type, url, true);
xhr.setRequestHeader("Content-Type", contentType);
xhr.send(null);
動態(tài)腳本標簽插入 發(fā)送數(shù)據(jù)

XMLHttpRequest

圖像燈標

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

通過 Douglas Crockford 的發(fā)明與推廣,JSON 是一個輕量級并易于解析的數(shù)據(jù)格式,它按照 JavaScript 對象和數(shù)組字面語法所編寫

Ajax 性能向?qū)?/b>

數(shù)據(jù)傳輸技術(shù)和數(shù)據(jù)格式

緩存數(shù)據(jù)

設置 HTTP 頭

本地存儲數(shù)據(jù)

總結(jié)

高性能 Ajax 包括:知道你項目的具體需求,選擇正確的數(shù)據(jù)格式和與之相配的傳輸技術(shù)

減少請求數(shù)量,可合并 js 和 css 文件

縮短頁面的加載時間,在頁面其它內(nèi)容加載之后,使用 Ajax 獲取少量重要文件

JSON 是高性能 AJAX 的基礎,尤其在使用動態(tài)腳本注入時

學會何時使用一個健壯的 Ajax 庫,何時編寫自己的底層 Ajax 代碼

封裝自己的 ajax 庫

(function(root) {
  root.MyAjax = (config = {}) => {
    let url = config.url;
    let type = config.type || "GET";
    let async = config.async || true;
    let headers = config.headers || [];
    let contentType = config.contentType || "application/json;charset=utf-8";
    let data = config.data;
    let dataType = config.dataType || "json";
    let successFn = config.success;
    let errorFn = config.error;
    let completeFn = config.complete;
    let xhr;
    if (window.XMLHttpRequest) {
      xhr = new XMLHttpRequest();
    } else {
      xhr = new ActiveXObject("Microsoft.XMLHTTP");
    }
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          let rsp = xhr.responseText || xhr.responseXML;
          if (dataType === "json") {
            rsp = eval("(" + rsp + ")");
          }
          successFn(rsp, xhr.statusText, xhr);
        } else {
          errorFn(xhr.statusText, xhr);
        }
        if (completeFn) {
          completeFn(xhr.statusText, xhr);
        }
      }
    };
    xhr.open(type, url, async);
    //設置超時
    if (async) {
      xhr.timeout = config.timeout || 0;
    }
    //設置請求頭
    for (let i = 0; i < headers.length; ++i) {
      xhr.setRequestHeader(headers[i].name, headers[i].value);
    }
    xhr.setRequestHeader("Content-Type", contentType);
    //send
    if (
      typeof data == "object" &&
      contentType === "application/x-www-form-urlencoded"
    ) {
      let s = "";
      for (attr in data) {
        s += attr + "=" + data[attr] + "&";
      }
      if (s) {
        s = s.slice(0, s.length - 1);
      }
      xhr.send(s);
    } else {
      xhr.send(data);
    }
  };
})(window);
八、編程實踐

避免二次評估,比如 eval,F(xiàn)unction

使用對象/數(shù)組直接量

不要重復工作

延遲加載

條件預加載

使用速度快的部分

位操作運算符
四種位邏輯操作符

位與
比如判斷數(shù)奇偶

num % 2 === 0; //取模與0進行判斷
num & 1; //位與1結(jié)果位1則為奇數(shù),為0則為偶數(shù)

位或

位異或

位非

位掩碼
位掩碼在計算機科學中是一種常用的技術(shù),可同時判斷多個布爾 選項,快速地將數(shù)字轉(zhuǎn)換為布爾標志數(shù)組。掩碼中每個選項的值都等于 2 的冪

var OPTION_A = 1;
var OPTION_B = 2;
var OPTION_C = 4;
var OPTION_D = 8;
var OPTION_E = 16;

通過定義這些選項,你可以用位或操作創(chuàng)建一個數(shù)字來包含多個選項:

var options = OPTION_A | OPTION_C | OPTION_D;

可以使用位與操作檢查一個給定的選項是否可用

//is option A in the list?
if (options & OPTION_A) {
  //do something
}
//is option B in the list?
if (options & OPTION_B) {
  //do something
}

像這樣的位掩碼操作非???,正因為前面提到的原因,操作發(fā)生在系統(tǒng)底層。如果許多選項保存在一起并經(jīng)常檢查,位掩碼有助于加快整體性能

原生方法

無論你怎樣優(yōu)化 JavaScript 代碼,它永遠不會比 JavaScript 引擎提供的原生方法更快。經(jīng)驗不足的 JavaScript 開發(fā)者經(jīng)常犯的一個錯誤是在代碼中進行復雜的數(shù)學運算,而沒有使用內(nèi)置 Math 對象中那些性能更好的版本。Math 對象包含專門設計的屬性和方法,使數(shù)學運算更容易。

//查看Math對象所有方法
Object.getOwnPropertyNames(Math);
總結(jié)

通過避免使用 eval()和 Function()構(gòu)造器避免二次評估。此外,給 setTimeout()和 setInterval()傳遞函數(shù)參數(shù)而不是字符串參數(shù)。

創(chuàng)建新對象和數(shù)組時使用對象直接量和數(shù)組直接量。它們比非直接量形式創(chuàng)建和初始化更快。

避免重復進行相同工作。當需要檢測瀏覽器時,使用延遲加載或條件預加載

當執(zhí)行數(shù)學遠算時,考慮使用位操作,它直接在數(shù)字底層進行操作。

原生方法總是比 JavaScript 寫的東西要快。盡量使用原生方法

九、創(chuàng)建并部署高性能 JavaScript 應用程序

合并 js 文件,減少 HTTP 請求的數(shù)量

以壓縮形式提供 js 文件(gzip 編碼)

通過設置 HTTP 響應報文頭使 js 文件可緩存,通過向文件名附加時間戳解決緩存問題

使用CDN提供 js 文件,CDN 不僅可以提高性能,它還可以為你管理壓縮和緩存

十、工具

當網(wǎng)頁或應用程序變慢時,分析網(wǎng)上傳來的資源,分析腳本的運行性能,使你能夠集中精力在那些需要努力優(yōu)化的地方。

使用網(wǎng)絡分析器找出加載腳本和其它頁面資源的瓶頸所在,這有助于決定哪些腳本需要延遲加載,或者進行進一步分析

盡量延遲加載腳本以使頁面渲染速度更快,向用戶提供更好的整體體驗。

使用性能分析器找出腳本運行時速度慢的部分,檢查每個函數(shù)所花費的時間,以及函數(shù)被調(diào)用的次數(shù),通過調(diào)用棧自身提供的一些線索來找出哪些地方應當努力優(yōu)化

后記

能讀到最后的同學也不容易,畢竟篇幅稍長。本書大概花了三周的零碎時間讀完,建議大家讀一讀。如果大家在看書過程中存在疑問,不妨打開電腦驗證書中作者的言論,或許會更加深刻。

若文中有錯誤歡迎大家評論指出,或者加我微信好友一起交流gm4118679254

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

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/104796.html

相關文章

  • 王下邀月熊_Chevalier的前端每周清單系列文章索引

    摘要:感謝王下邀月熊分享的前端每周清單,為方便大家閱讀,特整理一份索引。王下邀月熊大大也于年月日整理了自己的前端每周清單系列,并以年月為單位進行分類,具體內(nèi)容看這里前端每周清單年度總結(jié)與盤點。 感謝 王下邀月熊_Chevalier 分享的前端每周清單,為方便大家閱讀,特整理一份索引。 王下邀月熊大大也于 2018 年 3 月 31 日整理了自己的前端每周清單系列,并以年/月為單位進行分類,具...

    2501207950 評論0 收藏0
  • 前端性能優(yōu)化--頁面渲染優(yōu)化全面解析

    摘要:下面我們撇開網(wǎng)絡方面的優(yōu)化,只分析靜態(tài)資源方面的優(yōu)化。不過,也會阻止的構(gòu)建和延緩網(wǎng)頁渲染。未優(yōu)化正常加載優(yōu)化后異步加載根據(jù)上面的分析,我們可以清楚的認識到,非必要優(yōu)先加載的,選擇異步加載是最優(yōu)選擇。 為什么做優(yōu)化 經(jīng)典問題:白屏時間過長,用戶體驗差產(chǎn)生的原因:網(wǎng)絡問題、關鍵渲染路徑(CRP)問題 怎么做優(yōu)化 如何做好優(yōu)化呢,網(wǎng)上隨便一搜,就有很多優(yōu)化總結(jié),無非就是網(wǎng)絡優(yōu)化、靜態(tài)資源(h...

    MadPecker 評論0 收藏0
  • 前端性能優(yōu)化--頁面渲染優(yōu)化全面解析

    摘要:下面我們撇開網(wǎng)絡方面的優(yōu)化,只分析靜態(tài)資源方面的優(yōu)化。不過,也會阻止的構(gòu)建和延緩網(wǎng)頁渲染。未優(yōu)化正常加載優(yōu)化后異步加載根據(jù)上面的分析,我們可以清楚的認識到,非必要優(yōu)先加載的,選擇異步加載是最優(yōu)選擇。 為什么做優(yōu)化 經(jīng)典問題:白屏時間過長,用戶體驗差產(chǎn)生的原因:網(wǎng)絡問題、關鍵渲染路徑(CRP)問題 怎么做優(yōu)化 如何做好優(yōu)化呢,網(wǎng)上隨便一搜,就有很多優(yōu)化總結(jié),無非就是網(wǎng)絡優(yōu)化、靜態(tài)資源(h...

    gghyoo 評論0 收藏0
  • 前端性能優(yōu)化--頁面渲染優(yōu)化全面解析

    摘要:下面我們撇開網(wǎng)絡方面的優(yōu)化,只分析靜態(tài)資源方面的優(yōu)化。不過,也會阻止的構(gòu)建和延緩網(wǎng)頁渲染。未優(yōu)化正常加載優(yōu)化后異步加載根據(jù)上面的分析,我們可以清楚的認識到,非必要優(yōu)先加載的,選擇異步加載是最優(yōu)選擇。 為什么做優(yōu)化 經(jīng)典問題:白屏時間過長,用戶體驗差產(chǎn)生的原因:網(wǎng)絡問題、關鍵渲染路徑(CRP)問題 怎么做優(yōu)化 如何做好優(yōu)化呢,網(wǎng)上隨便一搜,就有很多優(yōu)化總結(jié),無非就是網(wǎng)絡優(yōu)化、靜態(tài)資源(h...

    gaomysion 評論0 收藏0

發(fā)表評論

0條評論

Coding01

|高級講師

TA的文章

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