• jQuery 源码细读 -- $.Callbacks


    $.Callbacks 是 jQuery 提供的可以方便地处理各种回调(callback)列表的类,其源代码是闭包的经典实现。

    基本原理就是通过在闭包环境内保存一个 list = [] 数组用于存储回调列表,并用 firing,firingStart,firingLength,firingIndex等标志位来控制闭包的有序执行,下面是最重要的2个内部函数,触发函数 fire 和 添加函数 add。

     1         fire = function (data) {
     2             memory = options.memory && data;
     3             fired = true;
     4             firingIndex = firingStart || 0;
     5             firingStart = 0;
     6             firingLength = list.length;
     7             firing = true;
     8             for (; list && firingIndex < firingLength; firingIndex++) {
     9                 if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
    10                     memory = false; // To prevent further calls using add
    11                     break;
    12                 }
    13             }
    14             firing = false;
    15             if ( list ) {
    16                 if ( stack ) {
    17                     if ( stack.length ) {
    18                         fire( stack.shift() );
    19                     }
    20                 } else if ( memory ) {
    21                     list = [];
    22                 } else {
    23                     self.disable();
    24                 }
    25             }
    26         }
    fire 函数
     1             add: function () {
     2                 if ( list ) {
     3                     // First, we save the current length
     4                     var start = list.length;
     5                     (function add( args ) {
     6                         jQuery.each( args, function( _, arg ) {
     7                             var type = jQuery.type( arg );
     8                             if ( type === "function" ) {
     9                                 if ( !options.unique || !self.has( arg ) ) {
    10                                     list.push( arg );
    11                                 }
    12                             } else if ( arg && arg.length && type !== "string" ) {
    13                                 // Inspect recursively
    14                                 add( arg );
    15                             }
    16                         });
    17                     })( arguments );
    18                     // Do we need to add the callbacks to the
    19                     // current firing batch?
    20 
    21                     if (firing) {
    22                         console.log('firing');
    23                         firingLength = list.length;
    24                     // With memory, if we're not firing then
    25                     // we should call right away
    26                     } else if ( memory ) {
    27                         firingStart = start;
    28                         fire( memory );
    29                     }
    30                 }
    31                 return this;
    32             }
    add 函数

    引起我思考的是遍历回调列表时, firing 标志位的使用,遍历前赋值 firing = true,遍历完赋值 firing = false。在 add 函数内如果检查到 firing === true,则将回调函数加入到还没遍历完的列表末端即可。

    可是问题来了,什么情况下会出现for 循环没执行完的情况下 add 函数被调用呢?即 add 函数执行时 firing 有没可能为 true?

    我在 add 函数碰上 firing === true 时加上一句调试 console.log('firing')

    1. 第一种情况,在某个回调函数内部再嵌套添加另一个回调。代码如下:

    var callBacks = $.Callbacks();
    callBacks.add(function () {
        console.log('callBacks 0');
    
        callBacks.add(function () {
            console.log('callBacks 2');
        });
    });
    
    callBacks.add(function () {
        console.log('callBacks 1');
    });
    
    callBacks.fire();
    //执行结果
    //callBacks 0
    //firing
    //callBacks 1
    //callBacks 2

    还有没其他的可能呢?js 不是单线程的吗?是不是只要在每个回调函数内不再调用 callBacks.add 就不会碰上 firing === true 呢?

    2. 利用浏览器对页面事件的响应处理

    <input id="text1" type="text" />
    <script type="text/javascript">
        var text1 = document.getElementById('text1');
    
        text1.onblur = function () {
            console.log('.onblur() is called');
    
            callBacks.add(function () {
                console.log('callBacks 2');
            });
        };
    
        text1.focus();
    
        var callBacks = $.Callbacks('');
    
        callBacks.add(function () {
            console.log('callBacks 0');
            text1.blur();
            console.log('.blur() is called');
        });
    
        callBacks.add(function () {
            console.log('callBacks 1');
        });
    
        callBacks.fire();
        //IE下执行结果
        //callBacks 0
        //.blur() is called
        //callBacks 1
        //.blur() is called 
    
        //非IE浏览器下执行结果
        //callBacks 0
        //.onblur() is called 
        //firing
        //.blur() is called 
        //callBacks 1
        //callBacks 2
    </script>

    这就是浏览器处理事件流程时带来的 js 执行流混乱。

    在非 IE 下 .blur() 的调用会使当前执行堆栈挂起,转而执行 onblur 事件的回调函数,等 onblur处理完了才回头执行 .blur() 后面的代码。

    IE则会等 .blur() 所在的上下文执行完之后才执行 onblur 事件的回调函数。

    总结就是,浏览器无法保证 JavaScript 的单线程线性执行。详细可以看看这篇文章 

    Is javascript guaranteed to be single-threaded?

    里面还讲到一个有趣的点,不要认为 alert 函数会挂起这个js 执行,window.onresize 事件在这种情况下还是可以被触发的,Linux 很容易, window 下可以通过改变分辨率的方式做到。

    最后,我开始思考另一个问题,脱离了浏览器的 nodeJs 会出现这种问题吗?nodeJs能保证一个函数的执行不因为另一个函数而挂起吗?

  • 相关阅读:
    分别使用vue和react创建一个可伸缩的树
    渲染一颗树(分别使用vue和react创建)
    n皇后问题JS实现(N-Queens)
    中序遍历二叉树(js)
    LeetCode 258 Add Digits
    js二维数组去重
    js 数组中sort方法存在的问题
    原生js实现一个简单轮播效果
    原生js实现一个连连看小游戏(三)-----------点击列表获取索引
    js生成随机不重复数字的几种方法
  • 原文地址:https://www.cnblogs.com/straybird/p/3469212.html
Copyright © 2020-2023  润新知