首页 > 代码库 > 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图片连接池)