Reading the callback module of zepto source code

Time:2020-5-26

The callbacks module is not a necessary module. Its function is to manage the callback function and provide support for the deffered module, which is also the Ajax modulepromiseThe style provides support, and the Ajax module will be analyzed soon. Before that, first look at the implementation of the callbacks module and the deferred module.

Reading the zepto source series has been put on GitHub. Welcome to star: reading zepto

Source version

The source code of this article is zepto1.2.0

Overall structure

After the code of the callbacks module is simplified, the structure is as follows:

;(function($){
  $.Callbacks = function(options) {
    ...
    Callbacks = {
      ...
    }
    return Callbacks
  }
})(Zepto)

In fact, it is tozeptoObject, added aCallbacksFunction, this is a factory function, which is called to return an object containing a series of methods.

optionsParameter is an object. In the source code, the author has annotated the meaning of each key value.

// Option flags:
  //   - once: Callbacks fired at most one time.
  //   - memory: Remember the most recent context and arguments
  //   - stopOnFalse: Cease iterating over callback list
  //   - unique: Permit adding at most one instance of the same callback
Once: a callback can only be triggered once at most
Memory: write down the latest triggered context and parameter list, and use this context and parameter to execute immediately when adding a new callback
Stoponfalse: if a callback in the queue returns' false ', immediately abort the execution of subsequent callbacks
Unique: the same callback can only be added once

global variable

options = $.extend({}, options)

var memory, // Last fire value (for non-forgettable lists)
    fired,  // Flag to know if list was already fired
    firing, // Flag to know if list is currently firing
    firingStart, // First callback to fire (used internally by add and fireWith)
    firingLength, // End of the loop when firing
    firingIndex, // Index of currently firing callback (modified by remove if needed)
    list = [], // Actual callback list
    stack = !options.once && [], // Stack of fire calls for repeatable lists
  • options: the configuration of the constructor, which defaults to an empty object

  • list: list of callback functions

  • stack: when the list can be triggered repeatedly, it is used to cache the unexecuted task parameters in the triggering process. If the list can only be triggered once,stackForever forfalse

  • memory: in memory mode, the context and parameters of the last trigger will be remembered

  • fired: callback function list has been triggered

  • firing: callback function list is triggering

  • firingStart: start of callback task

  • firingIndex: index of the current callback task

  • firingLength: length of callback task

Basic usage

I usejQueryandZeptoThe time is quite short, I haven’t used it directly beforeCallbacksModule, it is not easy to understand how it works simply by looking at the code. Before analyzing, first look at the simpleAPICall, which may be helpful for understanding.

var callbacks = $.Callbacks({memory: true})
var a = function(a) {
  console.log('a ' + a)
}
var b = function(b) {
  console.log('b ' + b)
}
var c = function(c) {
  console.log('c ' + c)
}
callbacks.add (a) . add (b). Add (c) // added three callbacks to the queue list
callbacks.remove (c) // delete C
callbacks.fire('fire') 
//At this point, 'a fire', 'B fire' is output and 'C fire' is not output`
callbacks.lock()
callbacks.fire ('fire after lock ') // no output until this step
//Continue to add callbacks to the queue. Note that the 'callbacks' parameter is' memory: true`
callbacks.add(function(d) {  
  console.log('after lock')
})
//Output ` after lock`
callbacks.disable()
callbacks.add(function(e) {
  console.log('after disable')
}) 
//No output

The above example is just a simple call with comments. Let’s start to analyzeAPI

Internal approach

fire

fire = function(data) {
  memory = options.memory && data
  fired = true
  firingIndex = firingStart || 0
  firingStart = 0
  firingLength = list.length
  firing = true
  for ( ; list && firingIndex < firingLength ; ++firingIndex ) {
    if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) {
      memory = false
      break
    }
  }
  firing = false
  if (list) {
    if (stack) stack.length && fire(stack.shift())
    else if (memory) list.length = 0
    else Callbacks.disable()
      }
}

CallbacksModule has only one internal methodfire, to triggerlistThis method isCallbacksThe core of the module.

Variable initialization

memory = options.memory && data
fired = true
firingIndex = firingStart || 0
firingStart = 0
firingLength = list.length
firing = true

fireReceive only one parameterdata, this internal methodfireCall usAPIThe received parameters are different. ThisdataIs an array. There are only two items in the array. The first item is the context object and the second item is the parameter array of the callback function.

Ifoptions.memorybytrue, thendata, that is, the context object and parameters are saved.

takelistStatus triggered or notfiredSet totrue

Index value of the current callback taskfiringIndexPoint to the start of the callback taskfiringStartOr the beginning of the callback list.

Starting position of the callback listfiringStartSet to the start of the callback list.

The length of the task to be recalledfiringLengthSet to the length of the callback list.

The start state of the callbackfiringSet totrue

Execute callback

for ( ; list && firingIndex < firingLength ; ++firingIndex ) {
  if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) {
    memory = false
    break
  }
}
firing = false

