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

本篇代码为

my-jQuery 1.0.2.js

my-jQuery 1.0.3.js

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

一、Callbacks基本概念

1. . c a l l b a c k s 用 于 管 理 函 数 队 列 。 2. 通 过 a d d 添 加 处 理 函 数 到 队 列 中 , 通 过 f i r e 去 执 行 这 些 函 数 。 3. .callbacks用于管理函数队列。 2.通过add添加处理函数到队列中,通过fire去执行这些函数。 3. .callbacks2.add,fire3..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⭐️,对作者也是一种鼓励。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