實戰篇 - 如何實現和淘寶移動端同樣的模塊化加載 (task-silce)

事情背景

事情的背景是個人實際項目,在個人實際項目當中,發現首屏渲染的速度比較慢,這樣致使白屏的時間會特別長,影響用戶體驗度,剛好我有一天看了淘寶移動端的加載方式,針對咱們的項目,就作了一次優化調整,並寫了一個簡單的工具庫javascript

這個工具是不限制環境和框架的,我如今的框架是 vue ,在 react 及小程序中也作過測試,是徹底可使用的,使用建議你們仔細閱讀這篇文章,但願能夠對你的工做有所幫助前端

性能優化的目的

咱們每一次的界面變化,都要經歷如下步驟:vue

人的眼睛大約每秒能夠看到 60 幀,那麼就表明咱們每 16.7ms 就要看到 1 幀,一幀就要經歷上圖的 5 步,說明咱們的每個任務(task) 不宜過長,這樣就會致使用戶對於界面感知的不友好性java

根據谷歌統計的數據,用戶在不一樣時間段內接收到的反饋,可能直接影響到對於網站的用戶留存,以下圖:react

在這裏咱們不深刻講對於這方面的一些細節,這篇文章主要是給你們講一下,若是作任務切片,如何優化界面的渲染速度和響應速度git

分析淘寶

淘寶的渲染方式

咱們先看一下淘寶的渲染方式es6

經過圖片和 Performancemain 部分,咱們能夠看得出來淘寶移動端的加載方式,是一塊一塊去加載的,暫時咱們稱之爲 模塊化加載github

performance 的使用和如何查看性能優化的數據,可經過 性能優化篇 - Performance(工具 & api) 來了解 performancenpm

淘寶的任務切片

咱們放大之後能夠看的出來,淘寶網在每一次的任務完成後,都會進行上面的 5 步進行界面的渲染,這樣可能不如把全部的界面所有渲染完畢後,在進行樣式計算、佈局、繪製、計算位置等的速度快,可是這樣能夠保證,讓用戶在最短的時間內,能夠看到咱們的網站內容小程序

簡單的介紹一下渲染的步驟和對用戶的影響,及淘寶的渲染方式,接下來咱們開始實現一個任務切片的工具

任務切片源碼介紹

任務切片,顧名思義就是咱們要把每個任務去作切片,縮短任務的執行時長,加快任務的渲染

這裏要使用 es6 的 generator 的特性去實現任務切片

初始化任務

function init({ sliceList, callback }) {
	if (!isFunction(callback)) {
		console.error('callback 爲必傳參數併爲 function');
		return;
	}
	// 添加切片隊列
	this.generator = this.sliceQueue({
		sliceList,
		callback
	});
	// 開始切片
	this.next();
}
複製代碼

在一開始的時候,咱們須要至少兩個參數:

sliceList 或者 sliceCount : 能夠是數組,也能夠是數字,數組就是用來切對應的內容去分塊,數字就是按次去切片

callback : 這裏須要使用者傳一個回調函數,用來通知使用者切片到什麼位置

切片隊列

function* sliceQueue({ sliceList, callback }) {
	let listOrNum = (isNum(sliceList) && sliceList) || (isArray(sliceList) && sliceList.length);
	for (let i = 0; i < listOrNum; ++i) {
		const start = performance.now();
		callback(i);
		while (performance.now() - start < 16.7) {
			yield;
		}
	}
}
複製代碼

因爲能夠接收數組和數字,因此要先作兼容處理

接下來就是核心代碼其中之一了:

咱們要記錄回調的執行時間,若是執行須要的時間少於 16.7ms,就中止繼續執行下去,釋放主線程讓主線程能夠利用這個時間再去作別的事情

若是大於的話,就在下一次繪製的時候去執行

這個時候你們可能會比較好奇,咱們爲何要對任務執行時間短的去作切片,時間長的就不切呢?

其實這個要結合下一段代碼來看,你們就會了解的比較清楚了

什麼時候執行下一個切片任務

function next() {
	const { generator } = this;
	const start = performance.now();
	let res = null;
	do {
		res = generator.next();
	}
	while (!res.done && performance.now() - start < 16.7);
	if (res.done) return;
	raf(this.next.bind(this));
}
複製代碼

有了這段代碼,上面最後的長任務的執行沒有打斷就很好理解了

仍是同樣,任務執行的時間少於 16.7ms 就繼續執行下一個切片任務

若是要是大於的話,咱們就不須要執行下一個切片了,咱們就要在下一次繪製(requestAnimFrame)的時候,去執行該任務,這樣就能夠把每個任務給切開了

使用方法

npm install task-slice

TaskSlice.init(number || array, function(i){
    //i 執行到第幾回,或者第幾個切片任務
})
複製代碼

到這裏,咱們就能夠模仿像淘寶同樣的模塊化的方式去加載,下圖是我本身使用該工具庫作的優化先後的數據統計:

很明顯,咱們的對於用戶的響應速度和界面渲染速度,提高了 50% 左右。

後續

git 地址:github.com/nextdoorUnc…

目前已發佈 1.0.0 版本,下一版本可能會支持 promise 或者 控制切片時間,這個看具體的需求,及你們的反饋,我會按期進行對該工具庫的更新

該工具已經在 npm 發了包,也在 git 提交了項目,有興趣的能夠去看看,順便點個 star ,謝謝了。

結尾

已經有 n 久沒有寫過文章了,因爲最近工做比較忙,並且項目當中對於前端性能還有架構方面的挑戰性仍是比較多的,此次是在作性能優化的時候,作的總結,接下來我會盡可能多分享這種用於實際項目當中的優化方案,感謝你們的支持,謝謝。

還要感謝一下 berwin,是他提出的時間切片給了我靈感,這是他的 git 地址:github.com/berwin

相關文章
相關標籤/搜索