保存成功
订阅成功
保存失败,请重试
提交成功

jQuery:-$.Callbacks 实现原理

向作者提问
从事前端开发工作10年,有丰富的项目实践经验
查看本场Chat

$.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

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,需要注意:这里的字符串是区分大小写

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”。

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的功能。


本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。

还没有评论
评论
查看更多
微信扫描登录