更容易扩展的JavaScript进度管理方式怎样实现
Admin 2021-10-26 群英技术资讯 804 次浏览
本文我们来来接怎样实现更容易扩展的JavaScript进度管理方式,下文有很详细的原理介绍及测试,对大家学习和工作都有一定的参考价值,感兴趣的朋友就继续往下看吧。
我们写程序的时候会经常遇到显示进度的需求,如加载进度、上传进度等。最常见的实现方式是通过记录已完成数量(loadedCount)和总数量(totalCount),然后算一下就能得到进度了。这种方式简单粗暴,容易实现,但不好扩展,必须有个地方维护所有loadedCount和totalCount。
本文将会基于上述实现方式,实现一种更容易扩展的进度管理方式。
笔者在写 WebGL 应用,在应用预加载阶段需要计算加载进度。
加载的内容包括:模型资源、贴图资源、脚本资源等。
其中模型资源中又会包含材质资源,材质资源里面又会包含贴图资源。
画图来表示的话就是如下的结构:
+-------------------------------------------------------------+ | | | resources | | | | +----------+ +-----------------+ +-----------------+ | | | script1 | | model1 | | model2 | | | +----------+ | | | | | | | -------------+ | | -------------+ | | | +----------+ | |model1.json | | | |model2.json | | | | | script2 | | +------------+ | | +------------+ | | | +----------+ | | | | | | | +------------+ | | +------------+ | | | +----------+ | | material1 | | | | material1 | | | | | texture1 | | | +--------+ | | | | +--------+ | | | | +----------+ | | |texture1| | | | | |texture1| | | | | | | +--------+ | | | | +--------+ | | | | +----------+ | | +--------+ | | | | +--------+ | | | | | texture2 | | | |texture2| | | | | |texture2| | | | | +----------+ | | +--------+ | | | | +--------+ | | | | | +------------+ | | +------------+ | | | | | | | | | | +------------+ | | +------------+ | | | | | material2 | | | | material2 | | | | | +------------+ | | +------------+ | | | +-----------------+ +-----------------+ | | | +-------------------------------------------------------------+
这里有个前提:当加载某个资源的时候,必须保证这个资源及它引用的资源全部加载完成后,才能算加载完成。
基于这个前提,我们已经实现了一个onProgress接口,这个接口返回的进度是已经包含了子资源的加载进度的了。
翻译成代码就是:
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))
);
});
}
}
既然有了这个接口,如果沿用全局维护loadedCount和totalCount的形式的话,处理起来其实挺麻烦的。
本文接下来要介绍的,就是一种变通的做法。
基本思想就是分而治之。把一个大任务拆分成多个小任务,然后分别计算所有小任务的进度,最后再把所有小任务的进度归并起来得到总进度。
如下图表示:
+--------------------------------------------------------------------+ | | | | | 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) | | | +----------+------------+ | | | +--------------------------------------------------------------------+
基于这个原理去实现进度,实现方式就是通过一个列表去保存所有资源当前的加载进度,然后每次触发onProgress的时候,执行一次归并操作,计算总进度。
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;
}
但这里面有个难点,当触发onProgress回调的时候,如何知道应该更新列表中的哪一项呢?
利用JavaScript的闭包特性,我们可以很容易实现这一功能。
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;
}
利用闭包保留资源的索引,当触发onProgress的时候,就能根据索引去更新列表中对应项的进度了。最后归并的时候就能计算出正确的进度了。
剩下的事情就是整合我们所有的代码,然后对其进行测试了
我们可以用下面的代码来模拟一下整个加载过程:
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
*/
这种方式的优点是能避开全局管理loadedCount和totalCount,把这部分工作交回资源内部管理,它要做的只是对大任务进行归并计算。
缺点也很明显,需要对onProgress接口进行一次统一。在已有项目中推进难度很大,所以比较适合新项目或者小项目去实践。
以上就是关于JavaScript进度管理方式的介绍了,上述方式仅供参考,希望大家能有帮助,想要了解更多JavaScript进度管理的内容,可以继续浏览群英网络其他相关的文章。
文本转载自脚本之家
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
虽然很少会遇到给bind返回的函数做new操作的场景,但面试中还是会涉及到的,所以本文将实现一下兼容new操作的bind写法,顺便学习一下new操作符,需要的可以参考一下
目录前言一、作用域(scope)1、作用域的分类二、预编译三、作用域链前言我们需要先知道的是引擎,引擎的工作简单粗暴,就是负责javascript从头到尾代码的执行。引擎的一个好朋友是编译器,主要负责代码的分析和编译等;引擎的另一个好朋友就是今天的主角--作用域。那么作用域用来干什么呢?作用域链跟作用域又有什么关系呢?
这篇文章主要介绍了Vue 服务端渲染SSR示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
JS中的History历史对象的用法是什么?JavaScript编程中的History历史对象是一定要学习的基本内容,想要了解详细知识点,可以跟随小编来了解下。
JavaScript怎么创建多个对象?javascript面向对象创建多个对象的方法,哪个最好用?字面量?工厂模式方法?构造函数方法?原型方法?原型加构造函数方法?进来一看便知。
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008