JavaScript前端图片加载管理器imagepool使用详解
本文详细介绍JavaScript工具imagepool,用于控制图片并发加载数量。通过初始化连接池、调用load方法加载单张或多张图片,支持成功/失败回调及once批量处理选项。内置重试机制与状态查询,源码展示核心实现,帮助前端优化移动端图片加载性能。
前言与背景
前言
imagepool是一个JavaScript工具,用于管理图片加载过程,允许开发者控制同时加载的图片数量。
传统的图片加载方式通常直接通过img标签指定src属性,例如:<img src="图片url" />。
随着技术演进,出现了延迟加载方案。此时图片URL不再直接置于src属性,而是存储在自定义属性(如data-src)中,例如:<img src="" data-src="图片url" />。浏览器不会自动加载此类图片,直到特定时刻通过JavaScript将data-src的值赋给src属性,或手动创建Image对象加载完成后设置src,从而显示图片。
尽管上述方案已具备一定控制力,但仍存在局限性。
即使仅加载部分图片,其数量仍可能达到较大规模。
对于PC端浏览器而言,这通常不是问题;但在移动端,过多的图片并发加载可能导致应用崩溃或性能严重下降。
因此,亟需一种类似后端数据库连接池的图片缓冲机制,既能限制并发数量,又能高效复用资源。
基于此需求,imagepool应运而生。
以下是其工作原理的示意图。
imagepool使用方法
使用说明
首先,需要初始化连接池:
var imagepool = initImagePool(5); initImagePool是一个全局方法,可在页面任意位置调用。它用于创建连接池,并可选指定最大连接数,默认值为5。
在同一页面中多次调用initImagePool,均会返回同一个核心实例(即首次调用时的实例),表现出单例模式的特征。例如:
代码如下:
var imagepool1 = initImagePool(3);
var imagepool2 = initImagePool(7);
此时,imagepool1和imagepool2的最大连接数均为3,因为它们内部共享同一个核心实例。需要注意的是,这仅指内部核心相同,imagepool1与imagepool2本身并不相等(imagepool1 !== imagepool2)。
初始化完成后,即可安全地加载图片。
最简单的调用方式如下:
代码如下:
var imagepool = initImagePool(10);
imagepool.load("图片url",{
success: function(src){
console.log("success:::::"+src);
},
error: function(src){
console.log("error:::::"+src);
}
});
只需在实例上调用load方法即可。
load方法接受两个参数:第一个是待加载的图片URL,第二个是配置选项对象,包含success和error回调函数,回调时会传入对应的图片URL。
上述方式仅支持单张图片加载,若需加载多张图片,可采用以下形式:
代码如下:
var imagepool = initImagePool(10);
imagepool.load(["图片1url","图片2url"],{
success: function(src){
console.log("success:::::"+src);
},
error: function(src){
console.log("error:::::"+src);
}
});
通过传入图片URL数组,即可一次性加载多张图片。
每张图片加载成功或失败时,都会触发对应的success或error回调,并传入该图片的URL。
然而,有时我们并不需要为每张图片单独触发回调,而是希望在所有图片处理完毕后统一回调。
只需在配置选项中添加一个属性即可实现:
代码如下:
var imagepool = initImagePool(10);
imagepool.load(["图片1url ","图片2url "],{
success: function(sArray, eArray, count){
console.log("sArray:::::"+sArray);
console.log("eArray:::::"+eArray);
console.log("count:::::"+count);
},
error: function(src){
console.log("error:::::"+src);
},
once: true
});
在配置选项中设置once属性为true,即可实现仅回调一次。
在此模式下,始终触发success回调,而忽略error回调。
此时success回调接收三个参数:成功URL数组、失败URL数组以及处理的图片总数。
此外,还提供获取连接池内部状态的方法:
代码如下:
var imagepool = initImagePool(10);
console.log(imagepool.info());
调用info方法可获取当前连接池状态,返回的数据结构如下:
Object.task.count 连接池中等待处理的任务数量 Object.thread.count 连接池最大连接数 Object.thread.free 连接池空闲连接数 建议避免频繁调用此方法。
最后需说明,若图片加载失败,系统最多尝试3次(包括初始请求),若仍失败则触发error回调。重试次数可在源码中修改。
再次强调,用户可向连接池持续添加图片(push),无需担心并发过载,imagepool会有序处理这些请求。
最后补充,imagepool理论上不会降低图片加载速度,仅使加载过程更加平缓。
源码解析
源码
代码如下:
(function(exports){
//单例
var instance = null;
var emptyFn = function(){};
//初始默认配置
var config_default = {
//线程池"线程"数量
thread: 5,
//图片加载失败重试次数
//重试2次,加上原有的一次,总共是3次
"try": 2
};
//工具
var _helpers = {
//设置dom属性
setAttr: (function(){
var img = new Image();
//判断浏览器是否支持HTML5 dataset
if(img.dataset){
return function(dom, name, value){
dom.dataset[name] = value;
return value;
};
}else{
return function(dom, name, value){
dom.setAttribute("data-"+name, value);
return value;
};
}
}()),
//获取dom属性
getAttr: (function(){
var img = new Image();
//判断浏览器是否支持HTML5 dataset
if(img.dataset){
return function(dom, name){
return dom.dataset[name];
};
}else{
return function(dom, name){
return dom.getAttribute("data-"+name);
};
}
}())
};
/**
* 构造方法
* @param max 最大连接数。数值。
*/
function ImagePool(max){
//最大并发数量
this.max = max || config_default.thread;
this.linkHead = null;
this.linkNode = null;
//加载池
//[{img: dom,free: true, node: node}]
//node
//{src: "", options: {success: "fn",error: "fn", once: true}, try: 0}
this.pool = [];
}
/**
* 初始化
*/
ImagePool.prototype.initPool = function(){
var i,img,obj,_s;
_s = this;
for(i = 0;i < this.max; i++){
obj = {};
img = new Image();
_helpers.setAttr(img, "id", i);
img.onload = function(){
var id,src;
//回调
//_s.getNode(this).options.success.call(null, this.src);
_s.notice(_s.getNode(this), "success", this.src);
//处理任务
_s.executeLink(this);
};
img.onerror = function(e){
var node = _s.getNode(this);
//判断尝试次数
if(node.try < config_default.try){
node.try = node.try + 1;
//再次追加到任务链表末尾
_s.appendNode(_s.createNode(node.src, node.options, node.notice, node.group, node.try));
}else{
//error回调
//node.options.error.call(null, this.src);
_s.notice(node, "error", this.src);
}
//处理任务
_s.executeLink(this);
};
obj.img = img;
obj.free = true;
this.pool.push(obj);
}
};
/**
* 回调封装
* @param node 节点。对象。
* @param status 状态。字符串。可选值:success(成功)|error(失败)
* @param src 图片路径。字符串。
*/
ImagePool.prototype.notice = function(node, status, src){
node.notice(status, src);
};
/**
* 处理链表任务
* @param dom 图像dom对象。对象。
*/
ImagePool.prototype.executeLink = function(dom){
//判断链表是否存在节点
if(this.linkHead){
//加载下一个图片
this.setSrc(dom, this.linkHead);
//去除链表头
this.shiftNode();
}else{
//设置自身状态为空闲
this.status(dom, true);
}
};
/**
* 获取空闲"线程"
*/
ImagePool.prototype.getFree = function(){
var length,i;
for(i = 0, length = this.pool.length; i < length; i++){
if(this.pool[i].free){
return this.pool[i];
}
}
return null;
};
/**
* 封装src属性设置
* 因为改变src属性相当于加载图片,所以把操作封装起来
* @param dom 图像dom对象。对象。
* @param node 节点。对象。
*/
ImagePool.prototype.setSrc = function(dom, node){
//设置池中的"线程"为非空闲状态
this.status(dom, false);
//关联节点
this.setNode(dom, node);
//加载图片
dom.src = node.src;
};
/**
* 更新池中的"线程"状态
* @param dom 图像dom对象。对象。
* @param status 状态。布尔。可选值:true(空闲)|false(非空闲)
*/
ImagePool.prototype.status = function(dom, status){
var id = _helpers.getAttr(dom, "id");
this.pool[id].free = status;
//空闲状态,清除关联的节点
if(status){
this.pool[id].node = null;
}
};
/**
* 更新池中的"线程"的关联节点
* @param dom 图像dom对象。对象。
* @param node 节点。对象。
*/
ImagePool.prototype.setNode = function(dom, node){
var id = _helpers.getAttr(dom, "id");
this.pool[id].node = node;
return this.pool[id].node === node;
};
/**
* 获取池中的"线程"的关联节点
* @param dom 图像dom对象。对象。
*/
ImagePool.prototype.getNode = function(dom){
var id = _helpers.getAttr(dom, "id");
return this.pool[id].node;
};
/**
* 对外接口,加载图片
* @param src 可以是src字符串,也可以是src字符串数组。
* @param options 用户自定义参数。包含:success回调、error回调、once标识。
*/
ImagePool.prototype.load = function(src, options){
var srcs = [],
free = null,
length = 0,
i = 0,
//只初始化一次回调策略
notice = (function(){
if(options.once){
return function(status, src){
var g = this.group,
o = this.options;
//记录
g[status].push(src);
//判断改组是否全部处理完成
if(g.success.length + g.error.length === g.count){
//异步
//实际上是作为另一个任务单独执行,防止回调函数执行时间过长影响图片加载速度
setTimeout(function(){
o.success.call(null, g.success, g.error, g.count);
},1);
}
};
}else{
return function(status, src){
var o = this.options;
//直接回调
setTimeout(function(){
o[status].call(null, src);
},1);
};
}
}()),
group = {
count: 0,
success: [],
error: []
},
node = null;
options = options || {};
options.success = options.success || emptyFn;
options.error = options.error || emptyFn;
srcs = srcs.concat(src);
//设置组元素个数
group.count = srcs.length;
//遍历需要加载的图片
for(i = 0, length = srcs.length; i < length; i++){
//创建节点
node = this.createNode(srcs[i], options, notice, group);
//判断线程池是否有空闲
free = this.getFree();
if(free){
//有空闲,则立即加载图片
this.setSrc(free.img, node);
}else{
//没有空闲,将任务添加到链表
this.appendNode(node);
}
}
};
/**
* 获取内部状态信息
* @returns {{}}
*/
ImagePool.prototype.info = function(){
var info = {},
length = 0,
i = 0,
node = null;
//线程
info.thread = {};
//线程总数量
info.thread.count = this.pool.length;
//空闲线程数量
info.thread.free = 0;
//任务
info.task = {};
//待处理任务数量
info.task.count = 0;
//获取空闲"线程"数量
for(i = 0, length = this.pool.length; i < length; i++){
if(this.pool[i].free){
info.thread.free = info.thread.free + 1;
}
}
//获取任务数量(任务链长度)
node = this.linkHead;
if(node){
info.task.count = info.task.count + 1;
while(node.next){
info.task.count = info.task.count + 1;
node = node.next;
}
}
return info;
};
/**
* 创建节点
* @param src 图片路径。字符串。
* @param options 用户自定义参数。包含:success回调、error回调、once标识。
* @param notice 回调策略。 函数。
* @param group 组信息。对象。{count: 0, success: [], error: []}
* @param tr 出错重试次数。数值。默认为0。
* @returns {{}}
*/
ImagePool.prototype.createNode = function(src, options, notice, group, tr){
var node = {};
node.src = src;
node.options = options;
node.notice = notice;
node.group = group;
node.try = tr || 0;
return node;
};
/**
* 向任务链表末尾追加节点
* @param node 节点。对象。
*/
ImagePool.prototype.appendNode = function(node){
//判断链表是否为空
if(!this.linkHead){
this.linkHead = node;
this.linkNode = node;
}else{
this.linkNode.next = node;
this.linkNode = node;
}
};
/**
* 删除链表头
*/
ImagePool.prototype.shiftNode = function(){
//判断链表是否存在节点
if(this.linkHead){
//修改链表头
this.linkHead = this.linkHead.next || null;
}
};
/**
* 导出对外接口
* @param max 最大连接数。数值。
* @returns {{load: Function, info: Function}}
*/
exports.initImagePool = function(max){
if(!instance){
instance = new ImagePool(max);
instance.initPool();
}
return {
/**
* 加载图片
*/
load: function(){
instance.load.apply(instance, arguments);
},
/**
* 内部信息
* @returns {*|any|void}
*/
info: function(){
return instance.info.call(instance);
}
};
};
}(this));
以上就是这款特别棒的javascript前端图片加载管理器的使用方法示例,小伙伴们学会使用了吗?
ES6模块化中js文件怎么用import和export交互
2026-11-05 17:00:48
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53
