JavaScript前端图片加载管理器imagepool使用详解


Posted in Javascript onDecember 29, 2014

前言

      imagepool是一款管理图片加载的JS工具,通过imagepool可以控制图片并发加载个数。

      对于图片加载,最原始的方式就是直接写个img标签,比如:<img src="图片url" />。

      经过不断优化,出现了图片延迟加载方案,这回图片的URL不直接写在src属性中,而是写在某个属性中,比如:<img src="" data-src="图片url" />。这样浏览器就不会自动加载图片,等到一个恰当的时机需要加载了,则用js把data-src属性中的url放到img标签的src属性中,或者读出url后,用js去加载图片,加载完成后再设置src属性,显示出图片。

      这看起来已经控制的很好了,但依然会有问题。

      虽然能做到只加载一部分图片,但这一部分图片,仍然可能是一个比较大的数量级。

      这对于PC端来说,没什么大不了,但对于移动端,图片并发加载数量过多,极有可能引起应用崩溃。

      因此我们迫切需要一种图片缓冲机制,来控制图片加载并发。类似于后端的数据库连接池,既不会创建过多连接,又能充分复用每一个连接。

      至此,imagepool诞生了。

 拙劣的原理图

JavaScript前端图片加载管理器imagepool使用详解 

使用说明

     首先要初始化连接池:

 var imagepool = initImagePool(5);
     initImagePool 是全局方法,任何地方都可以直接使用。作用是创建一个连接池,并且可以指定连接池的最大连接数,可选,默认为5。

     在同一个页面中,多次调用initImagePool均返回同一个核心实例,永远是第一个,有点单例的感觉。比如:

 var imagepool1 = initImagePool(3);

 var imagepool2 = initImagePool(7);

     此时imagepool1和imagepool2的最大连接数均为3,内部使用的是同一个核心实例。注意,是内部的核心相同,并不是说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,第二个参数是各种选项,包含了成功、失败的回调,回调时会传入图片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。

     但有时候我们并不需要这样频繁的回调,传入一个图片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数组、失败的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前端图片加载管理器的使用方法示例,小伙伴们学会使用了吗?

Javascript 相关文章推荐
jquery中的$(document).ready()与window.onload的区别
Nov 18 Javascript
jQuery 渐变下拉菜单
Dec 15 Javascript
JQuery 技巧和窍门整理(8个)
Apr 22 Javascript
js前台判断开始时间是否小于结束时间
Feb 23 Javascript
javascript通过className来获取元素的简单示例代码
Jan 10 Javascript
浅谈JS闭包中的循环绑定处理程序
Nov 09 Javascript
JavaScript返回网页中锚点数目的方法
Apr 03 Javascript
Angular的自定义指令以及实例
Dec 26 Javascript
jquery+css实现侧边导航栏效果
Jun 12 jQuery
vue-cli+webpack项目 修改项目名称的方法
Feb 28 Javascript
解决vue select当前value没有更新到vue对象属性的问题
Aug 30 Javascript
优化Vue中date format的性能详解
Jan 13 Javascript
JavaScript版的TwoQueues缓存模型
Dec 29 #Javascript
浅谈重写window对象的方法
Dec 29 #Javascript
JavaScript中的console.log()函数详细介绍
Dec 29 #Javascript
深入分析原生JavaScript事件
Dec 29 #Javascript
JavaScript中的alert()函数使用技巧详解
Dec 29 #Javascript
JavaScript实现三阶幻方算法谜题解答
Dec 29 #Javascript
浅谈JavaScript Date日期和时间对象
Dec 29 #Javascript
You might like
php 三维饼图的实现代码
2008/09/28 PHP
Ubuntu server 11.04安装memcache及php使用memcache来存储session的方法
2016/05/31 PHP
JavaScript入门教程(5) js Screen屏幕对象
2009/01/31 Javascript
javascript Demo模态窗口
2009/12/06 Javascript
jQuery中bind,live,delegate与one方法的用法及区别解析
2013/12/30 Javascript
JS倒计时代码汇总
2014/11/25 Javascript
jquery使用animate方法实现控制元素移动
2015/03/27 Javascript
基于javascript实现九九乘法表
2016/03/27 Javascript
详解nodejs 文本操作模块-fs模块(五)
2016/12/23 NodeJs
利用JavaScript在网页实现八数码启发式A*算法动画效果
2017/04/16 Javascript
vue router使用query和params传参的使用和区别
2017/11/13 Javascript
Angular5集成eventbus的示例代码
2018/07/19 Javascript
解决js相同的正则多次调用test()返回的值却不同的问题
2018/10/10 Javascript
layui type2 通过url给iframe子页面传值的例子
2019/09/06 Javascript
python实现简单ftp客户端的方法
2015/06/28 Python
用Python写脚本,实现完全备份和增量备份的示例
2018/04/29 Python
Python3中内置类型bytes和str用法及byte和string之间各种编码转换 问题
2018/09/27 Python
Python静态类型检查新工具之pyright 使用指南
2019/04/26 Python
Python两台电脑实现TCP通信的方法示例
2019/05/06 Python
python 字符串追加实例
2019/07/20 Python
python发qq消息轰炸虐狗好友思路详解(完整代码)
2020/02/15 Python
Python实现动态循环输出文字功能
2020/05/07 Python
CSS3 @font-face属性使用指南
2014/12/12 HTML / CSS
在C#中如何实现多态
2014/07/02 面试题
办公室主任先进事迹
2014/01/18 职场文书
法制报告会主持词
2014/04/02 职场文书
《金子》教学反思
2014/04/13 职场文书
关于读书的演讲稿600字
2014/08/27 职场文书
大型主题婚礼活动策划方案
2014/09/15 职场文书
求职信格式范文
2015/03/19 职场文书
装饰施工员岗位职责
2015/04/11 职场文书
工作态度怎么写
2015/06/25 职场文书
网络舆情信息简报
2015/07/21 职场文书
感恩的心主题班会
2015/08/12 职场文书
六年级作文之家庭作文
2019/12/12 职场文书
MySQL中存储时间的最佳实践指南
2021/07/01 MySQL