詳解JavaScript進(jìn)度管理
前言
我們寫程序的時候會經(jīng)常遇到顯示進(jìn)度的需求,如加載進(jìn)度、上傳進(jìn)度等。
最常見的實現(xiàn)方式是通過記錄已完成數(shù)量(loadedCount)和總數(shù)量(totalCount),然后算一下就能得到進(jìn)度了。
這種方式簡單粗暴,容易實現(xiàn),但不好擴(kuò)展,必須有個地方維護(hù)所有l(wèi)oadedCount和totalCount。
本文將會基于上述實現(xiàn)方式,實現(xiàn)一種更容易擴(kuò)展的進(jìn)度管理方式。
問題
筆者在寫 WebGL 應(yīng)用,在應(yīng)用預(yù)加載階段需要計算加載進(jìn)度。
加載的內(nèi)容包括:模型資源、貼圖資源、腳本資源等。
其中模型資源中又會包含材質(zhì)資源,材質(zhì)資源里面又會包含貼圖資源。
畫圖來表示的話就是如下的結(jié)構(gòu):
+-------------------------------------------------------------+ | | | resources | | | | +----------+ +-----------------+ +-----------------+ | | | script1 | | model1 | | model2 | | | +----------+ | | | | | | | -------------+ | | -------------+ | | | +----------+ | |model1.json | | | |model2.json | | | | | script2 | | +------------+ | | +------------+ | | | +----------+ | | | | | | | +------------+ | | +------------+ | | | +----------+ | | material1 | | | | material1 | | | | | texture1 | | | +--------+ | | | | +--------+ | | | | +----------+ | | |texture1| | | | | |texture1| | | | | | | +--------+ | | | | +--------+ | | | | +----------+ | | +--------+ | | | | +--------+ | | | | | texture2 | | | |texture2| | | | | |texture2| | | | | +----------+ | | +--------+ | | | | +--------+ | | | | | +------------+ | | +------------+ | | | | | | | | | | +------------+ | | +------------+ | | | | | material2 | | | | material2 | | | | | +------------+ | | +------------+ | | | +-----------------+ +-----------------+ | | | +-------------------------------------------------------------+
這里有個前提:當(dāng)加載某個資源的時候,必須保證這個資源及它引用的資源全部加載完成后,才能算加載完成。
基于這個前提,我們已經(jīng)實現(xiàn)了一個onProgress接口,這個接口返回的進(jìn)度是已經(jīng)包含了子資源的加載進(jìn)度的了。
翻譯成代碼就是:
class Asset {
load(onProgress) {
return new Promise((resolve) => {
if (typeof onProgress !== 'function') {
onProgress = (_p) => { };
}
let loadedCount = 0;
let totalCount = 10; // NOTE: just for demo
let onLoaded = () => {
loadedCount++;
onProgress(loadedCount / totalCont);
if (loadedCount === totalCount) resolve();
};
Promise.all(
this.refAssets.map(asset => asset.load().then(onLoaded))
);
});
}
}
既然有了這個接口,如果沿用全局維護(hù)loadedCount和totalCount的形式的話,處理起來其實挺麻煩的。
本文接下來要介紹的,就是一種變通的做法。
原理
基本思想就是分而治之。把一個大任務(wù)拆分成多個小任務(wù),然后分別計算所有小任務(wù)的進(jìn)度,最后再把所有小任務(wù)的進(jìn)度歸并起來得到總進(jìn)度。
如下圖表示:
+--------------------------------------------------------------------+ | | | | | total progress | | | | +---------+---------+----------+----------+--------+--------+ | | | script1 | script2 | texture1 | texture2 | model1 | model2 | | | | (0~1) | (0~1) | (0~1) | (0~1) | (0~1) | (0~1) | | | +---------+---------+----------+----------+--------+--------+ | | | | model1 | | +-------------+-----------------------+-----------+ | | | model1.json | material1 | material2 | | | | (0~1) | (0~1) | (0~1) | | | +------------------------+------------------------+ | | | texture1 | texture2 | | | | (0~1) | (0~1) | | | +----------+------------+ | | | | model2 | | +-------------+-----------------------+-----------+ | | | model2.json | material1 | material2 | | | | (0~1) | (0~1) | (0~1) | | | +------------------------+------------------------+ | | | texture1 | texture2 | | | | (0~1) | (0~1) | | | +----------+------------+ | | | +--------------------------------------------------------------------+
基于這個原理去實現(xiàn)進(jìn)度,實現(xiàn)方式就是通過一個列表去保存所有資源當(dāng)前的加載進(jìn)度,然后每次觸發(fā)onProgress的時候,執(zhí)行一次歸并操作,計算總進(jìn)度。
var progresses = [
0, // script1,
0, // script2,
0, // texture1,
0, // texture2,
0, // model1,
0, // model2
];
function onProgress(p) {
// TODO: progresses[??] = p;
return progresses.reduce((a, b) => a + b, 0) / progresses.length;
}
但這里面有個難點,當(dāng)觸發(fā)onProgress回調(diào)的時候,如何知道應(yīng)該更新列表中的哪一項呢?
利用JavaScript的閉包特性,我們可以很容易實現(xiàn)這一功能。
var progresses = [];
function add() {
progresses.push(0);
var index = progresses.length - 1;
return function onProgress(p) {
progresses[index] = p;
reduce();
};
}
function reduce() {
return progresses.reduce((a, b) => a + b, 0) / progresses.length;
}
利用閉包保留資源的索引,當(dāng)觸發(fā)onProgress的時候,就能根據(jù)索引去更新列表中對應(yīng)項的進(jìn)度了。最后歸并的時候就能計算出正確的進(jìn)度了。
剩下的事情就是整合我們所有的代碼,然后對其進(jìn)行測試了
測試
我們可以用下面的代碼來模擬一下整個加載過程:
class Asset {
constructor(totalCount) {
this.loadedCount = 0;
this.totalCount = totalCount;
this.timerId = -1;
}
load(onProgress) {
if (typeof onProgress !== 'function') {
onProgress = (_p) => { };
}
return new Promise((resolve) => {
this.timerId = setInterval(() => {
this.loadedCount++;
onProgress(this.loadedCount / this.totalCount);
if (this.loadedCount === this.totalCount) {
clearInterval(this.timerId);
resolve();
}
}, 1000);
});
}
}
class Progress {
constructor(onProgress) {
this.onProgress = onProgress;
this._list = [];
}
add() {
this._list.push(0);
const index = this._list.length - 1;
return (p) => {
this._list[index] = p;
this.reduce();
};
}
reduce() {
const p = Math.min(1, this._list.reduce((a, b) => a + b, 0) / this._list.length);
this.onProgress(p);
}
}
const p = new Progress(console.log);
const asset1 = new Asset(1);
const asset2 = new Asset(2);
const asset3 = new Asset(3);
const asset4 = new Asset(4);
const asset5 = new Asset(5);
Promise.all([
asset1.load(p.add()),
asset2.load(p.add()),
asset3.load(p.add()),
asset4.load(p.add()),
asset5.load(p.add()),
]).then(() => console.log('all resources loaded'));
/**
輸出
Promise { <state>: "pending" }
0.2
0.3
0.36666666666666664
0.41666666666666663
0.45666666666666667
0.5566666666666668
0.6233333333333333
0.6733333333333333
0.7133333333333333
0.78
0.8300000000000001
0.8699999999999999
0.9199999999999999
0.96
1
all resources loaded
*/
這種方式的優(yōu)點是能避開全局管理loadedCount和totalCount,把這部分工作交回資源內(nèi)部管理,它要做的只是對大任務(wù)進(jìn)行歸并計算。
缺點也很明顯,需要對onProgress接口進(jìn)行一次統(tǒng)一。在已有項目中推進(jìn)難度很大,所以比較適合新項目或者小項目去實踐。
以上就是JavaScript進(jìn)度管理的詳細(xì)內(nèi)容,更多關(guān)于JavaScript進(jìn)度管理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS使用setInterval實現(xiàn)的簡單計時器功能示例
這篇文章主要介紹了JS使用setInterval實現(xiàn)的簡單計時器功能,涉及javascript基于setInterval的定時觸發(fā)與數(shù)值運算相關(guān)操作技巧,需要的朋友可以參考下2018-04-04
uni-app調(diào)取接口的3種方式以及封裝uni.request()詳解
我們在實際工作中要將數(shù)據(jù)傳輸?shù)椒?wù)器端,從服務(wù)器端獲取信息,都是通過接口的形式,下面這篇文章主要給大家介紹了關(guān)于uni-app調(diào)取接口的3種方式以及封裝uni.request()的相關(guān)資料,需要的朋友可以參考下2022-08-08
HTA版JSMin(省略修飾語若干)基于javascript語言編寫
JSMin是一種很有用的ECMAScript代碼減肥工具,雖然它只是一種較為初級的工具,但用它來對付我自己編寫的大部分ECMAScript代碼效果都很不錯,而且在我這里也從未發(fā)生過減肥后代碼出錯之類的問題。2009-12-12
JS代碼實現(xiàn)百度地圖 畫圓 刪除標(biāo)注
這篇文章主要介紹了JS代碼實現(xiàn)百度地圖 畫圓 刪除標(biāo)注的相關(guān)資料,實現(xiàn)此功能的設(shè)計思路非常明確,代碼簡單易懂,非常不錯,具有參考借鑒價值,感興趣的朋友參考下吧2016-10-10

