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 相关文章推荐
ExtJs 表单提交登陆实现代码
Aug 19 Javascript
基于jquery的分页控件(C#)
Jan 06 Javascript
用javascript替换URL中的参数值示例代码
Jan 27 Javascript
在JavaScript中判断整型的N种方法示例介绍
Jun 18 Javascript
jquery常用操作小结
Jul 21 Javascript
jQuery元素的隐藏与显示实例
Jan 20 Javascript
原生js模拟淘宝购物车项目实战
Nov 18 Javascript
JavaScript 详解预编译原理
Jan 22 Javascript
微信小程序实现指定显示行数多余文字去掉用省略号代替
Jul 25 Javascript
JS无限级导航菜单实现方法
Jan 05 Javascript
layui使用label标签的方法
Sep 14 Javascript
Openlayers显示瓦片网格信息的方法
Sep 28 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的中问验证码
2006/11/25 PHP
谈谈PHP的输入输出流
2007/02/14 PHP
PHP中文分词的简单实现代码分享
2011/07/17 PHP
在PHP中使用X-SendFile头让文件下载更快
2014/06/01 PHP
PHP 无限级分类
2017/05/04 PHP
PHP批量删除jQuery操作
2017/07/23 PHP
Javascript 刷新全集常用代码
2009/11/22 Javascript
制作jquery遮罩层效果导航菜单代码分享
2013/12/25 Javascript
jquery ajax跨域解决方法(json方式)
2014/02/04 Javascript
根据user-agent判断蜘蛛代码黑帽跳转代码(js版与php版本)
2015/09/14 Javascript
Ubuntu系统下Angularjs开发环境安装
2016/09/01 Javascript
js实现网页定位导航功能
2017/03/07 Javascript
Bootstrap table使用方法总结
2017/05/10 Javascript
快速解决vue-cli不能初始化webpack模板的问题
2018/03/20 Javascript
layui问题之模拟select点击事件的实例讲解
2018/08/15 Javascript
一些你可能不熟悉的JS知识点总结
2019/03/15 Javascript
vue+elementUI中表格高亮或字体颜色改变操作
2020/11/02 Javascript
[01:56]无止竞 再出发——中国军团出征2017年DOTA2国际邀请赛
2017/07/05 DOTA
[13:25]VP vs VICI (BO3)
2018/06/07 DOTA
[50:44]DOTA2-DPC中国联赛 正赛 SAG vs Dragon BO3 第二场 2月22日
2021/03/11 DOTA
Python实现的批量下载RFC文档
2015/03/10 Python
wxPython实现窗口用图片做背景
2018/04/25 Python
对python中的argv和argc使用详解
2018/12/15 Python
Python Django切换MySQL数据库实例详解
2019/07/16 Python
Python3 使用selenium插件爬取苏宁商家联系电话
2019/12/23 Python
HTML5实现Notification API桌面通知功能
2016/03/02 HTML / CSS
阿迪达斯中国官网:Adidas中国
2020/12/14 全球购物
铭立家具面试题
2012/12/06 面试题
人力资源管理专业应届生求职信
2013/09/28 职场文书
污水厂厂长岗位职责
2014/01/04 职场文书
探矿工程师自荐信
2014/01/24 职场文书
五好党支部事迹材料
2014/02/06 职场文书
目标管理责任书
2014/04/15 职场文书
学校师德承诺书
2014/05/23 职场文书
2014个人反腐倡廉思想汇报
2014/09/15 职场文书
入党现实表现材料
2014/12/23 职场文书