前端開發(fā)中導致頁面卡頓的因素

之前在做實習的一個項目的時候碰到了一個讓我抓狂的BUG孔飒,需求是讓我實現(xiàn)一個實時刷新獲取消息列表的功能灌闺,但是后臺返回的接口數(shù)據(jù)是成百上千的,頁面卡的不得了坏瞄,筆記本的風扇都呼呼的桂对,所以這篇博客是為了總結一下導致頁面卡頓的原因。


一.頁面卡頓的原因大體上可以分為兩種類型

1.渲染不及時鸠匀,頁面掉幀

  • 長時間占用js線程
  • 頁面回流和重繪較多
  • 資源加載堵塞

我覺得這就是我上次碰到的哪個問題的原因蕉斜,每一次請求時間差不多有2s左右,js長時間占用線程缀棍,而且加載的數(shù)據(jù)很多宅此,頁面回流與重繪嚴重,上一次請求還沒結束爬范,這一次請求又發(fā)起了父腕,也造成了資源加載堵塞,多種原因?qū)е挛业捻撁婵ū罎⒘恕?br> 2.網(wǎng)頁內(nèi)存占用過高青瀑,運行卡頓

內(nèi)存泄漏導致內(nèi)存過大

  • 意外的全局變量引起的內(nèi)存泄漏
  • 閉包引起的內(nèi)存泄漏
  • 被遺忘的定時器
  • 循環(huán)引用
  • DOM 刪除時沒有解綁事件
  • 沒有清理的DOM元素引用

dom節(jié)點或事件占用內(nèi)存過大

二.詳細解讀-渲染不及時的問題

1.長時間占用js線程

瀏覽器包括js線程和GUI線程璧亮,而且二者是互斥的,當長時間占用js線程時斥难,會導致渲染不及時枝嘶,出現(xiàn)頁面卡頓

  • 1.1同步方式獲取數(shù)據(jù),會導致gui線程掛起蘸炸,數(shù)據(jù)返回后再執(zhí)行渲染
$.ajax({
   url: '',
   async: false
})
  • 1.2計算時間過長導致頁面渲染不及時

很明顯的例子就是我遇到的哪個BUG躬络,2S的響應時間,不卡才怪搭儒。
渲染不及時的原因:
瀏覽器的渲染頻率一般是60HZ穷当,即要求1幀的時間為1s / 60 = 16.67ms,瀏覽器顯示頁面的時候淹禾,要處理js邏輯馁菜,還要做渲染,每個執(zhí)行片段不能超過16.67ms铃岔。實際上汪疮,瀏覽器內(nèi)核自身支撐體系運行也需要消耗一些時間,所以留給我們的時間差不多只有10ms毁习。
常見的優(yōu)化方式:

  • 使用requestIdleCallback和requestAnimationFrame智嚷,任務分片
function Task(){
    this.tasks = [];
}
//添加一個任務
Task.prototype.addTask = function(task){
    this.tasks.push(task);
};
//每次重繪前取一個task執(zhí)行
Task.prototype.draw = function(){
    var that = this;
    window.requestAnimationFrame(function(){
        var tasks = that.tasks;        if(tasks.length){
            var task = tasks.shift();
            task();
        }
        window.requestAnimationFrame(function(){that.draw.call(that)});
    });
};

2.頁面回流和重繪較多

  • 盡量減少layout
    獲取scrollTop、clentWidth等維度屬性時都會觸發(fā)layout以獲取實時的值纺且,所以在for循環(huán)里面應該把這些值緩存一下
  • 簡化DOM結構
    當DOM結構越復雜時盏道,需要重繪的元素也就越多。所以dom應該保持簡單载碌,特別是那些要做動畫的猜嘱,或者要監(jiān)聽scroll/mousemove事件的。另外使用flex比使用float在重繪方面會有優(yōu)勢嫁艇。

3.資源加載阻塞

  • js資源放在body之前
  • 行內(nèi)script阻塞
  • css加載會阻塞DOM樹渲染(css并不會阻塞DOM樹的解析)
  • 資源過大阻塞

三.詳細解讀-內(nèi)存過大導致的頁面卡頓

1.內(nèi)存泄漏導致內(nèi)存過大

瀏覽器有自己的一套垃圾回收機制朗伶,主流垃圾回收機制是標記清除,不過在ie中訪問原生dom會采用引用計數(shù)方式機制步咪,而如果閑置內(nèi)存得不到及時回收论皆,就會導致內(nèi)存泄漏。

