美文网首页JQuery思想整理jQuery葵花宝典JQuery
D005+技术|jQuery-$.Callbacks()实现原理

D005+技术|jQuery-$.Callbacks()实现原理

作者: 冰果2016 | 来源:发表于2019-09-17 16:38 被阅读0次

    $.Callbacks用于管理函数队列,通过add添加处理函数到队列中,通过fire去执行这些处理函数。

    本节向大家介绍$.Callbacks的实现的原理,并简单实现一个自己的callbacks。

    概念解读

    从事件函数了解Callbacks,事件通常与函数配合使用,这样就可以通过触发事件来驱动函数的执行。
    原则上,一个事件对应一个事件函数。在一个事件对应多个事件函数的情况下,后者会覆盖前者。

    ele.onclick = function(){
        console.log("code")
    }
    ele.onclick = function(){
        console.log("code1")
    }
    
    

    上边这个Demo中后面绑定的这个事件函数会覆盖前边的,事件触发时会打印"code1"。

    事件驱动改造

    如果想让触发事件时执行多个函数,是否可行呢?

    当然可以,我们可以把需要执行的多个函数放在一个数组里,事件触发时循环执行这个数组里的函数。
    下面看一下伪代码:

    var callbacks = [function a(){}, function b(){}, function c(){}];
    ele.onclick = function(){
        var _this = this;
        callbacks.forEach(function(fn){
            fn.call(_this);
        });
    }
    

    而Callbacks并不仅仅是一个数组,而是一个容器。

    $.Callbacks API的使用

    基础应用

    
    // 1. $.Callbacks()返回Callbacks的实例对象
    var cb = $.Callbacks();
    // 2. 方法add()向内部队列添加函数
    cb.add(() => {
        console.log(1)
    });
    // 3. 方法fire()传入参数执行队列里的函数
    cb.fire();
    // 控制台结果:1
    

    Callbacks参数

    $.Callbacks()通过字符串参数的形式,支持4种特定的功能:once,unique,stopOnFalse,memory

    1. once 函数队列只执行一次
    // 不添加参数
    var cb = $.Callbacks();
    cb.add(() => {
        console.log(1)
    });
    cb.fire(); // 控制台打印: 1
    cb.fire(); // 控制台打印: 1
    
    

    如果不指定参数,调用fire()方法两次,控制台将打印两次1

    // 添加参数
    var cb = $.Callbacks("once");
    cb.add(() => {
        console.log(1)
    });
    cb.fire(); // 控制台打印: 1
    cb.fire();
    
    

    如果不指定参数为字符串once,调用fire()方法两次,控制台只打印一次1,需要注意:这里的字符串是区分大小写

    1. unique 使添加到内部函数队列里的函数保持唯一,不能重复添加
    // 不加参数 
    var cb = $.Callbacks();
    var test = function () {
        console.log("unique test");
    };
    cb.add(test, test);
    cb.fire(); 
    // 控制台打印: 
    // unique test  
    // unique test 
    

    我们通过add()方法往函数队列里添加了两个test函数,调用fire()方法,控制台打印了两次“unique test”。

    // 不加参数 
    var cb = $.Callbacks(“unique”);
    var test = function () {
        console.log("unique test");
    };
    cb.add(test, test);
    cb.fire(); // 控制台打印: unique test 
    

    指定参数unique,即使我们往函数队列里添加了两个test函数,调用fire()方法,控制台也只会打印一次“unique test”。

    1. stopOnFalse 内部函数队列顺序依次执行,当某个函数返回值为false时,停止该函数后边的函数继续执行
    // 不添加参数
    var cb = $.Callbacks();
    var test1 = function () {
        console.log("stopOnFalse one");
    };
    var test2 = function () {
        console.log("stopOnFalse two");
        return false;
    };
    var test3 = function () {
        console.log("stopOnFalse three");
    };
    cb.add(test1, test2, test3);
    cb.fire()
    // 控制台打印:
    // stopOnFalse one
    // stopOnFalse two
    // stopOnFalse three
    

    不添加参数时函数队列依次执行

    // 添加参数
    var cb = $.Callbacks("stopOnFalse");
    var test1 = function () {
        console.log("stopOnFalse one");
    };
    var test2 = function () {
        console.log("stopOnFalse two");
        return false;
    };
    var test3 = function () {
        console.log("stopOnFalse three");
    };
    cb.add(test1, test2, test3);
    cb.fire()
    // 控制台打印:
    // stopOnFalse one
    // stopOnFalse two
    

    指定参数为stopOnFalse,当函数执行到test2时,因为该函数返回了false,所以后边的test3将不再执行

    1. memory 当函数队列fire一次过后,内部会记录当前fire方法的参数。当下次调用add时,会把记录的参数传递给新添加的函数并立即执行这个新添加的函数。
    // 不添加参数
    var cb = $.Callbacks();
    var test1 = function () {
        console.log("memory one");
    };
    var test2 = function () {
        console.log("memory two");
    };
    cb.add(test1);
    cb.fire(); // 控制台打印:memory one
    cb.add(test2);
    
    // 添加参数
    var cb = $.Callbacks("memory");
    var test1 = function () {
        console.log("memory one");
    };
    var test2 = function () {
        console.log("memory two");
    };
    cb.add(test1);
    cb.fire(); // 控制台打印:memory one memory two
    cb.add(test2);
    

    $.Callbacks 实现

    参考jQuery的Callbacks,我们自己来实现一下。

    基本结构

    (function (root) {
        var _ = {
            callbacks: function () {
                console.log("test");
            }
        }
        root._ = _;
    })(this);
    _.callbacks(); // test
    
    

    通过一个闭包把执行上下文传入,因为在浏览器里执行这里传入的是Window对象,把_做为root的属性,这样我们在全局就可以访问_

    (function (root) {
        var optionsCache = {};
        var _ = {
            callbacks: function (options) {
                // 判断传入的参数类型
                // 字符串:存储在optionsCache对象里,如果该对象已经存在这个属性直接使用,如果不存在则创建这个属性值为true
                options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};
    
                console.log(options); // {"once":true,"memory":true}
                console.log(optionsCache) // {"once":true,"memory":true}
            }
    
        }
        root._ = _;
        var createOptions = function (options) {
            var object = {};
            // 如果同时指定两种类型,传入的参数为“once memory”,需拆解成单独的字符串
            options.split(/\s+/).forEach(value => {
                object[value] = optionsCache[value] = true;
            });
            return object;
        }
    })(this);
    _.callbacks("once memory");
    

    上边这个函数支持获取用户传过来的参数,并且支持用户同时指定多个类型的参数,并把这些参数存储在optionsCache缓存对象中。接下来看一下add(),fire()方法的实现。

    (function (root) {
        var optionsCache = {};
        var _ = {
            callbacks: function (options) {
                // 判断传入的参数类型
                // 字符串:存储在optionsCache对象里,如果该对象已经存在这个属性直接使用,如果不存在则创建这个属性值为true
                options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};
    
                var self = {
                    add: function () {
                        console.log("add");
                    },
                    fire: function () {
                        console.log("fire");
                    }
                }
                return self;
            }
    
        }
        root._ = _;
        var createOptions = function (options) {
            var object = {};
            // 如果同时指定两种类型,传入的参数为“once memory”,需拆解成单独的字符串
            options.split(/\s+/).forEach(value => {
                object[value] = optionsCache[value] = true;
            });
            return object;
        }
    })(this);
    
    var cb = _.callbacks();
    cb.add(); // add
    cb.fire(); // fire   
    

    在callbacks函数中创建self对象,并添加add方法和fire方法,然后把这个self返回,这样调用callbacks函数后就可以获取这个self。参考上面的代码。下一步,我们来实现一下add方法和fire方法。

    add方法和fire方法的实现

    (function (root) {
        var optionsCache = {};
        var _ = {
            callbacks: function (options) {
                // 判断传入的参数类型是否为字符串
                // 存储在optionsCache对象里,如果该对象已经存在这个属性直接使用,如果不存在则创建这个属性值为true
                options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};
    
                var fnList = [];
                var index, length;
                var fire = function (data) {
                    index = 0;
                    length = fnList.length;
                    for (; index < length; index++) {
                        fnList[index].apply(data[0], data[1]);
                    }
                }
                var self = {
                    add: function () {
                        // 将传入的参数转成数组
                        var argArr = Array.prototype.slice.call(arguments);
                        argArr.forEach((fn) => {
                            // 判断传入的参数是否为function
                            if (toString.call(fn) === "[object Function]") {
                                fnList.push(fn);
                            }
                        })
                    },
                    fireWith: function (context, arguments) {
                        var args = [context, arguments];
                        fire(args);
                    },
                    fire: function () {
                        self.fireWith(this, arguments);
                    }
                }
                return self;
            }
        }
        root._ = _;
        var createOptions = function (options) {
            var object = {};
            // 如果同时指定两种类型,传入的参数为“once memory”,需拆解成单独的字符串
            options.split(/\s+/).forEach(value => {
                object[value] = optionsCache[value] = true;
            });
            return object;
        }
    })(this);
    
    
    var cb = _.callbacks();
    cb.add(function a(params) {
        console.log("a");
    }, function b(params) {
        console.log("b");
    }); 
    cb.fire(); // a b
    
    

    add()可以往函数队列里添加函数,fire()可以依次执行队列的函数。上面的代码fire()是顺序执行队列,接下来实现通过指定callbacks的参数来控制函数队列的执行。

    参数stopOnFalse功能

    (function (root) {
        var optionsCache = {};
        var _ = {
            callbacks: function (options) {
                // 判断传入的参数类型是否为字符串
                // 存储在optionsCache对象里,如果该对象已经存在这个属性直接使用,如果不存在则创建这个属性值为true
                options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};
    
                var fnList = [];
                var index, length;
                var fire = function (data) {
                    index = 0;
                    length = fnList.length;
                    for (; index < length; index++) {
                        // 判断函数执行结果是否为false并且设置了stopOnFalse
                        // data[0]:执行上下文
                        // data[1]:执行时传入的参数
                        if (!(fnList[index].apply(data[0], data[1])) && options['stopOnFalse']) {
                            break;
                        }
                    }
                }
                var self = {
                    add: function () {
                        // 将传入的参数转成数组
                        var argArr = Array.prototype.slice.call(arguments);
                        argArr.forEach((fn) => {
                            // 判断传入的参数是否为function
                            if (toString.call(fn) === "[object Function]") {
                                fnList.push(fn);
                            }
                        })
                    },
                    fireWith: function (context, arguments) {
                        var args = [context, arguments];
                        fire(args);
                    },
                    fire: function () {
                        self.fireWith(this, arguments);
                    }
                }
                return self;
            }
        }
        root._ = _;
        var createOptions = function (options) {
            var object = {};
            // 如果同时指定两种类型,传入的参数为“once memory”,需拆解成单独的字符串
            options.split(/\s+/).forEach(value => {
                object[value] = optionsCache[value] = true;
            });
            return object;
        }
    })(this);
    
    // 不指定参数
    var cb = _.callbacks();
    cb.add(function a(params) {
        console.log("a");
        return false;
    }, function b(params) {
        console.log("b");
    }); 
    cb.fire(); // a b
    
    // 指定参数为stopOnFalse
    var cb = _.callbacks("stopOnFalse");
    cb.add(function a(params) {
        console.log("a");
        return false;
    }, function b(params) {
        console.log("b");
    }); 
    cb.fire(); // a
    

    上面代码实现了calLbacks指定参数为stopOnFalse时的效果。

    参数once功能

    (function (root) {
        var optionsCache = {};
        var _ = {
            callbacks: function (options) {
                // 判断传入的参数类型是否为字符串
                // 存储在optionsCache对象里,如果该对象已经存在这个属性直接使用,如果不存在则创建这个属性值为true
                options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};
    
                var fnList = [];
                var index, length, isFire;
                var fire = function (data) {
                    index = 0;
                    length = fnList.length;
                    for (; index < length; index++) {
                        // 判断函数执行结果是否为false并且设置了stopOnFalse
                        // data[0]:执行上下文
                        // data[1]:执行时传入的参数
                        if (!(fnList[index].apply(data[0], data[1])) && options['stopOnFalse']) {
                            break;
                        }
                    }
                    isFire = true;
                }
                var self = {
                    add: function () {
                        // 将传入的参数转成数组
                        var argArr = Array.prototype.slice.call(arguments);
                        argArr.forEach((fn) => {
                            // 判断传入的参数是否为function
                            if (toString.call(fn) === "[object Function]") {
                                fnList.push(fn);
                            }
                        })
                    },
                    fireWith: function (context, arguments) {
                        var args = [context, arguments];
                        if (!(options["once"] && isFire)) {
                            fire(args);
                        }
    
                    },
                    fire: function () {
                        self.fireWith(this, arguments);
                    }
                }
                return self;
            }
        }
        root._ = _;
        var createOptions = function (options) {
            var object = {};
            // 如果同时指定两种类型,传入的参数为“once memory”,需拆解成单独的字符串
            options.split(/\s+/).forEach(value => {
                object[value] = optionsCache[value] = true;
            });
            return object;
        }
    })(this);
    
    // 不添加参数
    var cb = _.callbacks();
    cb.add(function a(params) {
        console.log("a");
        return false;
    }, function b(params) {
        console.log("b");
    });
    cb.fire(); // ab
    cb.fire(); // ab
    
    // 添加参数once
    var cb = _.callbacks("once");
    cb.add(function a(params) {
        console.log("a");
        return false;
    }, function b(params) {
        console.log("b");
    });
    cb.fire(); // ab
    cb.fire(); 
    

    上面的代码实现了当设置参数为once时,fire调用多次,只会执行1次。

    参数memory功能

    (function (root) {
        var optionsCache = {};
        var _ = {
            callbacks: function (options) {
                // 判断传入的参数类型是否为字符串
                // 存储在optionsCache对象里,如果该对象已经存在这个属性直接使用,如果不存在则创建这个属性值为true
                options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};
    
                var fnList = [];
                var index, length, isFire, memory, start;
                var fire = function (data) {
                    // 如果设置"memory",则记录当前传入的参数
                    memory = options["memory"] && data;
                    index = start || 0;
                    start = 0
                    length = fnList.length;
                    for (; index < length; index++) {
                        // 判断函数执行结果是否为false并且设置了stopOnFalse
                        // data[0]:执行上下文
                        // data[1]:执行时传入的参数
                        if (fnList[index].apply(data[0], data[1]) === false && options['stopOnFalse']) {
                            break;
                        }
                    }
                    isFire = true;
                }
                var self = {
                    add: function () {
                        // 将传入的参数转成数组
                        var argArr = Array.prototype.slice.call(arguments);
                        argArr.forEach((fn) => {
                            // 判断传入的参数是否为function
                            if (toString.call(fn) === "[object Function]") {
                                fnList.push(fn);
                            }
                        })
    
                        // 如果设置了memory参数,并且参数存在,则调用
                        if (memory) {
                            // start为次一次执行时的顺序
                            start = fnList.length - 1;
                            fire(memory)
                        }
                    },
                    fireWith: function (context, arguments) {
                        var args = [context, arguments];
                        if (!(options["once"] && isFire)) {
                            fire(args);
                        }
                    },
                    fire: function () {
                        self.fireWith(this, arguments);
                    }
                }
                return self;
            }
        }
        root._ = _;
        var createOptions = function (options) {
            var object = {};
            // 如果同时指定两种类型,传入的参数为“once memory”,需拆解成单独的字符串
            options.split(/\s+/).forEach(value => {
                object[value] = optionsCache[value] = true;
            });
            return object;
        }
    })(this);
    
    
    var cb = _.callbacks("memory");
    cb.add(function a(params) {
        console.log("a");
        return false;
    });
    cb.add(function c(params) {
        console.log("c");
    }, function d(params) {
        console.log("d");
    })
    cb.fire(); // a c d b
    cb.add(function b(params) {
        console.log("b");
    })
    

    上面代码实现了memory的功能。

    相关文章

      网友评论

        本文标题:D005+技术|jQuery-$.Callbacks()实现原理

        本文链接:https://www.haomeiwen.com/subject/sbrcuctx.html