首页 > 代码库 > imagepool前端图片加载管理器(JavaScript图片连接池)
imagepool前端图片加载管理器(JavaScript图片连接池)
前言
imagepool是一款管理图片加载的JS工具,通过imagepool可以控制图片并发加载个数。
对于图片加载,最原始的方式就是直接写个img标签,比如:<img src="http://www.mamicode.com/图片url" />。
经过不断优化,出现了图片延迟加载方案,这回图片的URL不直接写在src属性中,而是写在某个属性中,比如:<img src="" data-src="http://www.mamicode.com/图片url" />。这样浏览器就不会自动加载图片,等到一个恰当的时机需要加载了,则用js把data-src属性中的url放到img标签的src属性中,或者读出url后,用js去加载图片,加载完成后再设置src属性,显示出图片。
这看起来已经控制的很好了,但依然会有问题。
虽然能做到只加载一部分图片,但这一部分图片,仍然可能是一个比较大的数量级。
这对于PC端来说,没什么大不了,但对于移动端,图片并发加载数量过多,极有可能引起应用崩溃。
因此我们迫切需要一种图片缓冲机制,来控制图片加载并发。类似于后端的数据库连接池,既不会创建过多连接,又能充分复用每一个连接。
至此,imagepool诞生了。
拙劣的原理图
使用说明
首先要初始化连接池:
1 var imagepool = initImagePool(5);
initImagePool 是全局方法,任何地方都可以直接使用。作用是创建一个连接池,并且可以指定连接池的最大连接数,可选,默认为5。
在同一个页面中,多次调用initImagePool均返回同一个核心实例,永远是第一个,有点单例的感觉。比如:
1 var imagepool1 = initImagePool(3);2 var imagepool2 = initImagePool(7);
此时imagepool1和imagepool2的最大连接数均为3,内部使用的是同一个核心实例。注意,是内部的核心相同,并不是说imagepool1 === imagepool2。
初始化之后,就可以放心大胆的加载图片了。
最简单的调用方法如下:
1 var imagepool = initImagePool(10); 2 3 imagepool.load("图片url",{ 4 success: function(src){ 5 console.log("success:::::"+src); 6 }, 7 error: function(src){ 8 console.log("error:::::"+src); 9 }10 });
直接在实例上调用load方法即可。
load方法有两个参数。第一个参数是需要加载的图片url,第二个参数是各种选项,包含了成功、失败的回调,回调时会传入图片url。
这样写只能传入一张图片,因此,也可以写成如下形式:
1 var imagepool = initImagePool(10); 2 3 imagepool.load(["图片1url","图片2url"],{ 4 success: function(src){ 5 console.log("success:::::"+src); 6 }, 7 error: function(src){ 8 console.log("error:::::"+src); 9 }10 });
通过传入一个图片url数组,就可以传入多个图片了。
每一个图片加载成功(或失败),都会调用success(或error)方法,并且传入对应的图片url。
但有时候我们并不需要这样频繁的回调,传入一个图片url数组,当这个数组中所有的图片都处理完成后,再回调就可以了。
只需加一个选项即可:
1 var imagepool = initImagePool(10); 2 3 imagepool.load(["图片1url ","图片2url "],{ 4 success: function(sArray, eArray, count){ 5 console.log("sArray:::::"+sArray); 6 console.log("eArray:::::"+eArray); 7 console.log("count:::::"+count); 8 }, 9 error: function(src){10 console.log("error:::::"+src);11 },12 once: true13 });
通过在选项中加一个once属性,并设置为true,即可实现只回调一次。
这一次回调,必然回调success方法,此时error方法是被忽略的。
此时回调success方法,不再是传入一个图片url参数,而是传入三个参数,分别为:成功的url数组、失败的url数组、总共处理的图片个数。
此外,还有一个方法可以获取连接池内部状态:
1 var imagepool = initImagePool(10);2 3 console.log(imagepool.info());
通过调用info方法,可以得到当前时刻连接池内部状态,数据结构如下:
- Object.task.count 连接池中等待处理的任务数量
- Object.thread.count 连接池最大连接数
- Object.thread.free 连接池空闲连接数
建议不要频繁调用此方法。
最后需要说明的是,如果图片加载失败,最多会尝试3次,如果最后还是加载失败,才回调error方法。尝试次数可在源码中修改。
最最后再强调一下,读者可以尽情的往连接池中push图片,完全不必担心并发过多的问题,imagepool会有条不絮的帮你加载这些图片。
最最最后,必须说明的是,imagepool理论上不会降低图片加载速度,只不过是平缓的加载。
源码
1 (function(exports){ 2 //单例 3 var instance = null; 4 var emptyFn = function(){}; 5 6 //初始默认配置 7 var config_default = { 8 //线程池"线程"数量 9 thread: 5, 10 //图片加载失败重试次数 11 //重试2次,加上原有的一次,总共是3次 12 "try": 2 13 }; 14 15 //工具 16 var _helpers = { 17 //设置dom属性 18 setAttr: (function(){ 19 var img = new Image(); 20 //判断浏览器是否支持HTML5 dataset 21 if(img.dataset){ 22 return function(dom, name, value){ 23 dom.dataset[name] = value; 24 return value; 25 }; 26 }else{ 27 return function(dom, name, value){ 28 dom.setAttribute("data-"+name, value); 29 return value; 30 }; 31 } 32 }()), 33 //获取dom属性 34 getAttr: (function(){ 35 var img = new Image(); 36 //判断浏览器是否支持HTML5 dataset 37 if(img.dataset){ 38 return function(dom, name){ 39 return dom.dataset[name]; 40 }; 41 }else{ 42 return function(dom, name){ 43 return dom.getAttribute("data-"+name); 44 }; 45 } 46 }()) 47 }; 48 49 /** 50 * 构造方法 51 * @param max 最大连接数。数值。 52 */ 53 function ImagePool(max){ 54 //最大并发数量 55 this.max = max || config_default.thread; 56 this.linkHead = null; 57 this.linkNode = null; 58 //加载池 59 //[{img: dom,free: true, node: node}] 60 //node 61 //{src: "", options: {success: "fn",error: "fn", once: true}, try: 0} 62 this.pool = []; 63 } 64 65 /** 66 * 初始化 67 */ 68 ImagePool.prototype.initPool = function(){ 69 var i,img,obj,_s; 70 71 _s = this; 72 for(i = 0;i < this.max; i++){ 73 obj = {}; 74 img = new Image(); 75 _helpers.setAttr(img, "id", i); 76 img.onload = function(){ 77 var id,src; 78 //回调 79 //_s.getNode(this).options.success.call(null, this.src); 80 _s.notice(_s.getNode(this), "success", this.src); 81 82 //处理任务 83 _s.executeLink(this); 84 }; 85 img.onerror = function(e){ 86 var node = _s.getNode(this); 87 88 //判断尝试次数 89 if(node.try < config_default.try){ 90 node.try = node.try + 1; 91 //再次追加到任务链表末尾 92 _s.appendNode(_s.createNode(node.src, node.options, node.notice, node.group, node.try)); 93 94 }else{ 95 //error回调 96 //node.options.error.call(null, this.src); 97 _s.notice(node, "error", this.src); 98 } 99 100 //处理任务101 _s.executeLink(this);102 };103 obj.img = img;104 obj.free = true;105 this.pool.push(obj);106 }107 };108 109 /**110 * 回调封装111 * @param node 节点。对象。112 * @param status 状态。字符串。可选值:success(成功)|error(失败)113 * @param src 图片路径。字符串。114 */115 ImagePool.prototype.notice = function(node, status, src){116 node.notice(status, src);117 };118 119 /**120 * 处理链表任务121 * @param dom 图像dom对象。对象。122 */123 ImagePool.prototype.executeLink = function(dom){124 //判断链表是否存在节点125 if(this.linkHead){126 //加载下一个图片127 this.setSrc(dom, this.linkHead);128 //去除链表头129 this.shiftNode();130 }else{131 //设置自身状态为空闲132 this.status(dom, true);133 }134 };135 136 /**137 * 获取空闲"线程"138 */139 ImagePool.prototype.getFree = function(){140 var length,i;141 for(i = 0, length = this.pool.length; i < length; i++){142 if(this.pool[i].free){143 return this.pool[i];144 }145 }146 147 return null;148 };149 150 /**151 * 封装src属性设置152 * 因为改变src属性相当于加载图片,所以把操作封装起来153 * @param dom 图像dom对象。对象。154 * @param node 节点。对象。155 */156 ImagePool.prototype.setSrc = http://www.mamicode.com/function(dom, node){157 //设置池中的"线程"为非空闲状态158 this.status(dom, false);159 //关联节点160 this.setNode(dom, node);161 //加载图片162 dom.src =http://www.mamicode.com/ node.src;163 };164 165 /**166 * 更新池中的"线程"状态167 * @param dom 图像dom对象。对象。168 * @param status 状态。布尔。可选值:true(空闲)|false(非空闲)169 */170 ImagePool.prototype.status = function(dom, status){171 var id = _helpers.getAttr(dom, "id");172 this.pool[id].free = status;173 //空闲状态,清除关联的节点174 if(status){175 this.pool[id].node = null;176 }177 };178 179 /**180 * 更新池中的"线程"的关联节点181 * @param dom 图像dom对象。对象。182 * @param node 节点。对象。183 */184 ImagePool.prototype.setNode = function(dom, node){185 var id = _helpers.getAttr(dom, "id");186 this.pool[id].node = node;187 return this.pool[id].node === node;188 };189 190 /**191 * 获取池中的"线程"的关联节点192 * @param dom 图像dom对象。对象。193 */194 ImagePool.prototype.getNode = function(dom){195 var id = _helpers.getAttr(dom, "id");196 return this.pool[id].node;197 };198 199 /**200 * 对外接口,加载图片201 * @param src 可以是src字符串,也可以是src字符串数组。202 * @param options 用户自定义参数。包含:success回调、error回调、once标识。203 */204 ImagePool.prototype.load = function(src, options){205 var srcs = [],206 free = null,207 length = 0,208 i = 0,209 //只初始化一次回调策略210 notice = (function(){211 if(options.once){212 return function(status, src){213 var g = this.group,214 o = this.options;215 216 //记录217 g[status].push(src);218 //判断改组是否全部处理完成219 if(g.success.length + g.error.length === g.count){220 //异步221 //实际上是作为另一个任务单独执行,防止回调函数执行时间过长影响图片加载速度222 setTimeout(function(){223 o.success.call(null, g.success, g.error, g.count);224 },1);225 }226 };227 }else{228 return function(status, src){229 var o = this.options;230 231 //直接回调232 setTimeout(function(){233 o[status].call(null, src);234 },1);235 };236 }237 }()),238 group = {239 count: 0,240 success: [],241 error: []242 },243 node = null;244 options = options || {};245 options.success = options.success || emptyFn;246 options.error = options.error || emptyFn;247 srcs = srcs.concat(src);248 249 //设置组元素个数250 group.count = srcs.length;251 //遍历需要加载的图片252 for(i = 0, length = srcs.length; i < length; i++){253 //创建节点254 node = this.createNode(srcs[i], options, notice, group);255 //判断线程池是否有空闲256 free = this.getFree();257 if(free){258 //有空闲,则立即加载图片259 this.setSrc(free.img, node);260 }else{261 //没有空闲,将任务添加到链表262 this.appendNode(node);263 }264 }265 };266 267 /**268 * 获取内部状态信息269 * @returns {{}}270 */271 ImagePool.prototype.info = function(){272 var info = {},273 length = 0,274 i = 0,275 node = null;276 //线程277 info.thread = {};278 //线程总数量279 info.thread.count = this.pool.length;280 //空闲线程数量281 info.thread.free = 0;282 //任务283 info.task = {};284 //待处理任务数量285 info.task.count = 0;286 287 //获取空闲"线程"数量288 for(i = 0, length = this.pool.length; i < length; i++){289 if(this.pool[i].free){290 info.thread.free = info.thread.free + 1;291 }292 }293 294 //获取任务数量(任务链长度)295 node = this.linkHead;296 if(node){297 info.task.count = info.task.count + 1;298 while(node.next){299 info.task.count = info.task.count + 1;300 node = node.next;301 }302 }303 304 return info;305 };306 307 /**308 * 创建节点309 * @param src 图片路径。字符串。310 * @param options 用户自定义参数。包含:success回调、error回调、once标识。311 * @param notice 回调策略。 函数。312 * @param group 组信息。对象。{count: 0, success: [], error: []}313 * @param tr 出错重试次数。数值。默认为0。314 * @returns {{}}315 */316 ImagePool.prototype.createNode = function(src, options, notice, group, tr){317 var node = {};318 319 node.src =http://www.mamicode.com/ src;320 node.options = options;321 node.notice = notice;322 node.group = group;323 node.try = tr || 0;324 325 return node;326 };327 328 /**329 * 向任务链表末尾追加节点330 * @param node 节点。对象。331 */332 ImagePool.prototype.appendNode = function(node){333 //判断链表是否为空334 if(!this.linkHead){335 this.linkHead = node;336 this.linkNode = node;337 }else{338 this.linkNode.next = node;339 this.linkNode = node;340 }341 };342 343 /**344 * 删除链表头345 */346 ImagePool.prototype.shiftNode = function(){347 //判断链表是否存在节点348 if(this.linkHead){349 //修改链表头350 this.linkHead = this.linkHead.next || null;351 }352 };353 354 /**355 * 导出对外接口356 * @param max 最大连接数。数值。357 * @returns {{load: Function, info: Function}}358 */359 exports.initImagePool = function(max){360 361 if(!instance){362 instance = new ImagePool(max);363 instance.initPool();364 }365 366 return {367 /**368 * 加载图片369 */370 load: function(){371 instance.load.apply(instance, arguments);372 },373 /**374 * 内部信息375 * @returns {*|any|void}376 */377 info: function(){378 return instance.info.call(instance);379 }380 };381 };382 383 }(this));
imagepool前端图片加载管理器(JavaScript图片连接池)