簡單介紹下兩種垃圾回收機制(GC Garbage Collection)

標記清除

定義和用法:
當變量進入環(huán)境時猾漫,將變量標記為進入環(huán)境纯丸,當變量離開環(huán)境時,標記為離開環(huán)境静袖。某一時刻觉鼻,垃圾回收器會過濾掉環(huán)境種的變量,以及被環(huán)境變量引用的變量队橙,剩下的就是被視為準備回收的變量坠陈。
到目前為止,IE捐康、Firefox仇矾、Opera、Chrome解总、Safari的js實現(xiàn)使用的都是標記清除的垃圾回收策略或類似的策略贮匕,只不過垃圾收集的時間間隔互不相同。
流程:

  • 瀏覽器在運行的時候會給存儲再內(nèi)存中的所有變量都加上標記
  • 去掉環(huán)境中的變量以及被環(huán)境中引用的變量的標記
  • 如果還有變量有標記花枫,就會被視為準備刪除的變量
  • 垃圾回收機制完成內(nèi)存的清除工作刻盐,銷毀那些帶標記的變量掏膏,并回收他們所占用的內(nèi)存空間

引用計數(shù)

定義和用法:引用計數(shù)是跟蹤記錄每個值被引用的次數(shù)。
基本原理:就是變量的引用次數(shù)敦锌,被引用一次則加1馒疹,當這個引用計數(shù)為0時,被視為準備回收的對象乙墙。
流程:

  • 聲明了一個變量并將一個引用類型的值賦值給這個變量颖变,這個引用類型值引用次數(shù)就是1
  • 同一個值又被賦值另一個變量,這個引用類型的值引用次數(shù)加1
  • 當包含這個引用類型值得變量又被賦值另一個值了听想,那么這個引用類型的值的引用次數(shù)減1
  • 當引用次數(shù)變成0時腥刹, 說明這個值需要解除引用
  • 當垃圾回收機制下次運行時,它就會釋放引用次數(shù)為0 的值所占用的內(nèi)存

常見的造成內(nèi)存泄漏的原因:

  • 意外的全局變量引起的內(nèi)存泄漏
    解決:使用嚴格模式避免
    實例:
<button onclick="createNode()">添加節(jié)點</button>
  <button onclick="removeNode()">刪除節(jié)點</button>
  <div id="wrapper"></div>
  <script>
  var text = [];
  function createNode() { 
      text.push(new Array(1000000).join('x'));  
      var textNode = document.createTextNode("新節(jié)點"),
          div = document.createElement('div');
      div.appendChild(textNode);
      document.getElementById("wrapper").appendChild(div);  
  }
  
  function removeNode() {
      var wrapper = document.getElementById("wrapper"),
          len = wrapper.childNodes.length;
      if (len > 0) {
          wrapper.removeChild(wrapper.childNodes[len - 1]);  
      }
  }
  </script>

text變量在createNode中引用汉买,導致text不能被回收

  • 閉包引起的內(nèi)存泄漏
    實例:
 <button onclick="replaceThing()">第二次點我就有泄漏</button>
  <script>
  var theThing = null;
  var replaceThing = function () {
      var originalThing = theThing;
      var unused = function () {
          if (originalThing) {
              console.log("hi");
          };
      }
      theThing = {
          longStr: new Array(1000000).join('*'),
          someMethod: function someMethod() {
              console.log('someMessage');
          }
      };
  };
  </script>

上面那段代碼泄漏的原因在于有兩個閉包:unused和someMethod衔峰,二者共享父級作用域。
因為后面的 theThing 是全局變量录别,someMethod是全局變量的屬性朽色,它引用的閉包作用域(unused 和somMethod共享)不會釋放,由于originalThing在共享的作用域中组题,造成originalThing不會釋放葫男,隨著 replaceThing 不斷調(diào)用,originalThing 指向前一次的 theThing崔列,而新的theThing.someMethod又會引用originalThing 梢褐,從而形成一個閉包引用鏈,而 longStr是一個大字符串赵讯,得不到釋放盈咳,從而造成內(nèi)存泄漏。
解決方法:在 replaceThing 的最后添加 originalThing = null

  • 被遺忘的定時器
    實例:
var someResource = getData(); 
setInterval(function() { 
    var node = document.getElementById('Node'); 
    if(node) { 
        // 處理 node 和 someResource 
        node.innerHTML = JSON.stringify(someResource)); 
    } 
}, 1000);

