imagepool前端圖片加載管理器(JavaScript圖片鏈接池)

前言node

 

      imagepool是一款管理圖片加載的JS工具,經過imagepool能夠控制圖片併發加載個數。數據庫

      對於圖片加載,最原始的方式就是直接寫個img標籤,好比:<img src="圖片url" />後端

      通過不斷優化,出現了圖片延遲加載方案,這回圖片的URL不直接寫在src屬性中,而是寫在某個屬性中,好比:<img src="" data-src="圖片url" />。這樣瀏覽器就不會自動加載圖片,等到一個恰當的時機須要加載了,則用js把data-src屬性中的url放到img標籤的src屬性中,或者讀出url後,用js去加載圖片,加載完成後再設置src屬性,顯示出圖片。數組

      這看起來已經控制的很好了,但依然會有問題。瀏覽器

      雖然能作到只加載一部分圖片,但這一部分圖片,仍然多是一個比較大的數量級。數據結構

      這對於PC端來講,沒什麼大不了,但對於移動端,圖片併發加載數量過多,極有可能引發應用崩潰。併發

      所以咱們迫切須要一種圖片緩衝機制,來控制圖片加載併發。相似於後端的數據庫鏈接池,既不會建立過多鏈接,又能充分複用每個鏈接。app

      至此,imagepool誕生了。dom

 

 拙劣的原理圖異步

 

 

使用說明

 

     首先要初始化鏈接池:

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: true
13 });

     經過在選項中加一個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 = function(dom, node){
157         //設置池中的"線程"爲非空閒狀態
158         this.status(dom, false);
159         //關聯節點
160         this.setNode(dom, node);
161         //加載圖片
162         dom.src = 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 = 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));
相關文章
相關標籤/搜索