- 作者:陳大魚頭
- github: KRISACHAN
根據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 requestAnimationFrame跟MDN 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>
參考資料
後記
如果你喜歡探討技術,或者對本文有任何的意見或建議,非常歡迎加魚頭微信好友一起探討,當然,魚頭也非常希望能跟你一起聊生活,聊愛好,談天說地。
魚頭的微信號是:krisChans95
也可以掃碼關注公衆號,訂閱更多精彩內容。