javasciprt性能優(yōu)化

本文主要是在我讀《高性能Javascript》之后辜膝,想要記錄下一些有用的優(yōu)化方案贤重,并且就我本身的一些經(jīng)驗(yàn),來(lái)大家一起分享下姑尺,

Javascript的加載與執(zhí)行

大家都知道竟终,瀏覽器在解析DOM樹的時(shí)候,當(dāng)解析到script標(biāo)簽的時(shí)候切蟋,會(huì)阻塞其他的所有任務(wù)统捶,直到該js文件下載、解析執(zhí)行完成后柄粹,才會(huì)繼續(xù)往下執(zhí)行喘鸟。因此,這個(gè)時(shí)候?yàn)g覽器就會(huì)被阻塞在這里驻右,如果將script標(biāo)簽放在head里的話什黑,那么在該js文件加載執(zhí)行前,用戶只能看到空白的頁(yè)面堪夭,這樣的用戶體驗(yàn)肯定是特別爛愕把。對(duì)此拣凹,常用的方法有以下:

  • 將所有的script標(biāo)簽都放到body最底部,這樣可以保證js文件是最后加載并執(zhí)行的恨豁,可以先將頁(yè)面展現(xiàn)給用戶嚣镜。但是,你首先得清楚圣絮,頁(yè)面的首屏渲染是否依賴于你的部分js文件祈惶,如果是的話雕旨,則需要將這一部分js文件放到head上扮匠。
  • 使用defer,比如下面這種寫法。使用defer這種寫法時(shí)凡涩,雖然瀏覽器解析到該標(biāo)簽的時(shí)候棒搜,也會(huì)下載對(duì)應(yīng)的js文件,不過(guò)它并不會(huì)馬上執(zhí)行活箕,而是會(huì)等到DOM解析完后(DomContentLoader之前)才會(huì)執(zhí)行這些js文件力麸。因此,就不會(huì)阻塞到瀏覽器育韩。
<script src="test.js" type="text/javascript" defer></script>
  • 動(dòng)態(tài)加載js文件,通過(guò)這種方式克蚂,可以在頁(yè)面加載完成后,再去加載所需要的代碼筋讨,也可以通過(guò)這種方式實(shí)現(xiàn)js文件懶加載/按需加載埃叭,比如現(xiàn)在比較常見的,就是webpack結(jié)合vue-router/react-router實(shí)現(xiàn)按需加載悉罕,只有訪問(wèn)到具體路由的時(shí)候赤屋,才加載相應(yīng)的代碼。具體的方法如下:

1.動(dòng)態(tài)的插入script標(biāo)簽來(lái)加載腳本,比如通過(guò)以下代碼

  function loadScript(url, callback) {
    const script = document.createElement('script')壁袄;
    script.type = 'text/javascript';
    // 處理IE
    if (script.readyState) {
      script.onreadystatechange = function () {
        if (script.readyState === 'loaded' || script.readyState === 'complete') {
          script.onreadystatechange = null;
          callback();
        }
      }
    } else {
      // 處理其他瀏覽器的情況
      script.onload = function () {
        callback();
      }
    }
    script.src = url;
    document.body.append(script);
  }

  // 動(dòng)態(tài)加載js
  loadScript('file.js', function () {
    console.log('加載完成');
  })

2.通過(guò)xhr方式加載js文件类早,不過(guò)通過(guò)這種方式的話,就可能會(huì)面臨著跨域的問(wèn)題嗜逻。例子如下:

  const xhr = new XMLHttpRequest();
  xhr.open('get', 'file.js');
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
      if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304>) {
        const script = document.createElement('script');
        script.type = 'text/javascript';
        script.text = xhr.responseText;
        document.body.append(script);
      }
    }
  }

3.將多個(gè)js文件合并為同一個(gè)涩僻,并且進(jìn)行壓縮。 原因:目前瀏覽器大多已經(jīng)支持并行下載js文件了栈顷,但是并發(fā)下載還是有一定的數(shù)量限制了(基于瀏覽器令哟,一部分瀏覽器只能下載4個(gè)),并且妨蛹,每一個(gè)js文件都需要建立一次額外的http連接屏富,加載4個(gè)25KB的文件比起加載一個(gè)100KB的文件消耗的時(shí)間要大。因此蛙卤,我們最好就是將多個(gè)js文件合并為同一個(gè)狠半,并且進(jìn)行代碼壓縮噩死。

javascript作用域

