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 相关文章推荐
[原创]静态页面也可以实现预览 列表不同的显示方式
Oct 14 Javascript
基于jQuery的弹出框插件
Mar 18 Javascript
setTimeout()与setInterval()方法区别介绍
Dec 24 Javascript
JS更改select内option属性的方法
Oct 14 Javascript
jquery ztree实现模糊搜索功能
Feb 25 Javascript
jQuery控制文本框只能输入数字和字母及使用方法
May 26 Javascript
jQuery删除当前节点元素
Dec 07 Javascript
JS弹性运动实现方法分析
Dec 15 Javascript
jQuery接受后台传递的List的实例详解
Aug 02 jQuery
react-native滑动吸顶效果的实现过程
Jun 03 Javascript
vue各种事件监听实例(小结)
Jun 24 Javascript
element 动态合并表格的步骤
Dec 31 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 ajax异步读取rss文档数据
2016/03/29 PHP
PHP目录操作实例总结
2016/09/27 PHP
php json转换相关知识(小结)
2018/12/21 PHP
用js怎么把&amp;字符换成&quot;&amp;amp:&quot;
2006/10/19 Javascript
WEB高性能开发之疯狂的HTML压缩
2010/06/19 Javascript
jQuery Mobile页面跳转后未加载外部JS原因分析及解决
2013/03/18 Javascript
将list转换为json失败的原因
2013/12/17 Javascript
JavaScript中的原型和继承详解(图文)
2014/07/18 Javascript
JavaScript中获取Radio被选中的值
2015/11/11 Javascript
Bootstrap如何创建表单
2016/10/21 Javascript
浅析 NodeJs 的几种文件路径
2017/06/07 NodeJs
JS基于递归实现网页版计算器的方法分析
2017/12/20 Javascript
Vue 拦截器对token过期处理方法
2018/01/23 Javascript
微信小程序使用蓝牙小插件
2019/09/23 Javascript
Node配合WebSocket做多文件下载以及进度回传
2019/11/07 Javascript
js防抖函数和节流函数使用场景和实现区别示例分析
2020/04/11 Javascript
[53:10]2018DOTA2亚洲邀请赛 4.6 淘汰赛 VP vs VG 第一场
2018/04/11 DOTA
python微信跳一跳游戏辅助代码解析
2018/01/29 Python
python字符串的方法与操作大全
2018/01/30 Python
python:pandas合并csv文件的方法(图书数据集成)
2018/04/12 Python
pycharm 解除默认unittest模式的方法
2018/11/30 Python
pyQT5 实现窗体之间传值的示例
2019/06/20 Python
Python双链表原理与实现方法详解
2020/02/22 Python
django模型动态修改参数,增加 filter 字段的方式
2020/03/16 Python
浅谈tensorflow使用张量时的一些注意点tf.concat,tf.reshape,tf.stack
2020/06/23 Python
html5 Web SQL Database 之事务处理函数transaction与executeSQL解析
2013/11/07 HTML / CSS
利用Node实现HTML5离线存储的方法
2020/10/16 HTML / CSS
英国电动工具购买网站:Anglia Tool Centre
2017/04/25 全球购物
韩国演唱会订票网站:StubHub韩国
2019/01/17 全球购物
周仰杰(JIMMY CHOO)法国官方网站:闻名世界的鞋子品牌
2019/09/27 全球购物
PHP两种查询函数array/row的区别
2013/06/03 面试题
2014年幼儿园保育工作总结
2014/12/02 职场文书
2015年采购工作总结
2015/04/10 职场文书
你对自己的信用报告有过了解吗?
2019/07/09 职场文书
python接口测试返回数据为字典取值方式
2022/02/12 Python
vue整合百度地图显示指定地点信息
2022/04/06 Vue.js