• 【 js 基础 】【 源码学习 】源码设计 (更新了backbone分析)


    学习源码,除了学习对一些方法的更加聪明的代码实现,同时也要学习源码的设计,把握整体的架构。(推荐对源码有一定熟悉了之后,再看这篇文章)

    目录结构:
    第一部分:zepto 设计分析

    第二部分:underscore 设计分析

    第三部分:backbone 设计分析


    第一部分: zepto 设计分析
    zepto 是一个轻量级的 Javascript 库。相对于 jquery 来说在 size 上更加小,主要是定位于移动设备。它是非常好的学习源码的入门级 javascript 库。这里重点说一下,这个库的设计,而对于详细的源码学习大家可以 star 我的 github 源码学习项目(https://github.com/JiayiLi/source-code-study) 进行关注。

    让我们先看看把所有代码删除只剩下这几行的 zepto :

    1 var Zepto = (function() {
    2     return $
    3 })() 
    4 window.Zepto = Zepto 
    5 window.$ === undefined && (window.$ = Zepto)

    一个匿名自执行函数返回 $ 传递给了 Zepto。然后把 Zepto 挂到 window 上,使其成为全局 window 的一个属性,同时,如果 window.$ 符号没有被占用,那么 $ 会被赋值为 Zepto,故可以全局范围内使用 $。

    然后咱们再来看看赋值给 zepto 的匿名自执行函数的核心代码具体干了什么:

     1 var Zepto = (function() {
     2     //zepto和$是Zepto中的两个命名空间,用来挂载静态函数
     3     var $,zepto = {};
     4 
     5     function Z(dom, selector) {
     6         var i, len = dom ? dom.length: 0
     7         for (i = 0; i < len; i++) this[i] = dom[i] this.length = len this.selector = selector || ''
     8     }
     9 
    10     zepto.Z = function(dom, selector) {
    11         return new Z(dom, selector)
    12     }
    13     zepto.init = function(selector, context) {....
    14         return zepto.Z(dom, selector)
    15     }
    16 
    17     //$()的返回值能够调用$.fn中的方法
    18     $ = function(selector, context) {
    19         return zepto.init(selector, context);
    20     }
    21 
    22     $.fn = {
    23         // 里面有若干个工具函数
    24     };
    25 
    26     zepto.Z.prototype = $.fn;
    27     $.zepto = zepto
    28 
    29     return $ //返回$,赋值给Zepto
    30 })()
    31 
    32 window.Zepto = Zepto
    33 //当$未被占用时就把Zepto赋值给$
    34 window.$ === undefined && (window.$ = Zepto)

    首先定义了 两个变量 zepto 和 $ ,还有一个 构造函数 Z 。
    对于变量 zepto ,给其定义了两个方法 Z 和 init。init 方法中调用了 zepto 的 Z 方法,而在 zepto 的 Z 方法中 则 实例化了 构造函数 Z。
    对于 变量 $ ,则是个函数,内部调用了 zepto 的 init 方法 ,也就是最后返回了 构造函数 Z 的 新实例 。同时也给 $ 上定义了一个属性 fn,fn 是一个 对象,这个对象里面实现了多个工具函数,比如 concat、slice、each、filter 等。

    然后将 刚才定义的 变量 zepto 中的 Z 方法的原型 即构造函数的原型 指向了 $.fn ,这样 调用 $ 函数所返回的 Z 实例 ,就继承了 $.fn 中的方法。

    最后通过 $.zepto = zepto 将内部 API 导出。如果有需要可以调用 zepto 上的内部方法。

    Z 实例 实际上一个 对象数组,即可以模拟数组操作的对象。

    咱们再用个图来梳理一下思路:

    学习并感谢:

    第二部分 underscore 设计分析
    underscore 是一个Javascript 实用库。是函数式编程的典型代表。它是非常好的学习源码的入门级 javascript 库,尤其是学习函数式编程的好材料。这里重点说一下,这个库的设计,而对于详细的源码学习大家可以 star 我的 github 源码学习项目(https://github.com/JiayiLi/source-code-study) 进行查看。

    underscore 的所有代码都包裹在匿名自执行函数中,

    1 (function() {
    2       ...
    3 }.call(this))   //  通过传入this(浏览器环境中其实就是window对象)来改变函数的作用域

    大多数的源码设计都是使用的 匿名自执行函数,这样做的好处:
    1、避免全局污染:库中所定义的变量,方法都封装到了该函数的作用域中。
    2、隐私保护:使用方只能获得 库 想暴露在外面的变量方法,而不能访问 不想暴露的内部变量方法。

    再说设计之前,这里还要说一个知识点:
    underscore 采用的是典型的函数式编程风格,这与面向对象的编程风格并不相同。
    函数式编程(fp)风格,设计的函数方法并不会属于任何一个对象,对象只是 这些函数方法的参数。
    而面向对象的编程(oop)风格则是 设计的函数方法都隶属于一个对象。作为对象的一个属性。
    如果你还没有明白,这里看一下调用方式的不同:
    函数式编程风格:

    1 var arr = [1, 2, 3];
    2 _.map(arr,function(item) {
    3      return item * 2;
    4 });

    arr 这个对象只是 map 方法的一个参数,map 并不属于 arr。

    面向对象风格:

    1 var arr = [1, 2, 3];
    2 arr.map(function(item) {
    3      return item * 2;
    4 });

    map是对象arr的一个方法。

    看出区别了吗?

    回到匿名自执行函数内部,核心代码如下:

      1 (function() {
      2      var root = this;
      3 
      4      // 核心函数
      5      // `_` 其实是一个构造函数
      6      var _ = function(obj) {
      7           // 以下均针对 OOP 形式的调用
      8           // 如果是非 OOP 形式的调用,不会进入该函数内部
      9           // 如果 obj 已经是 `_` 函数的实例,则直接返回 obj
     10           if (obj instanceof _) return obj;
     11 
     12           // 如果不是 `_` 函数的实例
     13           // 则调用 new 运算符,返回实例化的对象
     14           if (! (this instanceof _)) return new _(obj);
     15 
     16           // 将 obj 赋值给 this._wrapped 属性
     17           this._wrapped = obj;
     18      };
     19 
     20      // 将上面定义的 `_` 局部变量赋值给全局对象中的 `_` 属性
     21      // 即客户端中 window._ = _
     22      // 服务端(node)中 exports._ = _
     23      // 同时在服务端向后兼容老的 require() API
     24      // 这样暴露给全局后便可以在全局环境中使用 `_` 变量(方法)
     25      if (typeof exports !== 'undefined') {
     26           if (typeof module !== 'undefined' && module.exports) {
     27                exports = module.exports = _;
     28           }
     29           exports._ = _;
     30      } else {
     31           root._ = _;
     32      }
     33 
     34      // .....  定义工具函数  如 _.each, _.map 等
     35 
     36 
     37      _.mixin = function(obj) {
     38           // 遍历 obj 的 key,将方法挂载到 Underscore 上
     39           // 其实是将方法浅拷贝到 _.prototype 上
     40           _.each(_.functions(obj),
     41           function(name) {
     42                // 直接把方法挂载到 _[name] 上
     43                // 调用类似 _.myFunc([1, 2, 3], ..)
     44                var func = _[name] = obj[name];
     45 
     46                // 浅拷贝
     47                // 将 name 方法挂载到 _ 对象的原型链上,使之能 OOP 调用
     48                _.prototype[name] = function() {
     49                     // 第一个参数
     50                     var args = [this._wrapped];
     51 
     52                     // arguments 为 name 方法需要的其他参数
     53                     push.apply(args, arguments);
     54                     // 执行 func 方法
     55                     // 支持链式操作
     56                     return result(this, func.apply(_, args));
     57                };
     58           });
     59      };
     60 
     61      // Add all of the Underscore functions to the wrapper object.
     62      // 将前面定义的 underscore 方法添加给包装过的对象
     63      // 即添加到 _.prototype 中
     64      // 使 underscore 支持面向对象形式的调用
     65      _.mixin(_);
     66 
     67      // Add all mutator Array functions to the wrapper.
     68      // 将 Array 原型链上有的方法都添加到 underscore 中
     69      _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'],
     70      function(name) {
     71           var method = ArrayProto[name];
     72           _.prototype[name] = function() {
     73                var obj = this._wrapped;
     74                method.apply(obj, arguments);
     75 
     76                if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
     77 
     78                // 支持链式操作
     79                return result(this, obj);
     80           };
     81      });
     82 
     83      // Add all accessor Array functions to the wrapper.
     84      // 添加 concat、join、slice 等数组原生方法给 Underscore
     85      _.each(['concat', 'join', 'slice'],
     86      function(name) {
     87           var method = ArrayProto[name];
     88           _.prototype[name] = function() {
     89                return result(this, method.apply(this._wrapped, arguments));
     90           };
     91      });
     92      // 一个包装过(OOP)并且链式调用的对象
     93      // 用 value 方法获取结果
     94      _.prototype.value = function() {
     95           return this._wrapped;
     96      };
     97 
     98      _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
     99 
    100      _.prototype.toString = function() {
    101           return '' + this._wrapped;
    102      };
    103 
    104 }.call(this))

    将this(浏览器环境中其实就是window对象)传入 匿名自执行 函数,并赋值给 root。
    创建 _ 函数,将其挂到 root 即全局作用域下,故可以全局范围内使用 _ 。
    然后在 _ 上定义了工具函数(函数即对象,可以在对象上添加方法),像 _.each, _.map 等。
    这样就可以全局使用函数式编程风格的方式 调用 _ 上的方法了, _.each, _.map等。

    但同时 underscore 也做了针对面向对象风格的调用方式的兼容。需要通过_()来包裹一下对象,调用例子:

    1 var arr = [1, 2, 3];
    2 _(arr).map(function(item) {
    3      return item * 2;
    4 });

     
    看一下 _ 函数内部: 
    var _ = function(obj) {
         // 以下均针对 OOP 形式的调用
         // 如果是非 OOP 形式的调用,不会进入该函数内部
         // 如果 obj 已经是 `_` 函数的实例,则直接返回 obj
         if (obj instanceof _) return obj;
    
         // 如果不是 `_` 函数的实例
         // 则调用 new 运算符,返回实例化的对象
         if (! (this instanceof _)) return new _(obj);
    
         // 将 obj 赋值给 this._wrapped 属性
         this._wrapped = obj;
    };

    如果你采用的是函数式编程风格 调用的话, 不传 obj ,所以不会进入两个 if 判断而直接执行最后一句 this._wrapped = obj;。
    如果你采用的是面向对象的编程风格 调用的话,如果 obj 已经是 _ 函数的实例,则直接返回 obj,如果不是 _ 函数的实例, 则调用 new 运算符,返回实例化的对象。

    那么 面向对象风格的调用方式,是如何拥有所有定义的方法的呢?
    从 代码中的 _.mixin 函数开始,就是为了兼容 这一种调用。
    定义了一个 _.mixin 方法 ,并在之后立即执行了,传入了 _ 作为参数。

    _.mixin = function(obj) {
        // 遍历 obj 的 key,将方法挂载到 Underscore 上
        // 其实是将方法浅拷贝到 _.prototype 上
        _.each(_.functions(obj),
        function(name) {
            // 直接把方法挂载到 _[name] 上
            // 调用类似 _.myFunc([1, 2, 3], ..)
            var func = _[name] = obj[name];
    
            // 浅拷贝
            // 将 name 方法挂载到 _ 对象的原型链上,使之能 OOP 调用
            _.prototype[name] = function() {
                // 第一个参数
                var args = [this._wrapped];
    
                // arguments 为 name 方法需要的其他参数
                push.apply(args, arguments);
                // 执行 func 方法
                // 支持链式操作
                return result(this, func.apply(_, args));
            };
        });
    };
    
    // Add all of the Underscore functions to the wrapper object.
    // 将前面定义的 underscore 方法添加给包装过的对象
    // 即添加到 _.prototype 中
    // 使 underscore 支持面向对象形式的调用
    _.mixin(_);
    
    // Add all mutator Array functions to the wrapper.
    // 将 Array 原型链上有的方法都添加到 underscore 中
    _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'],
    function(name) {
        var method = ArrayProto[name];
        _.prototype[name] = function() {
            var obj = this._wrapped;
            method.apply(obj, arguments);
    
            if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
    
            // 支持链式操作
            return result(this, obj);
        };
    });
    
    // Add all accessor Array functions to the wrapper.
    // 添加 concat、join、slice 等数组原生方法给 Underscore
    _.each(['concat', 'join', 'slice'],
    function(name) {
        var method = ArrayProto[name];
        _.prototype[name] = function() {
            return result(this, method.apply(this._wrapped, arguments));
        };
    });
    // 一个包装过(OOP)并且链式调用的对象
    // 用 value 方法获取结果
    _.prototype.value = function() {
        return this._wrapped;
    };
    
    _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
    
    _.prototype.toString = function() {
        return '' + this._wrapped;
    };

    _.mixin 函数中将遍历_的属性,如果某个属性的类型是function,就把该函数挂载到 _ 原型链上,这样对于 _ 函数的实例自然就可以调用 _ 原型链上的方法。


    对于函数式编程,其实是另一种编程思想,它相较于大家所熟知的面向对象的编程风格来说 ,应该是各有好处。推荐大家看 underscore源码 和 书 《Javascript函数式编程》 深入了解。


    学习并感谢:
    http://www.jianshu.com/p/e602ce36b6f7
    https://yoyoyohamapi.gitbooks.io/undersercore-analysis/content/base/%E7%BB%93%E6%9E%84.html

     

    第三部分 backbone 设计分析
    backbone 是一个以类 jq 和 underscore 为基础的 mvc 框架。它是非常好的学习 mvc 框架的入门级 javascript 库。这里主要说一下这个框架的设计,而对于细节上的知识点可以 star 我的 github 源码学习项目(https://github.com/JiayiLi/source-code-study) 进行查看。

    这里是删减了许多代码的 backbone,只剩下一个外壳:

     1 (function(factory) {
     2     // 定义全局对象 root 变量,在浏览器环境下为 window,在 服务器 环境下为 global, self 指向window
     3     var root = (typeof self == 'object' && self.self === self && self) || (typeof global == 'object' && global.global === global && global);
     4 
     5     // 支持AMD规范
     6     // 使用define函数定义Backbone模块, 依赖`underscore`, `jquery`, `exports`三个模块.
     7     if (typeof define === 'function' && define.amd) {
     8         define(['underscore', 'jquery', 'exports'],
     9         function(_, $, exports) {
    10             // Export global even in AMD case in case this script is loaded with
    11             // others that may still expect a global Backbone.
    12             root.Backbone = factory(root, exports, _, $);
    13         });
    14 
    15     // 支持 commonJs 规范(NodeJS使用的规范, 主要用于服务器端, 所以jQuery非必须).
    16     // CommonJS规范中, exports是用于导出模块的对象.
    17     } else if (typeof exports !== 'undefined') {
    18         var _ = require('underscore'),
    19             $;
    20         try {
    21             $ = require('jquery');
    22         } catch(e) {}
    23         factory(root, exports, _, $);
    24 
    25     // 以上两种情况都没有,则以最简单的执行函数方式,将函数的返回值作为全局对象Backbone
    26     } else {
    27         root.Backbone = factory(root, {},
    28         root._, (root.jQuery || root.Zepto || root.ender || root.$));
    29     }
    30 
    31 })(function(root, Backbone, _, $) {
    32 
    33     //  ……………
    34 
    35     return Backbone;
    36 });

    可以看出来,整个代码包装在一个匿名自执行函数中,在对匿名自执行函数立刻调用时,传的参数也是一个函数,这个函数返回了已经被定义好所有方法的 backbone,作为 factory。之后就对这个 factory 进行了支持各种模块规范的封装(7-29行)。这种写法是非常常见的对于库或者框架等的封装方式。
     
    对于常见的模块规范,以及模块化加载 大家可以看这篇文章进行学习:http://www.cnblogs.com/lijiayi/p/js_node_module.html
     
    首先定义了 root 变量(3行),在浏览器环境下为 window,在 服务器 环境下为 global,也就是要明确最后要把 backbone 挂在谁的上面。
    然后 进入 if 条件循环判断,来看当前环境支持的是哪种 模块 规范(7-29行)。
    先判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块,(7-13行)
    如果不支持AMD,则判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式,(17-23行)
    如果以上两种都不支持,则直接将函数的返回值挂到全局对象上。(26-29行)
    在挂到全局变量的时候,就调用了 factory ,并向其中传入不同规范所需的参数,分别对应 backbone 函数定义的 root、Backbone、_、$ 参数。(12行、23行、27行)
     
    这种对于AMD 规范和 Commonjs 规范的判断,其实也有一个名字,叫 umd 规范,即 amd 和 commonjs 的综合判断。
     
    现在咱们知道了 为何可以在全局使用 backbone 以及它是如何封装的,咱们再来看看它内部方法的具体实现都用到了些什么?
      1 (function(factory) {
      2     
      3 })(function(root, Backbone, _, $) {
      4     // Backbone.Events
      5     // ---------------
      6     // Backbone 事件部分
      7     // Backbone的 Events 实际上就是一个观察者模式(发布订阅模式)的实现,并且巧妙的是,还可以作为mixin混入到自己写的object中,
      8 
      9     // 初始化Events为一个空对象,js中的对象是按引用传递的。
     10     var Events = Backbone.Events = {};
     11 
     12     // Bind an event to a `callback` function. Passing `"all"` will bind
     13     // the callback to all events fired.
     14     // 绑定事件。将一个事件绑定到 `callback` 函数上。事件触发时执行回调函数`callback`。
     15     Events.on = function(name, callback, context) {
     16         
     17     };
     18 
     19     // “on”的控制反转版本。
     20     // 让 object 监听 另一个(other)对象上的一个特定事件。跟踪它正在监听的内容,以便以后解除绑定。
     21     Events.listenTo = function(obj, name, callback) {
     22     
     23     };
     24 
     25     // 此函数作用于删除一个或多个回调。
     26     Events.off = function(name, callback, context) {
     27     
     28     };
     29 
     30     // 解除 当前 object 监听的 其他对象上制定事件,或者说是所有当前监听的事件
     31     // 如果 传入被监听对象 obj 就是解除特定对象上的事件,没有就是解除所有事件
     32     Events.stopListening = function(obj, name, callback) {
     33     
     34     };
     35 
     36     // 绑定事件只能触发一次。在第一次调用回调之后,它的监听器将被删除。如果使用空格分隔的语法传递多个事件,则处理程序将针对每个事件触发一次,而不是一次所有事件的组合。
     37     Events.once = function(name, callback, context) {
     38         
     39     };
     40 
     41     // once的反转控制版本
     42     Events.listenToOnce = function(obj, name, callback) {
     43         
     44     };
     45 
     46     // 触发一个或者多个事件,并触发所有的回调函数
     47     Events.trigger = function(name) {
     48         
     49     };
     50 
     51     // 将Events的特性全部extend到Backbone, 即Backbone也可以做Backbone.on/Backbone.trigger这样的操作.
     52     // underscore:_.extend(destination, *sources) 复制source对象中的所有属性覆盖到destination对象上,并且返回 destination 对象. 复制是按顺序的, 所以后面的对象属性会把前面的对象属性覆盖掉(如果有重复).
     53     _.extend(Backbone, Events);
     54 
     55     // 至此,Events部分结束,接下来是Model部分
     56     // Backbone.Model  模型Model绑定键值数据和自定义事件;
     57     // --------------
     58     // 每当一个模型建立,一个 cid 便会被自动创建
     59     // 实际上,Model 函数内的语句顺序也是很重要的,这个不能随便打乱顺序(初始化过程)
     60 
     61     // 创建具有指定属性的新model。 客户端ID(`cid`)自动生成并分配给您。
     62     var Model = Backbone.Model = function(attributes, options) {
     63 
     64         // Model的唯一的id,这和自己传入的id并不一样,虽然我们也要保证id是唯一的
     65         this.cid = _.uniqueId(this.cidPrefix);
     66 
     67         // model 模型元数据都存储在`attributes`变量中.
     68         this.attributes = {};
     69 
     70         // 如果指定`collection`则保存, model 在构造 url 时可能会用到此参数.
     71         if (options.collection) this.collection = options.collection;
     72 
     73         // 如果之后 new 的时候传入的是 JSON,我们必须在 options 选项中声明 parse 为 true
     74         if (options.parse) attrs = this.parse(attrs, options) || {};
     75 
     76         // 存储历史变化记录,用于保存上一次`set`之后改变的数据字段.
     77         this.changed = {};
     78         // 调用initialize初始化方法。这个initialize也是空的,给初始化之后调用
     79         this.initialize.apply(this, arguments);
     80     };
     81 
     82     // 使用extend方法为Model原型定义一系列属性和方法。
     83     _.extend(Model.prototype, Events, {
     84         // preinitialize默认为空函数。您可以使用函数或对象覆盖它。在模型中运行任何实例逻辑之前,preinitialize将运行。
     85         preinitialize: function() {},
     86 
     87         // 默认情况下,Initialize是一个空的函数。用自己的初始化逻辑覆盖它。
     88         initialize: function() {},
     89 
     90         // Get the value of an attribute.
     91         // 从当前model中获取当前属性(attributes)值,比如: note.get("title")
     92         get: function(attr) {
     93             return this.attributes[attr];
     94         },
     95 
     96         // 检查模型中是否存在某个属性。属性值为非 null 或非 undefined 时返回 true。
     97         // 当该属性的值被转换为Boolean类型后值为false, 则认为不存在。如果值为false, null, undefined, 0, NaN, 或空字符串时, 均会被转换为false。
     98         has: function(attr) {
     99             return this.get(attr) != null;
    100         },
    101 
    102         // 向 model 设置一个或多个 hash 属性(attributes)。如果任何一个属性改变了 model 的状态,在不传入 {silent: true} 选项参数的情况下,会触发 "change" 事件,更改特定属性的事件也会触发。 可以绑定事件到某个属性,例如:change:title,及 change:content。
    103         set: function(key, val, options) {
    104 
    105         },
    106 
    107         // 从 model 中删除所有属性, 包括id属性。 如果未设置 silent 选项,会触发 "change" 事件。unset 设置为 true,为删除操作。
    108         clear: function(options) {
    109 
    110         },
    111 
    112         // 标识模型从上次 set 事件发生后是否改变过。 如果传入 att ,当指定属性改变后返回 true。
    113         hasChanged: function(attr) {
    114     
    115         },
    116 
    117         // 如果服务器已经持久化,则在服务器上销毁此模型。如果有模型,则可以从Collection集合中删除该模型。 如果设置了“wait:true”,等待服务器成功响应再删除。
    118         // 如果模型是在客户端新建的, 则直接从客户端删除
    119         // 如果模型数据同时存在服务器, 则同时会删除服务器端的数据
    120         destroy: function(options) {
    121             
    122         }
    123     });
    124 
    125     // 至此,Model部分结束,接下来是Collection部分
    126     // Backbone.Collection 集合Colection是模型的有序或无序集合,带有丰富的可枚举API;
    127     // -------------------
    128     
    129     // 如果 model 更倾向于表示单行数据,那么 Backbone Collection 更类似于完整数据的表,或者表数据的一小片或者一页表格,或者因为一些原因而聚集在一起的多条行数据。Collections 维护其模型的索引,无论是按顺序的还是通过“id”进行查找。
    130     
    131     // 创建一个新的 **Collection**,可能包含了某一种特定的类型的 model。如果指定了“comparator”,则Collection将按照排序顺序维护其模型,当它们被添加和删除。
    132     var Collection = Backbone.Collection = function(models, options) {
    133         // 配置对象
    134         options || (options = {});
    135         // 提前初始化
    136         this.preinitialize.apply(this, arguments);
    137 
    138         // 实例化时重置集合的内部状态(第一次调用时可理解为定义状态)
    139         this._reset();
    140     };
    141 
    142     // 通过extend方法定义集合类原型方法
    143     _.extend(Collection.prototype, Events, {
    144 
    145         // preinitialize默认为空函数。你可以用函数或者对象重写它。preinitialize将在Collection中运行任何实例逻辑之前,先运行。
    146         preinitialize: function() {},
    147 
    148         // initialize 默认也是空函数。你可以用你自己的初始化函数覆盖它。
    149         initialize: function() {},
    150 
    151         // 向集合中增加一个模型(或一个模型数组),触发"add"事件。  
    152         add: function(models, options) {
    153         
    154         },
    155 
    156         // 从集合中删除一个模型(或一个模型数组),会触发 "remove" 事件,
    157         remove: function(models, options) {
    158     
    159         },
    160 
    161         
    162         // 通过“set”更新集合,添加新的模型,删除不再存在的模型,并根据需要合并集合中已存在的模型。 类似于** Model#set **,用于更新集合所包含的数据的核心操作。
    163         set: function(models, options) {
    164         
    165         },
    166 
    167         // 添加一个 model 到 collection 集合的最后。
    168         push: function(model, options) {
    169         
    170         },
    171 
    172         // 删除并且返回集合中最后一个模型。选项和remove相同。
    173         pop: function(options) {
    174         
    175         }
    176         
    177     });
    178 
    179     // Backbone.View  视图相关
    180     // -------------
    181 
    182     // 视图类用于创建与数据低耦合的界面控制对象, 通过将视图的渲染方法绑定到数据模型的change事件, 当数据发生变化时会通知视图进行渲染
    183     // 视图对象中的el用于存储当前视图所需要操作的DOM最父层元素, 这主要是为了提高元素的查找和操作效率, 其优点包括:
    184     // - 查找或操作元素时, 将操作的范围限定在el元素内, 不需要再整个文档树中搜索
    185     // - 在为元素绑定事件时, 可以方便地将事件绑定到el元素(默认也会绑定到el元素)或者是其子元素
    186     // - 在设计模式中, 将一个视图相关的元素, 事件, 和逻辑限定在该视图的范围中, 降低视图与视图间的耦合(至少在逻辑上是这样)
    187     var View = Backbone.View = function(options) {
    188         // 为每一个视图对象创建一个唯一标识, 前缀为"view"
    189         this.cid = _.uniqueId('view');
    190         // 提前初始化
    191         this.preinitialize.apply(this, arguments);
    192         // underscore _.pick(object, *keys)方法:返回一个object副本,只过滤出keys(有效的键组成的数组)参数指定的属性值。或者接受一个判断函数,指定挑选哪个key。
    193         _.extend(this, _.pick(options, viewOptions));
    194         // 初始化dom元素和jQuery元素工作,设置或创建视图中的元素
    195         this._ensureElement();
    196         // 调用自定义的初始化方法
    197         this.initialize.apply(this, arguments);
    198     };
    199 
    200     // 设置所有可继承的** Backbone.View **属性和方法。
    201     _.extend(View.prototype, Events, {
    202 
    203         // 默认空函数。可重写。
    204         initialize: function() {},
    205 
    206         // ** render **是您的视图应该覆盖的核心函数,以便使用适当的HTML填充其元素(`this.el`)。这个惯例是** render **总是返回`this`。
    207         render: function() {
    208             return this;
    209         },
    210 
    211         //移除这个View。
    212         //通过将元素从DOM中移除,并删除任何适用的Backbone.Events侦听器来删除此视图。
    213         remove: function() {
    214         
    215         },
    216 
    217         // 如果你想应用一个Backbone视图到不同的DOM元素, 使用setElement, 这也将创造缓存$el引用,视图的委托事件从旧元素移动到新元素上。
    218         // 更改视图的元素(`this.el`属性)并重新委派新元素上的视图事件。
    219         setElement: function(element) {
    220             
    221         }
    222     });
    223 
    224     // Backbone.sync    Backbone 每次向服务器读取或保存模型时都要调用执行的函数。
    225     // -------------
    226     // 覆盖此功能以更改Backbone将models持续到服务器的方式。你需要传递 request 的类型以及有问题的 model。默认情况下,一个 RESTful Ajax 请求会调用 model 的 url() 方法。一些可能的使用场景:
    227     // 1、使用 setTimeout 将快速更新 批量导入到单个请求中。
    228     // 2、发送 XML 形式的 model
    229     // 3、通过WebSockets而不是Ajax来持久化模型。
    230     Backbone.sync = function(method, model, options) {
    231         
    232     };
    233 
    234     // Backbone.Router Backbone的路由部分,这部分被认为是backbone的MVC结构中的被弱化的controller
    235     // ---------------
    236 
    237     var Router = Backbone.Router = function(options) {
    238         options || (options = {});
    239         // 提前初始化
    240         this.preinitialize.apply(this, arguments);
    241         // 如果在options中设置了routes对象(路由规则), 则赋给当前实例的routes属性
    242         // routes属性记录了路由规则与事件方法的绑定关系, 当URL与某一个规则匹配时, 会自动调用关联的事件方法
    243         if (options.routes) this.routes = options.routes;
    244         // 解析和绑定路由规则
    245         this._bindRoutes();
    246         // 调用自定义的初始化方法
    247         this.initialize.apply(this, arguments);
    248     };
    249 
    250     // 向Router类的原型对象中扩展属性和方法
    251     _.extend(Router.prototype, Events, {
    252 
    253         // 默认为空函数,可以重写,在路由器中运行任何实例化逻辑之前,preinitialize将先运行。
    254         preinitialize: function() {},
    255 
    256         // 自定义初始化方法, 在路由器Router实例化后被自动调用
    257         initialize: function() {},
    258 
    259         // 将一个路由规则绑定给一个监听事件, 当URL片段匹配该规则时, 会自动调用触发该事件。
    260         route: function(route, name, callback) {
    261 
    262         },
    263 
    264         // 在route方法内部被调用,  每当路由和其相应的callback匹配时被执行。 覆盖它来执行自定义解析或包装路由。
    265         execute: function(callback, args, name) {
    266             
    267         },
    268 
    269     
    270         // 每当你达到你的应用的一个点时,你想保存为一个URL,  可以调用navigate以更新的URL。 
    271         navigate: function(fragment, options) {
    272         
    273         }
    274     });
    275 
    276     // Backbone.History 路由器管理
    277     // ----------------
    278     // 
    279     // Backbone的history是通过绑定hashchange事件的监听来监听网页url的变化(通过popstate和onhashchange事件进行监听, 对于不支持事件的浏览器通过setInterval心跳监控),从而调用相关函数
    280     // 另外,在不支持hashchange事件的浏览器中,采用轮询的方式
    281     var History = Backbone.History = function() {
    282 
    283         // handlers属性记录了当前所有路由对象中已经设置的规则和监听列表
    284         // 形式如: [{route: route, callback: callback}], route记录了正则表达式规则, callback记录了匹配规则时的监听事件
    285         // 当history对象监听到URL发生变化时, 会自动与handlers中定义的规则进行匹配, 并调用监听事件
    286         this.handlers = [];
    287 
    288         this.checkUrl = _.bind(this.checkUrl, this);
    289     };
    290 
    291     // 向History类的原型对象中添加方法, 这些方法可以通过History的实例调用(即Backbone.history对象)
    292     _.extend(History.prototype, Events, {
    293 
    294         // 如果处于根节点那么this.location.pathname获取到的应该是`/`
    295         // 另外这里用到了getSearch来获取?后面的内容,如果能获取到自然说明并不是在根节点
    296         atRoot: function() {
    297             var path = this.location.pathname.replace(/[^/]$/, '$&/');
    298             return path === this.root && !this.getSearch();
    299         },
    300 
    301         // 防止像%这样的可能是参数的一部分的符号被编码 ????
    302         decodeFragment: function(fragment) {
    303             return decodeURI(fragment.replace(/%25/g, '%2525'));
    304         },
    305 
    306         // 取得?以及其后面的内容
    307         getSearch: function() {
    308             var match = this.location.href.replace(/#.*/, '').match(/?.+/);
    309             return match ? match[0] : '';
    310         },
    311 
    312         // 停止history对路由的监控, 并将状态恢复为未监听状态
    313         // 调用stop方法之后, 可重新调用start方法开始监听, stop方法一般用户在调用start方法之后, 需要重新设置start方法的参数, 或用于单元测试
    314         stop: function() {
    315             
    316         },
    317 
    318         // 检查当前的URL相对上一次的状态是否发生了变化
    319         checkUrl: function(e) {
    320         
    321         },
    322 
    323         // 导航到指定的URL,存储/更新历史记录
    324         navigate: function(fragment, options) {
    325         
    326         },
    327     });
    328 
    329     // Create the default Backbone.history.
    330     Backbone.history = new History;
    331 
    332     return Backbone;
    333 });
    粗略的过一下,可以看出来backbone大部分模块,像 events、model、view 等都采用的是原型继承的方式,使用构造函数来定义一些不同实例自己独有的变量,方法以及调用一些初始化方法,而使用 prototype,也就是原型来定义所有实例都有的方法,变量。除了 最先定义的 events 模块,后面的模块(除了sync模块),在用原型定义通用方法的时候,先调用 extends 方法将 Events 模块上的方法复制到了当前模块,这样模块就享有了events 模块上的所有方法,这样就可以方便不同模块在有需要的时候监听或者调用某些方法。
     
    对于继承,大家可以看我的这一篇文章进行学习: http://www.cnblogs.com/lijiayi/p/jsprototype.html
     
     
    以上。
     
     
     

    文章会持续更新。欢迎大家  star 我的 github 阅读源码项目(https://github.com/JiayiLi/source-code-study),进行关注。

     
     
  • 相关阅读:
    4种排序实践
    redis 应用场景和数据类型
    建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C
    分布式线程安全(redis、zookeeper、数据库)
    [专项]3道改错题
    kafka 业务埋点
    spring boot集成kafka
    kafka本地调试
    C语言 gets()和scanf()函数的区别
    EOF
  • 原文地址:https://www.cnblogs.com/lijiayi/p/sourcecode.html
Copyright © 2020-2023  润新知