計時器回調(diào)函數(shù)沒被回收(計時器停止才會被回收)

  • 循環(huán)引用

定義:循環(huán)引用就是對象A中包含另一個指向?qū)ο驜的指針边翼,B中也包含一個指向A的引用鱼响。

因為IE中的BOM、DOM的實現(xiàn)使用了COM组底,而COM對象使用的垃圾收集機制是引用計數(shù)策略丈积。所以會存在循環(huán)引用的問題

解決方法:手工斷開js對象和DOM之間的鏈接。賦值為null债鸡。
實例:

function handle () {
    var element = document.getElementById(“testId”);
    element.onclick = function (){
        alert(element.id)
    }
}

element綁定的事件中引用了element上的屬性

onclick事件是一個閉包江滨,閉包可以維持函數(shù)內(nèi)局部變量,使其得不到釋放厌均。也就是說element變量得不到釋放唬滑,每調(diào)用一次element都會得不到釋放,最終內(nèi)存泄漏
解決問題:

function handle () {
    var element = document.getElementById(“testId”);
    element.onclick = function (){
        alert(element.id)
    }
    element = null
}
  • DOM刪除是沒有解綁事件
  • 沒有清除DOM元素的引用

2.dom節(jié)點或事件占用內(nèi)存過大

實例:

function addDom(){
        let d = document.createDocumentFragment();
       
        for(var i = 0;i<30;i++){
            let li = document.createElement('li')
            li.addEventListener('click', function(e) {
                let _this = e.target;
                let dom = e.target.tagName.toLowerCase();
                _this.style.color = 'red';
            })
        li.innerHTML = `</div>
            <h4>
                測試圖片 
            </h4>
            <img style = "height:20px;width:100px" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1591105123139&di=90a63b4962d0b4405ce65c2096a676c2&imgtype=0&src=http%3A%2F%2Fimg0.imgtn.bdimg.com%2Fit%2Fu%3D3769023270%2C3433174748%26fm%3D214%26gp%3D0.jpg"/>
            </div>`
            d.appendChild(li)
        }
        document.getElementById('app').appendChild(d)
    }

上面的代碼是下拉加載,每次都會添加dom晶密,最終導致內(nèi)存過大
解決辦法:采用虛擬列表和事件委托


總結:頁面卡頓在實際開發(fā)過程中有很多場景擒悬,可以使用內(nèi)存泄漏檢測工具(sIEve,針對IE)進行檢測惹挟,也可以使用chrome提供的timeline和profiles茄螃,或者performance缝驳,這里不再詳細介紹连锯。


查閱:
https://blog.csdn.net/ycf74514/article/details/51123263?locationNum=3&fps=1
https://blog.csdn.net/c11073138/article/details/84728132
https://www.cnblogs.com/yanglongbo/articles/9762359.html

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市用狱,隨后出現(xiàn)的幾起案子运怖,更是在濱河造成了極大的恐慌,老刑警劉巖夏伊,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摇展,死亡現(xiàn)場離奇詭異,居然都是意外死亡溺忧,警方通過查閱死者的電腦和手機咏连,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鲁森,“玉大人祟滴,你說我怎么就攤上這事「韪龋” “怎么了垄懂?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長痛垛。 經(jīng)常有香客問我草慧,道長,這世上最難降的妖魔是什么匙头? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任漫谷,我火速辦了婚禮,結果婚禮上蹂析,老公的妹妹穿的比我還像新娘舔示。我一直安慰自己,他們只是感情好识窿,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布斩郎。 她就那樣靜靜地躺著,像睡著了一般喻频。 火紅的嫁衣襯著肌膚如雪缩宜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音锻煌,去河邊找鬼妓布。 笑死,一個胖子當著我的面吹牛宋梧,可吹牛的內(nèi)容都是我干的匣沼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捂龄,長吁一口氣:“原來是場噩夢啊……” “哼释涛!你這毒婦竟也來了?” 一聲冷哼從身側響起倦沧,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤唇撬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后展融,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窖认,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年告希,在試婚紗的時候發(fā)現(xiàn)自己被綠了扑浸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡燕偶,死狀恐怖喝噪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情杭跪,我是刑警寧澤仙逻,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站涧尿,受9級特大地震影響系奉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜姑廉,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一缺亮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桥言,春花似錦萌踱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至扔涧,卻和暖如春园担,著一層夾襖步出監(jiān)牢的瞬間届谈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工弯汰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留艰山,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓咏闪,卻偏偏與公主長得像曙搬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鸽嫂,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

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