當(dāng)一個(gè)函數(shù)執(zhí)行的時(shí)候,會(huì)生成一個(gè)執(zhí)行上下文神年,這個(gè)執(zhí)行上下文定義了函數(shù)執(zhí)行時(shí)的環(huán)境已维。當(dāng)函數(shù)執(zhí)行完畢后,這個(gè)執(zhí)行上下文就會(huì)被銷毀已日。因此垛耳,多次調(diào)用同一個(gè)函數(shù)會(huì)導(dǎo)致創(chuàng)建多個(gè)執(zhí)行上下文。每隔執(zhí)行上下文都有自己的作用域鏈飘千。相信大家應(yīng)該早就知道了作用域這個(gè)東西堂鲜,對(duì)于一個(gè)函數(shù)而言,其第一個(gè)作用域就是它函數(shù)內(nèi)部的變量护奈。在函數(shù)執(zhí)行過(guò)程中缔莲,每遇到一個(gè)變量,都會(huì)搜索函數(shù)的作用域鏈找到第一個(gè)匹配的變量霉旗,首先查找函數(shù)內(nèi)部的變量痴奏,之后再沿著作用域鏈逐層尋找。因此厌秒,若我們要訪問(wèn)最外層的變量(全局變量)读拆,則相比直接訪問(wèn)內(nèi)部的變量而言,會(huì)帶來(lái)比較大的性能損耗?鸵闪。因此檐晕,我們可以將經(jīng)常使用的全局變量引用儲(chǔ)存在一個(gè)局部變量里

const a = 5;
function outter () {
  const a = 2;
  function inner () {
    const b = 2;
    console.log(b); // 2
    console.log(a); // 2
  }
  inner();
}

對(duì)象的讀取

javascript中岛马,主要分為字面量棉姐、局部變量、數(shù)組元素和對(duì)象這四種啦逆。訪問(wèn)字面量和局部變量的速度最快伞矩,而訪問(wèn)數(shù)組元素和對(duì)象成員相對(duì)較慢。而訪問(wèn)對(duì)象成員的時(shí)候夏志,就和作用域鏈一樣乃坤,是在原型鏈(prototype)上進(jìn)行查找。因此沟蔑,若查找的成員在原型鏈位置太深湿诊,則訪問(wèn)速度越慢。因此瘦材,我們應(yīng)該盡可能的減少對(duì)象成員的查找次數(shù)和嵌套深度厅须。比如以下代碼

  // 進(jìn)行兩次對(duì)象成員查找
  function hasEitherClass(element, className1, className2) {
    return element.className === className1 || element.className === className2;
  }
  // 優(yōu)化,如果該變量不會(huì)改變食棕,則可以使用局部變量保存查找的內(nèi)容
  function hasEitherClass(element, className1, className2) {
    const currentClassName = element.className;
    return currentClassName === className1 || currentClassName === className2;
  }

DOM操作優(yōu)化

  • 最小化DOM的操作次數(shù)朗和,盡可能的用javascript來(lái)處理错沽,并且盡可能的使用局部變量?jī)?chǔ)存DOM節(jié)點(diǎn)。比如以下的代碼:
  // 優(yōu)化前眶拉,在每次循環(huán)的時(shí)候千埃,都要獲取id為t的節(jié)點(diǎn),并且設(shè)置它的innerHTML
  function innerHTMLLoop () {
    for (let count = 0; count < 15000; count++) {
      document.getElementById('t').innerHTML += 'a';
    }
  }
  // 優(yōu)化后忆植,
  function innerHTMLLoop () {
    const tNode = document.getElemenById('t');
    const insertHtml = '';
    for (let count = 0; count < 15000; count++) {
      insertHtml += 'a';
    }
    tNode.innerHtml += insertHtml;
  }
  • 盡可能的減少重排和重繪,重排和重匯可能會(huì)代價(jià)非常昂貴放可,因此,為了減少重排重匯的發(fā)生次數(shù)朝刊,我們可以做以下的優(yōu)化

1.當(dāng)我們要對(duì)Dom的樣式進(jìn)行修改的時(shí)候耀里,我們應(yīng)該盡可能的合并所有的修改并且一次處理,減少重排和重匯的次數(shù)坞古。

  // 優(yōu)化前
  const el = document.getElementById('test');
  el.style.borderLeft = '1px';
  el.style.borderRight = '2px';
  el.style.padding = '5px';

  // 優(yōu)化后,一次性修改樣式备韧,這樣可以將三次重排減少到一次重排
  const el = document.getElementById('test');
  el.style.cssText += '; border-left: 1px ;border-right: 2px; padding: 5px;'

