Nothing Special   »   [go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.Callbacks原理分析及实现 #20

Open
Geek-James opened this issue Jul 21, 2019 · 0 comments
Open

3.Callbacks原理分析及实现 #20

Geek-James opened this issue Jul 21, 2019 · 0 comments
Labels
jQuery about jQuery

Comments

@Geek-James
Copy link
Owner
Geek-James commented Jul 21, 2019


jQuery 源码解析代码地址: https://github.com/Geek-James/Blog

本篇代码为

my-jQuery 1.0.2.js

my-jQuery 1.0.3.js

建议下载源码然后据文章思路学习,最好自己边思考边多敲几遍。

一、Callbacks基本概念

1.$.callbacks用于管理函数队列。
2.通过add添加处理函数到队列中,通过fire去执行这些函数。
3.$.callbacks是在jQuery内部使用的,如为.ajax,$.Deffed等组件提供基础功能函数。它也可以在类似功能的一些组件中,如自己开发的插件

二、Callbacks API

  • 1.$.callbacks,获取实例
  • 2.add向内部队列添加函数
  • 3.fire 依次找到并执行队列里的函数
var cb = $.callbacks();
cb.add(function(){
console.log('add one');
});
cb.add(function(){
console.log('add two');
});
cb.add(function(){
console.log('add three');
});
cb.fire() 
//依次输出
add one 
add two 
add three

三、Callbacks参数的特定功能

callbacks通过字符串参数的形式,支持四种特定的功能

  • 1.once
    • 函数队列只执行一次
 // once关键字
var cbOne = $.Callbacks('once');
cbOne.add(function(){
    console.log("this is cbOne1");
});
cbOne.add(function(){
    console.log("this is a cbOne2");
});
// 只输出执行一次,后面调用都不生效
cbOne.fire();
cbOne.fire();
执行结果:
this is cbOne1
this is a cbOne2
  • 2.unique
    • 往内部队列添加的函数保持唯一,不能重复添加
 // unique
var cbUnique = $.Callbacks('unique');
function demo(){
    console.log("this is a cbUnique");
}
cbUnique.add(demo,demo);
cbUnique.fire();
// 输出了一次
this is a cbUnique
  • 3.stopOnFalse
    • 内部队列里的函数是依次执行的,当某个函数的返回值是false时,停止继续执行剩下的函数。
// stopOnFalse 关键字
// 不加关键字的情况
var cbDemo = $.Callbacks();
cbDemo.add(function(){
    console.log("this is cbDemo 1");
    return false;
    
},function(){
    console.log("this is cbDemo 2");
})
cbDemo.fire();
输出:
this is cbDemo 1
this is cbDemo 2

// 加关键字的情况
var cbStopOnFalse = $.Callbacks('stopOnFalse');
cbStopOnFalse.add(function(){
    console.log("this is a cbStopOnFalse 1");
    return false;
},function(){
    console.log("this is a cbStopOnFalse 2");
});
cbStopOnFalse.fire(); 
输出:
this is a cbStopOnFalse 1
  • 4.memory
    • 当参数队列fire一次过后,内部会记录当前fire的参数。当下次调用add的时候,会把记录的参数传递给新添加的函数并立即执行这个新添加的函数。
// 参数 memory
// 不加参数的情况
var cbNoMemory = $.Callbacks();
cbNoMemory.add(function(){
    console.log("this is a cbNoMemory 1");
});
cbNoMemory.fire(); 
输出:
this is a cbNoMemory 1
cbNoMemory.add(function (){
    console.log("this is a cbNoMemory 2");
});

// 添加参数的情况
var cbMemory = $.Callbacks('memory');
cbMemory.add(function(){
    console.log("this is a cbMemory 1");
});
cbMemory.fire(); 
输出:
this is a cbMemory 1
this is a cbMemory 2

cbMemory.add(function(){
    console.log("this is a cbMemory 2");
})

四、从事件函数了解Callbacks

1.事件通常与函数配合使用,这样就可以通过发生的事件来驱动函数的执行.

原则:一个事件对应一个事件函数
在一个事件对应多个函数的情况下,后者会覆盖掉前者。

问题: 那么我们能否有一种方案来改变一对一的事件模型呢?

解决方案: 把事件放到一个数组里,然后通过遍历数组依次执行的方式来达到一对多的事件模型。

// 一对多事件模型
function one(){
    console.log("one");
    
};
function two(){
    console.log("two");
    
};
function three(){
    console.log("three");
    
};
function four(){
    console.log("four");
    
};
var clickCallBack = [one,two,three,four];

// 在body中定义一个button <button id="btn">按钮</button>

$("#btn").click(function(){
   var _this = this;
   clickCallBack.forEach(function(fn){
       fn.call(_this);
   })
});
// 输出结果:
one
three
three
four

2.Callbacks 不仅仅是一个数组,可以把它看成一个容器。

五、开始剖析

上面我们已经通过jQuery来调用Callbacks的API并输出了内容,根据两个方法,add(),fire()及四个参数,"once","unique","menory","stopOnfalse",的相关特性我们开始反推实现过程.

首先是add()方法:将穿过来的options先把他们转为真数组,然后将数组遍历出来挑选出类型为"Function"的数据,将数据添加到一个空数组中,等待执行。

仅是代码片段,完整代码下载地址:
源码下载

核心代码片段:

add:function(){
    // Array.prototype.slice.call(arguments 伪数组转真数组
    var args = Array.prototype.slice.call(arguments);
    start = list.length;
    // 遍历args 找出里面的Function
    args.forEach(function(fn){
        // 检索fn是是否是Function
        if (toString.call(fn) === "[object Function]") {
            // unique 不存在 且fn在list中 那么可以把fn添加到队里中
                list.push(fn);
            }
        }
    });

fire()方法:fire其实就是把添加到队列中的方法依次按规则输出执行,需要一个中间件fireWith提供上下文。

核心代码:

var fire = function(data){
    index = 0;
    length = list.length;
    // 遍历循环list
    for(; index < length; index++){
        // 通过遍历查找list[index]的值为false 且options有stopOnfalse这个参数时遍历终止返回
        if (list[index].apply(data[0],data[1]) == false){
            break;
        }
    }
}
// 定义一个上下文绑定函数
fileWith:function(context,arguments){
    var args = [context,arguments];
        fire(args);
}
fire:function(){
    self.fileWith(this,arguments);
},

到此以上代码可以实现 add() 方法和 fire() 方法,其次我们在考虑四种参数的情况,首先我们先考虑 stopOnfalse 的情况.

stopOnfalse这个参数生效的阶段是在调用fire()方法后执行 add() 添加的队列函数中是否有返回false的情况,所以首先我们应该想到在fire()这个方法里做文章.

思路:直接在遍历方法的时候来判定optionss是否有 stopOnfalse 参数如果有立马退出.

核心代码:

 var fire = function(data){
    // memory
    memory = options.memory && data;
    // 为了防止memory再次调用一次定义了starts
    index = 0;
    length = list.length;
    // 遍历循环list
    for(; index < length; index++){
        // 通过遍历查找list[index]的值为false 且options有stopOnfalse这个参数时遍历终止返回
        if (list[index].apply(data[0],data[1]) == false && options.stopOnfalse){
            break;
        }
    }

once 参数生效的情况是,当once存在执行第一次完成后,如果还有fire()方法,那么就直接退出不执行.

思路:首先明白受影响阶段是fire(), 定义一个参数来记录第一次执行fire()的方法,然后在调用执行fire()这个方法判断是否传入有 once参数如果有,那么就不会再去执行fire()方法.

核心代码:

var fire = function(data){
index = 0;
length = list.length;
startAdd = true;// 用来记录fire()方式是否执行 便于"once"方法操作
// 遍历循环list
for(; index < length; index++){
    // 通过遍历查找list[index]的值为false 且options有stopOnfalse这个参数时遍历终止返回
    if (list[index].apply(data[0],data[1]) == false && options.stopOnfalse){
        break;
    }
 }
}
// 定义一个上下文绑定函数
fileWith:function(context,arguments){
    var args = [context,arguments];
    // 非fire做限制调用
    if(!options.once || !startAdd) {
        fire(args);
    }
},

memory这个参数生效的情况是,如果执行 fire() 方法后,还存在 add() 的方法,那么后面的 add() 方法依然有效。

思路: 首先要搞明白memory在哪个阶段会受影响,在add()阶段和fire()阶段都有影响,add()阶段要记录传入的options是否有memory这个参数,其次在执行fire()的阶段,主要是要记录住它的index值。

核心代码:

var fire = function(data){
    // memory
    memory = options.memory && data;
    // 为了防止memory再次调用一次定义了memoryStarts
    index = memoryStarts || 0;
    start = 0;
    length = list.length;
    startAdd = true; // 用来记录fire()方式是否执行 便于"once"方法操作
    // 遍历循环list
    for(; index < length; index++){
        // 通过遍历查找list[index]的值为false 且options有stopOnfalse这个参数时遍历终止返回
        if (list[index].apply(data[0],data[1]) == false && options.stopOnfalse){
            break;
        }
}
}
// memory 
    if (memory) {
        memoryStarts = start;
        fire(memory);
    }

最难的是 unique 这个参数.

unique 这个参数生效的情况是,同一个方法被add()多次,仅执行一次改方法。

思路: unique影响阶段是add()时候,所有我们在这里做拦截操作是最好的,因此我们在add()的时候做判断如果存在 unique 这个参数,那么我们就不让同样的这个方法push到队里中,没有添加到队列,那么我们就不会再次执行这个方法啦.

两种方法:
(1).通过数组的[].indexOf.call()来查看是否存在于数组中,不存在返回-1.

(2).可以用ES6的set进行过滤重复值

我们采用方法一来完成此操作。

核心代码:

// 添加 方法
add:function(){
    // Array.prototype.slice.call(arguments 伪数组转真数组
    var args = Array.prototype.slice.call(arguments);
    start = list.length;
    // 遍历args 找出里面的Function
    args.forEach(function(fn){
        // 检索fn是是否是Function
        if (toString.call(fn) === "[object Function]") {
            // unique 不存在 且fn在list中 那么可以把fn添加到队里中
            // 处理 unique 参数
            if(!options.unique || !self.has(fn,list)) {
                list.push(fn);
            }
        }
    });
has:function(fn,array){
    return arr = jQuery.inArray(fn,array) > -1;
}
jQuery.inArray = function (elem,arr){
    return arr == null?-1:[].indexOf.call(arr,elem);
}

至此,大功告成!! 完成了Callbacks()实现原理剖析,你学会了吗?

其他

jQuery 源码剖析 系列目录地址:https://github.com/Geek-James/Blog

jQuery 源码剖析 系列预计写十篇左右,旨在加深对原生JavaScript 部分知识点的理解和深入,重点讲解 jQuery核心功能函数、选择器、Callback 原理、延时对象原理、事件绑定、jQuery体系结构、委托设计模式、dom操作、动画队列等。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star⭐️,对作者也是一种鼓励。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
jQuery about jQuery
Projects
None yet
Development

No branches or pull requests

1 participant