什麼是時間分片(Time Slicing)?

根據W3C性能小組的介紹,超過50ms的任務就是長任務。

圖片描述

圖片來自使用 RAIL 模型評估性能

根據上圖我們可以知道,當延遲超過100ms,用戶就會察覺到輕微的延遲。

所以爲了避免這種情況,我們可以使用兩種方案,一種是Web Worker,另一種是時間切片(Time Slicing)

Web Worker

我們都知道,JS是單線程,所以當我們在運行長任務時,容易造成頁面假死的狀態,雖然我們可以將任務放在任務隊列中,通過異步的方式執行,但這並不能改變JS的本質。

所以爲了改變這種現狀,whatwg推出了Web Workers

具體的語法不會進行說明,有興趣的童鞋可以查看MDN Web Worker

我們可以看看使用了Web Worker之後的優化效果:

const testWorker = new Worker('./worker.js')
setTimeout(_ => {
  testWorker.postMessage({})
  testWorker.onmessage = function (ev) {
    console.log(ev.data)
  }
}, 5000)

// worker.js
self.onmessage = function () {
  const start = performance.now()
  while (performance.now() - start < 1000) {}
  postMessage('done!')
}

圖片描述

代碼以及截圖來自於讓你的網頁更絲滑

時間切片(Time Slicing)

時間切片是一項使用得比較廣的技術方案,它的本質就是將長任務分割爲一個個執行時間很短的任務,然後再一個個地執行。

這個概念在我們日常的性能優化上是非常有用的。

例如當我們需要在頁面中一次性插入一個長列表時(當然,通常這種情況,我們會使用分頁去做)。

如果利用時間分片的概念來實現這個功能,我們可以使用requestAnimationFrame+DocumentFragment

關於這兩個API,我同樣不會做詳細的介紹,有興趣的可以查看MDN requestAnimationFrameMDN DocumentFragment

這裏有兩個DEMO,大家可以對比下流暢程度:

未使用時間分片:

<style>
    * {
        margin: 0;
        padding: 0;
    }
    .list {
        width: 60vw;
        position: absolute;
        left: 50%;
        transform: translateX(-50%);
    }
</style>
<ul class="list"></ul>
<script>
    'use strict'
    let list = document.querySelector('.list')
    let total = 100000
    for (let i = 0; i < total; ++i) {
        let item = document.createElement('li')
        item.innerText = `我是${i}`
        list.appendChild(item)
    }
</script>

使用時間分片:

<style>
    * {
        margin: 0;
        padding: 0;
    }
    .list {
        width: 60vw;
        position: absolute;
        left: 50%;
        transform: translateX(-50%);
    }
</style>
<ul class="list"></ul>
<script>
    'use strict'
    let list = document.querySelector('.list')
    let total = 100000
    let size = 20
    let index = 0
    const render = (total, index) => {
        if (total <= 0) {
            return
        }
        let curPage = Math.min(total, size)
        window.requestAnimationFrame(() => {
            let fragment = document.createDocumentFragment()
            for (let i = 0; i < curPage; ++i) {
                let item = document.createElement('li')
                item.innerText = `我是${index + i}`
                fragment.appendChild(item)
            }
            list.appendChild(fragment)
            render(total - curPage, index + curPage)
        })
    }
    render(total, index)
</script>

沒有做太多的測評,但是從用戶視覺上的感受來看就是,第一種方案,我就是想刷新都要打好幾個轉,往下滑的時候也有白屏的現象。

除了上述的生成DOM的方案,我們同樣可以利用Web Api requestIdleCallback 以及ES6 API Generator]來實現。

同樣不會做太多的介紹,詳細規則可以看MDN requestIdleCallback以及MDN Generator

具體實現如下:

<style>
    * {
        margin: 0;
        padding: 0;
    }
    .list {
        width: 60vw;
        position: absolute;
        left: 50%;
        transform: translateX(-50%);
    }
</style>
<ul class="list"></ul>
<script>
    'use strict'
    function gen(task) {
      requestIdleCallback(deadline => {
        let next = task.next()
        while (!next.done) {
          if (deadline.timeRemaining() <= 0) {
            gen(task)
            return
          }
          next = task.next()
        }
      })
    }
    let list = document.querySelector('.list')
    let total = 100000
    function* loop() {
      for (let i = 0; i < total; ++i) {
        let item = document.createElement('li')
        item.innerText = `我是${i}`
        list.appendChild(item)
        yield
      }
    }
    gen(loop())
</script>

參考資料

  1. web-performance
  2. Measure Performance with the RAIL Model
  3. 讓你的網頁更絲滑
  4. 「前端進階」高性能渲染十萬條數據(時間分片)

後記

如果你喜歡探討技術,或者對本文有任何的意見或建議,非常歡迎加魚頭微信好友一起探討,當然,魚頭也非常希望能跟你一起聊生活,聊愛好,談天說地。
魚頭的微信號是:krisChans95
也可以掃碼關注公衆號,訂閱更多精彩內容。

https://user-gold-cdn.xitu.io/2020/3/4/170a55cc795174aa?w=1000&h=480&f=png&s=311000

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章