2.當(dāng)我們要批量修改DOM節(jié)點(diǎn)的時(shí)候劫樟,我們可以將DOM節(jié)點(diǎn)隱藏掉痪枫,然后進(jìn)行一系列的修改操作,之后再將其設(shè)置為可見叠艳,這樣就可以最多只進(jìn)行兩次重排奶陈。具體的方法如下:

  // 未優(yōu)化前
  const ele = document.getElementById('test');
  // 一系列dom修改操作

  // 優(yōu)化方案一,將要修改的節(jié)點(diǎn)設(shè)置為不顯示附较,之后對(duì)它進(jìn)行修改吃粒,修改完成后再顯示該節(jié)點(diǎn),從而只需要兩次重排
  const ele = document.getElementById('test');
  ele.style.display = 'none';
  // 一系列dom修改操作
  ele.style.display = 'block';

  // 優(yōu)化方案二拒课,首先創(chuàng)建一個(gè)文檔片段(documentFragment),然后對(duì)該片段進(jìn)行修改徐勃,之后將文檔片段插入到文檔中,只有最后將文檔片段插入文檔的時(shí)候會(huì)引起重排,因此只會(huì)觸發(fā)一次重排早像。僻肖。
  const fragment = document.createDocumentFragment();
  const ele = document.getElementById('test');
  // 一系列dom修改操作
  ele.appendChild(fragment);

3.使用事件委托:事件委托就是將目標(biāo)節(jié)點(diǎn)的事件移到父節(jié)點(diǎn)來(lái)處理,由于瀏覽器冒泡的特點(diǎn)卢鹦,當(dāng)目標(biāo)節(jié)點(diǎn)觸發(fā)了該事件的時(shí)候臀脏,父節(jié)點(diǎn)也會(huì)觸發(fā)該事件。因此冀自,由父節(jié)點(diǎn)來(lái)負(fù)責(zé)監(jiān)聽和處理該事件揉稚。那么,它的優(yōu)點(diǎn)在哪里呢熬粗?假設(shè)你有一個(gè)列表搀玖,里面每一個(gè)列表項(xiàng)都需要綁定相同的事件,而這個(gè)列表可能會(huì)頻繁的插入和刪除驻呐。如果按照平常的方法灌诅,你只能給每一個(gè)列表項(xiàng)都綁定一個(gè)事件處理器葛超,并且,每當(dāng)插入新的列表項(xiàng)的時(shí)候延塑,你也需要為新的列表項(xiàng)注冊(cè)新的事件處理器绣张。這樣的話,如果列表項(xiàng)很大的話关带,就會(huì)導(dǎo)致有特別多的事件處理器侥涵,造成極大的性能問(wèn)題。而通過(guò)事件委托宋雏,我們只需要在列表項(xiàng)的父節(jié)點(diǎn)監(jiān)聽這個(gè)事件芜飘,由它來(lái)統(tǒng)一處理就可以了。這樣磨总,對(duì)于新增的列表項(xiàng)也不需要做額外的處理嗦明。而且事件委托的用法其實(shí)也很簡(jiǎn)單:

function handleClick(target) {
  // 點(diǎn)擊列表項(xiàng)的處理事件
}
function delegate (e) {
  // 判斷目標(biāo)對(duì)象是否為列表項(xiàng)
  if (e.target.nodeName === 'LI') {
    handleClick(e.target);
  }
}
const parent = document.getElementById('parent');
parent.addEventListener('click', delegate);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蚪燕,隨后出現(xiàn)的幾起案子娶牌,更是在濱河造成了極大的恐慌,老刑警劉巖馆纳,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诗良,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鲁驶,警方通過(guò)查閱死者的電腦和手機(jī)鉴裹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)钥弯,“玉大人径荔,你說(shuō)我怎么就攤上這事〈圉” “怎么了总处?”我有些...
    開封第一講書人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)绪穆。 經(jīng)常有香客問(wèn)我辨泳,道長(zhǎng),這世上最難降的妖魔是什么玖院? 我笑而不...
    開封第一講書人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任菠红,我火速辦了婚禮,結(jié)果婚禮上难菌,老公的妹妹穿的比我還像新娘试溯。我一直安慰自己,他們只是感情好郊酒,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開白布遇绞。 她就那樣靜靜地躺著键袱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪摹闽。 梳的紋絲不亂的頭發(fā)上蹄咖,一...
    開封第一講書人閱讀 52,584評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音付鹿,去河邊找鬼澜汤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛舵匾,可吹牛的內(nèi)容都是我干的俊抵。 我是一名探鬼主播,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼坐梯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼徽诲!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起吵血,我...
    開封第一講書人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤谎替,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后践瓷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體院喜,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亡蓉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年晕翠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砍濒。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡淋肾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出爸邢,到底是詐尸還是另有隱情樊卓,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布杠河,位于F島的核電站碌尔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏券敌。R本人自食惡果不足惜唾戚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望待诅。 院中可真熱鬧叹坦,春花似錦、人聲如沸卑雁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至莹捡,卻和暖如春鬼吵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背篮赢。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工而柑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荷逞。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓媒咳,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親种远。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涩澡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容