imagepool前端圖片加載管理器(JavaScript圖片連接池)
前言
imagepool是一款管理圖片加載的JS工具,通過imagepool可以控制圖片并發(fā)加載個數(shù)。
對于圖片加載,最原始的方式就是直接寫個img標簽,比如:<img src="圖片url" />。
經(jīng)過不斷優(yōu)化,出現(xiàn)了圖片延遲加載方案,這回圖片的URL不直接寫在src屬性中,而是寫在某個屬性中,比如:<img src="" data-src="圖片url" />。這樣瀏覽器就不會自動加載圖片,等到一個恰當?shù)臅r機需要加載了,則用js把data-src屬性中的url放到img標簽的src屬性中,或者讀出url后,用js去加載圖片,加載完成后再設(shè)置src屬性,顯示出圖片。
這看起來已經(jīng)控制的很好了,但依然會有問題。
雖然能做到只加載一部分圖片,但這一部分圖片,仍然可能是一個比較大的數(shù)量級。
這對于PC端來說,沒什么大不了,但對于移動端,圖片并發(fā)加載數(shù)量過多,極有可能引起應(yīng)用崩潰。
因此我們迫切需要一種圖片緩沖機制,來控制圖片加載并發(fā)。類似于后端的數(shù)據(jù)庫連接池,既不會創(chuàng)建過多連接,又能充分復(fù)用每一個連接。
至此,imagepool誕生了。
拙劣的原理圖