The whole logic of executing the callback is to traverse the callback list and execute the callback one by one.

The condition of the loop is that the list exists and the index value of the current callback taskfiringIndexIt’s easy to understand that it’s smaller than the callback task. If the current index value exceeds the task’s length, the task cannot be executed.

list[firingIndex].apply(data[0], data[1])It is to find the corresponding task from the callback list, bind the context object, and pass in the corresponding parameters to execute the task.

If the callback returns explicitly after executionfalse, andoptions.stopOnFalseSet totrue, the execution of subsequent tasks is aborted and clearedmemoryCache of.

After the callback task is completed, thefiringSet tofalse, indicating that there are no tasks currently executing.

Detect the unexecuted callback and cleaning work

if (list) {
  if (stack) stack.length && fire(stack.shift())
  else if (memory) list.length = 0
  else Callbacks.disable()
}

After the list task is completed, check firststackWhether there are any executed tasks in, if so, take out the task parameters and callfireFunction execution. We’ll see later,stackThe task of storage ispushInside, useshiftFetch indicates that the order of task execution is first in, first out.

memoryIf yes, the callback list will be clearedlist.length = 0Is a way to empty the list. In the global parameters, you can see that,stackbyfalseThere is only one caseoptions.oncebytrueThis means that the task can only be executed once, so you need to clear the list. andmemorybytrue, indicating that the tasks added later can still be executed, so you must also keeplistContainer for subsequent tasks to be added and executed.

Call directly in other casesCallbacks.disable()Method to disable the addition and execution of all callback tasks.

.add()

add: function() {
  if (list) {
    var start = list.length,
        add = function(args) {
          $.each(args, function(_, arg){
            if (typeof arg === "function") {
              if (!options.unique || !Callbacks.has(arg)) list.push(arg)
                }
            else if (arg && arg.length && typeof arg !== 'string') add(arg)
              })
        }
    add(arguments)
    if (firing) firingLength = list.length
    else if (memory) {
      firingStart = start
      fire(memory)
    }
  }
  return this
},

startIs the length of the original callback list. It is saved for later correction of the starting position of the callback task.

Internal method add

add = function(args) {
  $.each(args, function(_, arg){
    if (typeof arg === "function") {
      if (!options.unique || !Callbacks.has(arg)) list.push(arg)
        }
    else if (arg && arg.length && typeof arg !== 'string') add(arg)
      })
}

addMethod is used to call back the functionpushGo to the callback list. parameterargumentsIs an array or a pseudo array.

use$.eachMethod to traverseargs, get array itemsarg, ifargbyfunctionType, the next judgment is made.

In the next judgment, ifoptions.uniqueNot fortrue, that is to say, duplicate callback functions are allowed, or the callback function does not exist in the original list, the callback function will be stored in the callback list.

