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 相关文章推荐
javascript addBookmark 加入收藏 多浏览器兼容
Aug 15 Javascript
javascript学习笔记(十八) 获得页面中的元素代码
Jun 20 Javascript
jquery select 设置默认选中的示例代码
Feb 07 Javascript
jQuery表格排序组件-tablesorter使用示例
May 26 Javascript
用简洁的jQuery方法toggleClass实现隔行换色
Oct 22 Javascript
AngularJS学习笔记之TodoMVC的分析
Feb 22 Javascript
jQuery实现字符串按指定长度加入特定内容的方法
Mar 11 Javascript
JQuery实现样式设置、追加、移除与切换的方法
Jun 11 Javascript
详解JavaScript中的事件流和事件处理程序
May 20 Javascript
浅谈vue中.vue文件解析流程
Apr 24 Javascript
JavaScript中工厂函数与构造函数示例详解
May 06 Javascript
Vue拖拽组件列表实现动态页面配置功能
Jun 17 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查询MySQL大量数据的时候内存占用分析
2011/07/22 PHP
PHP防范SQL注入的具体方法详解(测试通过)
2014/05/09 PHP
php上传图片之时间戳命名(保存路径)
2014/08/15 PHP
PHP封装的MSSql操作类完整实例
2016/05/26 PHP
php使用CutyCapt实现网页截图保存的方法
2016/10/03 PHP
php数据库的增删改查 php与javascript之间的交互
2017/08/31 PHP
Yii框架常见缓存应用实例小结
2019/09/09 PHP
理解javascript中的回调函数(callback)
2014/09/02 Javascript
AngularJS基础学习笔记之控制器
2015/05/10 Javascript
javascript中JSON对象与JSON字符串相互转换实例
2015/07/11 Javascript
JS实现浏览器状态栏文字从右向左弹出效果代码
2015/10/27 Javascript
jQuery Mobile弹出窗、弹出层知识汇总
2016/01/05 Javascript
Window.Open打开窗体和if嵌套代码
2016/04/15 Javascript
AngularJS基于factory创建自定义服务的方法详解
2017/05/25 Javascript
使用Bootstrap + Vue.js实现表格的动态展示、新增和删除功能
2017/11/27 Javascript
详解微信小程序中组件通讯
2018/10/30 Javascript
基于jquery ajax的多文件上传进度条过程解析
2019/09/11 jQuery
详解JavaScript 事件流
2020/09/02 Javascript
python获得linux下所有挂载点(mount points)的方法
2015/04/29 Python
Python中遍历字典过程中更改元素导致异常的解决方法
2016/05/12 Python
举例讲解Python的lambda语句声明匿名函数的用法
2016/07/01 Python
简单谈谈Python流程控制语句
2016/12/04 Python
一张图带我们入门Python基础教程
2017/02/05 Python
Python进阶之自定义对象实现切片功能
2019/01/07 Python
Django框架 查询Extra功能实现解析
2019/09/04 Python
python 通过exifread读取照片信息
2020/12/24 Python
简单总结CSS3中视窗单位Viewport的常见用法
2016/02/04 HTML / CSS
html5+svg学习指南之SVG基础知识
2014/12/17 HTML / CSS
美国隐形眼镜网:Major Lens
2018/02/09 全球购物
2014世界杯球队球队口号
2014/06/05 职场文书
2014年学习厉行节约反对浪费思想汇报
2014/09/10 职场文书
小学教育见习报告
2014/10/31 职场文书
2014年招商工作总结
2014/11/22 职场文书
工作试用期自我评价
2015/03/10 职场文书
Anaconda配置各版本Pytorch的实现
2021/08/07 Python
HTTP中的Content-type详解
2022/01/18 HTML / CSS