<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                [TOC] # 時間分片 ## 使用定時器 在 JS 的`Event Loop`中,當JS引擎所管理的執行棧中的事件以及所有微任務事件全部執行完后,才會觸發渲染線程對頁面進行渲染。 頁面的卡頓是由于同時渲染大量DOM所引起的,所以我們考慮將渲染過程分批進行。 <br> ## requestAnimationFrame ### setTimeout 和閃屏現象 * `setTimeout`的執行時間并不是確定的。在JS中,`setTimeout`任務被放進事件隊列中,只有主線程執行完才會去檢查事件隊列中的任務是否需要執行,因此`setTimeout`的實際執行時間可能會比其設定的時間晚一些。 * 刷新頻率受屏幕分辨率和屏幕尺寸的影響,因此不同設備的刷新頻率可能會不同,而`setTimeout`只能設置一個固定時間間隔,這個時間不一定和屏幕的刷新時間相同。 以上兩種情況都會導致setTimeout的執行步調和屏幕的刷新步調不一致。 在`setTimeout`中對dom進行操作,必須要等到屏幕下次繪制時才能更新到屏幕上,如果兩者步調不一致,就可能導致中間某一幀的操作被跨越過去,而直接更新下一幀的元素,從而導致丟幀現象。 <br> ### 使用 requestAnimationFrame 與`setTimeout`相比,`requestAnimationFrame`最大的優勢是由系統來決定回調函數的執行時機。 如果屏幕刷新率是60Hz,那么回調函數就每16.7ms被執行一次,如果刷新率是75Hz,那么這個時間間隔就變成了1000/75=13.3ms,換句話說就是,`requestAnimationFrame`的步伐跟著系統的刷新步伐走。它能保證回調函數在屏幕每一次的刷新間隔中只被執行一次,這樣就不會引起丟幀現象。 <br> ## DocumentFragment `DocumentFragments`是DOM節點,但并不是DOM樹的一部分,可以認為是存在內存中的,所以將子元素插入到文檔片段時不會引起頁面回流。 <br> ## 最終代碼 ~~~ <ul id="container"></ul> ~~~ ~~~ //需要插入的容器 let ul = document.getElementById('container'); // 插入十萬條數據 let total = 100000; // 一次插入 20 條 let once = 20; //總頁數 let page = total/once //每條記錄的索引 let index = 0; //循環加載數據 function loop(curTotal,curIndex){ if(curTotal <= 0){ return false; } //每頁多少條 let pageCount = Math.min(curTotal , once); window.requestAnimationFrame(function(){ let fragment = document.createDocumentFragment(); for(let i = 0; i < pageCount; i++){ let li = document.createElement('li'); li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total) fragment.appendChild(li) } ul.appendChild(fragment) loop(curTotal - pageCount,curIndex + pageCount) }) } loop(total,index); ~~~ <br> <br> # worker ## 什么是worker ~~~ 運行者 Worker 接口是Web Workers API的一部分,代表一個后臺任務, 它容易被創建并向創建者發回消息。創建一個運行者只要簡單的調用Worker()構造函數,指定一個腳本,在工作線程中執行。(引自MDN) ~~~ 看概念可能有點枯燥,通俗點講就是:因為js是單線程運行的,在遇到一些需要處理大量數據的js時,可能會阻塞頁面的加載,造成頁面的假死。這時我們可以使用worker來開辟一個獨立于主線程的子線程來進行哪些大量運算。這樣就不會造成頁面卡死。也說明 worker可以用來解決大量運算是造成頁面卡死的問題。 <br> <br> ## 語法 ### 創建 Web Workers ~~~ const worker=new Worker(aURL, options) ~~~ 它有兩個參數: * aURL(必須)是一個DOMString 表示worker 將執行的腳本的URL。它必須遵守同源策略。 * options (可選)它的一個作用就是指定 Worker 的名稱,用來區分多個 Worker 線程 <br> ### 收發消息 Web Workers 用來執行異步腳本,只要掌握了它與主線程通信的方式,就可以在指定時機運行異步腳本,并在運行完時將結果傳遞給主線程。 <br> ### 主線程接收發 Web Workers 消息 ~~~text const worker = new Worker("../src/worker.js"); worker.onmessage = e => {}; worker.postMessage("Marco!"); ~~~ 每個`worker`實例通過`onmessage`接收消息,通過`postMessage`發送消息。 <br> ### Web Workers 收發主線程消息 ~~~text self.onmessage = e => {}; self.postMessage("Marco!"); ~~~ 和主線程代碼類似,在 Web Workers 代碼中,也是`onmessage`接收消息,這個消息來自主線程或者其它 Workers。也可以通過`postMessage`發送消息。 <br> ### 銷毀 Web Workers ~~~text worker.terminate(); ~~~ 文章內容就這么多,是不是有寫太簡單了呢!筆者結合自己的使用經驗,再補充一些知識。 <br> <br> ## 使用worker的注意點 <br> ### 1.同源限制 分配給 Worker 線程運行的腳本文件,必須與主線程的腳本文件同源。 <br> ### 2.DOM 限制 Worker 線程所在的全局對象,與主線程不一樣,無法讀取主線程所在網頁的 DOM 對象,也無法使用document、window、parent這些對象。但是,Worker 線程可以navigator對象和location對象。 <br> ### 3.通信聯系 Worker 線程和主線程不在同一個上下文環境,它們不能直接通信,必須通過消息完成。 <br> ### 4.腳本限制 Worker 線程不能執行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 對象發出 AJAX 請求。 <br> ### 5.文件限制 Worker 線程無法讀取本地文件,即不能打開本機的文件系統(file://),它所加載的腳本,必須來自網絡。 <br> ## 優化 ### 對象轉移(Transferable Objects) 對象轉移就是將對象引用零成本轉交給 Web Workers 的上下文,而不需要進行結構拷貝。 這里要解釋的是,**主線程與 Web Workers 之間的通信,并不是對象引用的傳遞,而是序列化/反序列化的過程**,當對象非常龐大時,序列化和反序列化都會消耗大量計算資源,降低運行速度。 ![](https://pic2.zhimg.com/80/v2-be0271a7f89a35c6cd4b74e5f2a7cc49_1440w.jpg) 上面的圖充分證明了,大對象傳遞,使用對象轉移各項指標都優于結構拷貝。 對象轉移使用方式很簡單,給`postMessage`增加一個參數,把對象引用傳過去即可: ~~~text var ab = new ArrayBuffer(1); worker.postMessage(ab, [ab]); ~~~ 瀏覽器兼容性也不錯:Currently Chrome 17+, Firefox, Opera, Safari, IE10+。更具體內容,可以看[Transferable Objects: Lightning Fast!](https://link.zhihu.com/?target=https%3A//developers.google.com/web/updates/2011/12/Transferable-Objects-Lightning-Fast)。 > 需要注意的是,對象引用轉移后,原先上下文就無法訪問此對象了,需要在 Web Workers 再次將對象還原到主線程上下文后,主線程才能正常訪問被轉交的對象。 ### 如何不用 JS 文件創建 Web Workers Web Workers 優勢這么大,但用起來需要在同域下創建一個 JS 文件實在不方便,尤其在前后端分離做的比較徹底的團隊,前端團隊能控制的僅僅是一個 JS 文件。那么下面給出幾個不用 JS 文件,就創建 Web Workers 的方法: ### webpack 插件 - worker-loader [worker-loader](https://link.zhihu.com/?target=https%3A//github.com/webpack-contrib/worker-loader)是一個 webpack 插件,可以將一個普通 JS 文件的全部依賴提取后打包并替換調用處,以 Blob 形式內聯在源碼中。 ~~~text import Worker from "worker-loader!./file.worker.js"; const worker = new Worker(); ~~~ 上述代碼的魔術在于,轉化成下面的方式執行: ~~~text const blob = new Blob([codeFromFileWorker], { type: "application/javascript" }); const worker = new Worker(URL.createObjectURL(blob)); ~~~ ### Blob URL 第二種方式由第一種方式自然帶出:如果不想用 webpack 插件,那自己通過 Blob 的方式創建也可以: ~~~text const code = ` importScripts('https://xxx.com/xxx.js'); self.onmessage = e => {}; `; const blob = new Blob([code], { type: "application/javascript" }); const worker = new Worker(URL.createObjectURL(blob)); ~~~ 看上去代碼更輕量一些,不過問題是當遇到復雜依賴時,如果不能把依賴都轉化為腳本通過`importScripts`方式引用,就無法訪問到主線程環境中的包。如果真的遇到了這個問題,可以用第一種 webpack 插件的方式解決,這個插件會自動把文件所有依賴都打包進源碼。 ### 管理 postMessage 隊列 為什么 postMessage 會形成隊列,為什么要管理它? 首先在 Web Workers 架構設計上就必須做成隊列,因為調用`postMessage`時,對應的 Web Workers 不一定完成了初始化,所以瀏覽器底層必須管理一個隊列,在 Web Workers 初始化完畢時,依次消費,這樣才能確保任何時候發出的`postMessage`都能被 Web Workers 接收到。 其次,為什么要手動維護這個隊列,原因可能取決于如下幾點: * 業務原因,前面的`postMessage`還沒來得及消費,就不要發送新的消息,或者丟棄新的消息,這時候需要通過雙向通信拿到 Web Workers 的執行結果回執,手動控制隊列。 * 性能原因,一般 Web Workers 都會被用來執行耗時的同步運算,如果運算時間比較長,那短期塞入多個消息隊列是沒有意義的。 ![](https://pic1.zhimg.com/80/v2-6bb0d68f3e9457ab3ea0f8d2b52a29e0_1440w.jpg) 如上圖所示,對于每次用戶輸入都要進行的 SQL Parser 很耗時,及時放在 Web Workers 也可能導致將 Workers 撐爆到無響應,這是不僅要使用多 Workers 緩沖池,還要對待執行隊列進行過濾,因為用戶永遠只關心最后一次輸入的 Parser 結果。 由于 Web Workers 運算被卡住時,除了銷毀 Worker 沒有別的辦法,而銷毀 Worker 的成本比較高,不能對每一個用戶輸入都銷毀并新建 Web Workers,所以利用 Workers 緩沖池,當緩沖池滿了,新的消費隊列又進來的時候,可以銷毀全部 Workers 緩沖池,換一批新緩沖池重新消費用戶輸入。 <br> <br> # 虛擬列表 https://juejin.cn/post/6844903982742110216#heading-5 <br> <br> # 參考資料 * [「中高級前端」高性能渲染十萬條數據(時間分片)](https://juejin.im/post/5d76f469f265da039a28aff7) * [ 聊聊前端開發中的長列表](https://zhuanlan.zhihu.com/p/26022258) * [再談前端虛擬列表的實現](https://zhuanlan.zhihu.com/p/34585166)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看