IfargIs an array or pseudo array (viaarg.lengthWhether there is judgment and eliminate itstringCall againaddFunction decomposition.

Fix callback task control variable

add(arguments)
if (firing) firingLength = list.length
else if (memory) {
  firingStart = start
  fire(memory)
}

calladdMethod to add a callback function to the list.

If the callback task is in progress, fix the length of the callback taskfiringLengthIs the length of the current task list so that subsequent added callback functions can be executed.

Otherwise, ifmemoryMode, set the start position of the callback task tostartThat is, the next bit of the last bit of the original list, that is, the first one added to the list, then called.fire, to cache context and parametersmemoryAsfireTo execute the newly added callback function immediately.

.remove()

remove: function() {
  if (list) {
    $.each(arguments, function(_, arg){
      var index
      while ((index = $.inArray(arg, list, index)) > -1) {
        list.splice(index, 1)
        // Handle firing indexes
        if (firing) {
          if (index <= firingLength) --firingLength
          if (index <= firingIndex) --firingIndex
            }
      }
    })
  }
  return this
},

Deletes the callback specified in the list.

Delete callback function

useeachTraversal parameter list, ineachThere’s another layer in traversalwhileCycle, the end conditions of the cycle are as follows:

(index = $.inArray(arg, list, index)) > -1

$.inArray()Finally, it returns the index value of the array item in the array. If it is not in the array, it returns-1, so the judgment is to make sure that the callback function exists in the list. about$.inArrayFor the analysis, see the tool function of reading zepto source code.

Then callsplicedeletelistThe array entry of the corresponding index value in thewhileThe loop is to make sure that repeated callbacks in the list are removed.

Fix callback task control variable

if (firing) {
  if (index <= firingLength) --firingLength
  if (index <= firingIndex) --firingIndex
}

If the callback task is in progress, because the length of the callback list has changed, you need to correct the control parameters of the callback task.

Ifindex <= firingLengthIn other words, the callback function reduces the number of callback tasks in the current callback task1

Ifindex <= firingIndex, that is, before executing the callback function, reduce the index value of the executing function1

This prevents the callback function from failing to find the corresponding task execution at the end of execution.

.fireWith

fireWith: function(context, args) {
  if (list && (!fired || stack)) {
    args = args || []
    args = [context, args.slice ? args.slice() : args]
    if (firing) stack.push(args)
    else fire(args)
      }
  return this
},

Triggers the callback function in a way that specifies the context of the callback function.

fireWithReceive two parameters, the first onecontextIs the context object, the secondargsIs a list of parameters.

fireWithThe condition for subsequent execution is that the list exists and the callback list has not been executed orstackThere is (can be an empty array). Pay attention to this, later ondisableMethods andlockThis is a very important judgment condition when the methods are different.

args = args || []
args = [context, args.slice ? args.slice() : args]

FirstargsWhen it does not exist, it is initialized to an array.

Recombine to a new variableargs, the first item of this variable is the context objectcontext, the second is the parameter list, callingargs.sliceCopy the array becausememoryThe last executed context object and parameters will be stored, which should be afraid of external influence on reference changes.

if (firing) stack.push(args)
else fire(args)

If the callback is in a triggered state, the context object and parameters are stored firststackFrom the inner functionfireAfter the callback function is executed, you can learn from thestackLieutenant generalargsTake it out and trigger it againfire

Otherwise, triggerfire, execute the callback function in the callback function list.

addandremoveWe have to judgefiringTo correct the callback task control variable,fireMethods should also be judgedfiring, to determine whether theargsDepositstackMedium, butjavascriptIt’s single threaded, so it should not be triggered at the same timeaddperhapsremoveOr call againfireThe situation.

.fire()

fire: function() {
  return Callbacks.fireWith(this, arguments)
},

fireMethod, most used, but very simple, calledfireWidthMethod, the context object isthis

.has()

has: function(fn) {
  return !!(list && (fn ? $.inArray(fn, list) > -1 : list.length))
},

hasIt has two functions. If there is a parameter passing, it is used to check the incomingfnIt is used to check whether there is a callback function in the callback list if there is no parameter passed.

fn ? $.inArray(fn, list) > -1 : list.length

This ternary expression is preceded by the judgment specifiedfnWhether it exists in the callback function list, later, iflist.lengthgreater than0, the callback list has been saved into the callback function.

.empty()

empty: function() {
  firingLength = list.length = 0
  return this
},

emptyIs used to clear the list of callback functions and tasks in progress, butlistIt still exists. It can alsolistContinue to add callback function in.

.disable()

disable: function() {
  list = stack = memory = undefined
  return this
},

disableIs to disable the callback function, which is to set the callback function list toundefinedAt the same timestackandmemorySet asundefined, calldisableAfter,addremovefirefireWithThe first condition of these methods is thatlistExists.

.disabled()

disabled: function() {
  return !list
},

Whether the callback has been disabled is actually a testlistWhether it exists.

.lock()

lock: function() {
  stack = undefined
  if (!memory) Callbacks.disable()
  return this
},

Locking the callback list is actually forbiddenfireandfireWithImplementation of.

In fact, it isstackSet toundefinedmemoryWhen it does not exist, it is calleddisableMethod to clear the entire list. The effect is equivalent to disabling the callback function.fireandaddMethods can no longer be executed.

The difference between. Lock() and. Disable()

Why?memoryWhen it exists,stackbyundefinedYou canfireandfireWithWhat about banning it? AbovefireWithI mentioned in particular!fired || stackThis judgment condition. staystackbyundefinedWhen,fireWithOn the execution conditions offiredThis condition. If the callback list has been executed,firedbytruefireWithNo more. If the callback list has not been executed,memorybyundefined, will calldisableMethod disable list,fireWithNor can it be implemented.

So,disableandlockThe main difference ismemoryIn mode, after the callback function is triggered,lockYou can also calladdMethod, add a callback function to the callback list, and use thememoryThe context and parameters of trigger the callback function.

.locked()

locked: function() {
  return !stack
},

Whether the callback list is locked.

It’s actually testingstackWhether it exists.

.fired()

fired: function() {
  return !!fired
}

Whether the callback list has been triggered.

After the callback list is triggered oncefiredIt will becometrue, using!!The goal is toundefinedConvert tofalsereturn.

Series of articles

  1. Reading the code structure of zepto source code

  2. Internal method of reading zepto source code

  3. Read the tool function of zepto source code

  4. The magic of reading zepto source code$

  5. Set operation of reading zepto source code

  6. Reading the set element of zepto source code

  7. Read the operation dom of zepto source code

  8. Reading zepto source code style operation

  9. Read the property operation of zepto source code

  10. Read the event module of zepto source code

  11. Read the IE module of zepto source code

reference resources

  • Zepto source code analysis callbacks module

  • Read jQuery 19 (multipurpose callback function list object)

License

Finally, all articles will be sent to WeChat official account simultaneously. Welcome to our attention. Welcome to comment:Reading the callback module of zepto source code

Author: opposite corner