使用說明
首先要初始化連接池:
1 var imagepool = initImagePool(5);
initImagePool 是全局方法,任何地方都可以直接使用。作用是創(chuàng)建一個連接池,并且可以指定連接池的最大連接數(shù),可選,默認為5。
在同一個頁面中,多次調(diào)用initImagePool均返回同一個核心實例,永遠是第一個,有點單例的感覺。比如:
1 var imagepool1 = initImagePool(3); 2 var imagepool2 = initImagePool(7);
此時imagepool1和imagepool2的最大連接數(shù)均為3,內(nèi)部使用的是同一個核心實例。注意,是內(nèi)部的核心相同,并不是說imagepool1 === imagepool2。
初始化之后,就可以放心大膽的加載圖片了。
最簡單的調(diào)用方法如下:
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 });
直接在實例上調(diào)用load方法即可。
load方法有兩個參數(shù)。第一個參數(shù)是需要加載的圖片url,第二個參數(shù)是各種選項,包含了成功、失敗的回調(diào),回調(diào)時會傳入圖片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數(shù)組,就可以傳入多個圖片了。
每一個圖片加載成功(或失敗),都會調(diào)用success(或error)方法,并且傳入對應(yīng)的圖片url。
但有時候我們并不需要這樣頻繁的回調(diào),傳入一個圖片url數(shù)組,當這個數(shù)組中所有的圖片都處理完成后,再回調(diào)就可以了。
只需加一個選項即可:
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屬性,并設(shè)置為true,即可實現(xiàn)只回調(diào)一次。
這一次回調(diào),必然回調(diào)success方法,此時error方法是被忽略的。
此時回調(diào)success方法,不再是傳入一個圖片url參數(shù),而是傳入三個參數(shù),分別為:成功的url數(shù)組、失敗的url數(shù)組、總共處理的圖片個數(shù)。
此外,還有一個方法可以獲取連接池內(nèi)部狀態(tài):
1 var imagepool = initImagePool(10); 2 3 console.log(imagepool.info());
通過調(diào)用info方法,可以得到當前時刻連接池內(nèi)部狀態(tài),數(shù)據(jù)結(jié)構(gòu)如下:
- Object.task.count 連接池中等待處理的任務(wù)數(shù)量
- Object.thread.count 連接池最大連接數(shù)
- Object.thread.free 連接池空閑連接數(shù)
建議不要頻繁調(diào)用此方法。
最后需要說明的是,如果圖片加載失敗,最多會嘗試3次,如果最后還是加載失敗,才回調(diào)error方法。嘗試次數(shù)可在源碼中修改。
最最后再強調(diào)一下,讀者可以盡情的往連接池中push圖片,完全不必擔心并發(fā)過多的問題,imagepool會有條不絮的幫你加載這些圖片。
最最最后,必須說明的是,imagepool理論上不會降低圖片加載速度,只不過是平緩的加載。
源碼
1 (function(exports){ 2 //單例 3 var instance = null; 4 var emptyFn = function(){}; 5 6 //初始默認配置 7 var config_default = { 8 //線程池"線程"數(shù)量 9 thread: 5, 10 //圖片加載失敗重試次數(shù) 11 //重試2次,加上原有的一次,總共是3次 12 "try": 2 13 }; 14 15 //工具 16 var _helpers = { 17 //設(shè)置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 * 構(gòu)造方法 51 * @param max 最大連接數(shù)。數(shù)值。 52 */ 53 function ImagePool(max){ 54 //最大并發(fā)數(shù)量 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 //回調(diào) 79 //_s.getNode(this).options.success.call(null, this.src); 80 _s.notice(_s.getNode(this), "success", this.src); 81 82 //處理任務(wù) 83 _s.executeLink(this); 84 }; 85 img.onerror = function(e){ 86 var node = _s.getNode(this); 87 88 //判斷嘗試次數(shù) 89 if(node.try < config_default.try){ 90 node.try = node.try + 1; 91 //再次追加到任務(wù)鏈表末尾 92 _s.appendNode(_s.createNode(node.src, node.options, node.notice, node.group, node.try)); 93 94 }else{ 95 //error回調(diào) 96 //node.options.error.call(null, this.src); 97 _s.notice(node, "error", this.src); 98 } 99 100 //處理任務(wù) 101 _s.executeLink(this); 102 }; 103 obj.img = img; 104 obj.free = true; 105 this.pool.push(obj); 106 } 107 }; 108 109 /** 110 * 回調(diào)封裝 111 * @param node 節(jié)點。對象。 112 * @param status 狀態(tài)。字符串。可選值:success(成功)|error(失敗) 113 * @param src 圖片路徑。字符串。 114 */ 115 ImagePool.prototype.notice = function(node, status, src){ 116 node.notice(status, src); 117 }; 118 119 /** 120 * 處理鏈表任務(wù) 121 * @param dom 圖像dom對象。對象。 122 */ 123 ImagePool.prototype.executeLink = function(dom){ 124 //判斷鏈表是否存在節(jié)點 125 if(this.linkHead){ 126 //加載下一個圖片 127 this.setSrc(dom, this.linkHead); 128 //去除鏈表頭 129 this.shiftNode(); 130 }else{ 131 //設(shè)置自身狀態(tài)為空閑 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屬性設(shè)置 152 * 因為改變src屬性相當于加載圖片,所以把操作封裝起來 153 * @param dom 圖像dom對象。對象。 154 * @param node 節(jié)點。對象。 155 */ 156 ImagePool.prototype.setSrc = function(dom, node){ 157 //設(shè)置池中的"線程"為非空閑狀態(tài) 158 this.status(dom, false); 159 //關(guān)聯(lián)節(jié)點 160 this.setNode(dom, node); 161 //加載圖片 162 dom.src = node.src; 163 }; 164 165 /** 166 * 更新池中的"線程"狀態(tài) 167 * @param dom 圖像dom對象。對象。 168 * @param status 狀態(tài)。布爾。可選值:true(空閑)|false(非空閑) 169 */ 170 ImagePool.prototype.status = function(dom, status){ 171 var id = _helpers.getAttr(dom, "id"); 172 this.pool[id].free = status; 173 //空閑狀態(tài),清除關(guān)聯(lián)的節(jié)點 174 if(status){ 175 this.pool[id].node = null; 176 } 177 }; 178 179 /** 180 * 更新池中的"線程"的關(guān)聯(lián)節(jié)點 181 * @param dom 圖像dom對象。對象。 182 * @param node 節(jié)點。對象。 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 * 獲取池中的"線程"的關(guān)聯(lián)節(jié)點 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字符串數(shù)組。 202 * @param options 用戶自定義參數(shù)。包含:success回調(diào)、error回調(diào)、once標識。 203 */ 204 ImagePool.prototype.load = function(src, options){ 205 var srcs = [], 206 free = null, 207 length = 0, 208 i = 0, 209 //只初始化一次回調(diào)策略 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 //實際上是作為另一個任務(wù)單獨執(zhí)行,防止回調(diào)函數(shù)執(zhí)行時間過長影響圖片加載速度 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 //直接回調(diào) 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 //設(shè)置組元素個數(shù) 250 group.count = srcs.length; 251 //遍歷需要加載的圖片 252 for(i = 0, length = srcs.length; i < length; i++){ 253 //創(chuàng)建節(jié)點 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 //沒有空閑,將任務(wù)添加到鏈表 262 this.appendNode(node); 263 } 264 } 265 }; 266 267 /** 268 * 獲取內(nèi)部狀態(tài)信息 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 //線程總數(shù)量 279 info.thread.count = this.pool.length; 280 //空閑線程數(shù)量 281 info.thread.free = 0; 282 //任務(wù) 283 info.task = {}; 284 //待處理任務(wù)數(shù)量 285 info.task.count = 0; 286 287 //獲取空閑"線程"數(shù)量 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 //獲取任務(wù)數(shù)量(任務(wù)鏈長度) 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 * 創(chuàng)建節(jié)點 309 * @param src 圖片路徑。字符串。 310 * @param options 用戶自定義參數(shù)。包含:success回調(diào)、error回調(diào)、once標識。 311 * @param notice 回調(diào)策略。 函數(shù)。 312 * @param group 組信息。對象。{count: 0, success: [], error: []} 313 * @param tr 出錯重試次數(shù)。數(shù)值。默認為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 * 向任務(wù)鏈表末尾追加節(jié)點 330 * @param node 節(jié)點。對象。 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 //判斷鏈表是否存在節(jié)點 348 if(this.linkHead){ 349 //修改鏈表頭 350 this.linkHead = this.linkHead.next || null; 351 } 352 }; 353 354 /** 355 * 導(dǎo)出對外接口 356 * @param max 最大連接數(shù)。數(shù)值。 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 * 內(nèi)部信息 375 * @returns {*|any|void} 376 */ 377 info: function(){ 378 return instance.info.call(instance); 379 } 380 }; 381 }; 382 383 }(this));

浙公網(wǎng)安備 33010602011771號