zoukankan      html  css  js  c++  java
  • [笔记]Backbone.js:Hacker's Guide

    [笔记]Backbone.js:Hacker's Guide

    [笔记]Backbone.js:Hacker's Guide

    1 Setup , Events , Models

     

    1.2 命名空间

    很流行一种方式,Backbone.js实现包装在一个立刻执行的匿名函数里

    (function(){
       // Backbone.js
     }).call(this);
    

    这个匿名函数里: Backbone 命名空间产生了 使用noConfilict()方法,支持多个版本的Backbone

    var root = this;
    var previousBackbone = root.Backbone;
    
    Backbone.noConflict = function() {
      root.Backbone = previousBackbone;
      return this;
    };   
    

    在写应用时,如果你确认页面上还有其它版本Backbone,为避免命名空间冲突,这样使用

    var Backbone19 = Backbone.noConflict();
    // Backbone19 refers to the most recently loaded version,
    // and `window.Backbone` will be restored to the previously
    // loaded version   
    

    Backbone19指向较新的版本,旧版本通过window.Backbone调用用,不受影响 支持CommonJS模块规范,便于用在Node项目中

    var Backbone;
    if (typeof exports !== 'undefined') {
      Backbone = exports;
    } else {
      Backbone = root.Backbone = {};
    }   
    

    Underscrore.js也做了上面类似的运行环境检测

    1.3 服务器支持

    Backbone通过配置支持HTTP扩展方法,put get post delete 另一项配置是服务器是否支持JSON的MIME

    Backbone.emulateHTTP = false;
    Backbone.emulateJSON = false;
    

    内部方法Backbone.sync,会用到上面的配置。 Backbone使用jQuery ajax的API。所有模型的save fetched deleted(destroyed)都会调用sync方法

    项目中没有使用jQuery怎么办?可以在全局覆写sync的逻辑 Backbone的文档中有说明:

    The sync function may be overriden globally as Backbone.sync, or at a finer-grained level, by adding a sync function to a Backbone collection or to an individual model.

    没有特别的插件来完成数据持久化,一个灵活简便的方式就是重写Backbone.sync

    Backbone.sync = function(method, model, options) {
    };   
    

    method的可能值是通过methodMap指定:

    var methodMap = {
      'create': 'POST',
      'update': 'PUT',
      'delete': 'DELETE',
      'read':   'GET'
    };  
    

    1.4 事件

    Backbone内置事件支持。其实就是一个拥有下列方法的object

    on: function(events, callback, context) , aliased to bind
    off: function(events, callback, context) {, aliased to unbind
    trigger: function(events) {
    

    这些方法都返回this,也就是说支持链式调用

    源码中的注释推荐使用Underscore.js的extend方法来给已有的object添加事件支持

    //     var object = {};
    //     _.extend(object, Backbone.Events);
    //     object.on('expand', function(){ alert('expanded'); });
    //     object.trigger('expand');   
    

    extend不会覆盖已有的方法,而是采用追加的策略。

    1.5 模型

    说到模型,就有点复杂了。模型构造时,生成供内部使用的属性,比如属性是否变化,是否保存。Underscore.js把Backbone.Events的方法复制到Model上面.Model就具有事件的API了。

    Model的set方法支持单属性和多属性设置

    // Handle both `"key", value` and `{key: value}` -style arguments.
    if (_.isObject(key) || key == null) {
      attrs = key;
      options = value;
    } else {
      attrs = {};
      attrs[key] = value;
    }   
    

    save方法也用了类似的技巧 注意作者的一个技巧

    options || (options = {});
    

    达到同样目的还有一种写法

    options = options || {};
    

    哪种写法更好? set方法会触发验证操作,验证失败,set也会失败

    if (!this._validate(attrs, options)) return false;
    

    接下来是each所有的属性,如果属性改变了,将这个属性记录下来。each完毕,触发所有改变属性的change事件 监听特定的事件,事件触发后改变UI。比如blogPost这个模型的title属性改变后,执行更新UI的操作

    blogPost.on('change:title', function() {
      // Update the HTML for the page title
    });
    
    blogPost.set('title', 'All Work and No Play Makes Blank a Blank Blank');  
    

    unset clear fetch也会触发change事件。有时候为了避免触发change事件,可以配置silent选项。源码中通过重用set方法来实现的

    // Clear all attributes on the model, firing `"change"` unless you choose
    // to silence it.
    clear: function(options) {
      options = _.extend({}, options, {unset: true});
      return this.set(_.clone(this.attributes), options);
    },
    

    fetch方法会触发sync,从服务器端取数据的操作。 save方法会存储经过验证属性后的模型。存储之前会有set操作

    if (options.wait) {
      if (!this._validate(attrs, options)) return false;
      current = _.clone(this.attributes);
    }
    
    // Regular saves `set` attributes before persisting to the server.
    var silentOptions = _.extend({}, options, {silent: true});
    if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
      return false;
    }
    
    // Do not persist invalid models.
    if (!attrs && !this.isValid()) return false;  
    

    sync持久化数据。isNew用于判断是新建或者更新model。isNew根据id是否存在决定。如果数据存储这块有不同,可以覆写sync逻辑。需要注意,Backbone内部通过this.id引用id,但是不会属性判断和设置的时候是不起作用的。 parse方法在数据获取和保存后会被调用。 数据并不总是以JSON方法存在的,这里有使用XML格式的例子【墙外】

    1.6 总结

    浏览了下Backbone内部代码,总结下:

    1.通过重写Backbone.sync可以支持任意类型的数据持久化

    2.对model的操作触发事件

    3.model的change事件用于驱动UI更新

    4.重用Backbone的模型、事件和Underscore的方法,能帮组组织结构良好的项目代码

    Backbone不支持插件,大概是因为从Ruby应用中抽离出来的,Backbone本身默认使用的是RESTful JSON API。但是,Backbone的设计时很开放的,可以重写Backbone.sync方法来达到支持其它风格的HTTP服务。 Backbone重度依赖Underscore.js。Underscore.js的作者也是Backbone的作者。

    2 Constructor, Inheritance, Collections, Chainable API

     

    2.1 原文

    see

    2.2 Constructor

    Backbone.Collection 是构造函数,接收一个model array 以及一个可选参数 注意void 0的使用

    if (options.comparator !== void 0) this.comparator = options.comparator;
    /*
      void 0 ==> undefined
    */
    

    还可以这样用?void操作符使用

    >The void operator evaluates the given expression and then returns undefined

    >void操作符计算给定的表达是,然后返回undefined

    void 0是获取undefined的一种理想方法 因为ECMAScript 5之前的版本undefined可能被重写

    constructor调用reset方法,移除存在的模型,添加新的模型。 等价手动清空模型,然后一个一个添加

    2.3 继承和组合Inheritance and Mixins

    Collection也是继承自Backbone.Events。Events既用于Backbone内部,又供外部使用。 Collection有toJSON方法,本质上是调用每个model的toJSON方法。 collections使用Underscore.js的方法,但是并非继承子Underscore。一些方法被添加到了Collection.prototype上,其它的根据需要,重写了。比如,pluck做为模型的方法来使用,sort方法使用boundComparator方法,和原生的Array.prototype.sort有点不同。

    2.4 添加和删除

    Collections基本上就是model 数组,支持事件,包装了Underscore相似的迭代方法。 每次添加操作都会触发add事件,在添加的时候会做验证和去重。添加的model根据id来索引,所有的model事件都绑定到了_onModelEvent,统一做事件分发,进行添删改的操作。

    如果collection需要排序,add方法在处理完所有的models后会调用sort。如果没有传入silent参数,add方法会触发每个model的add事件。

    接下来的remove方法也要做相当多的事情。索引后的id必须删除, _removeReference删除到collection的引用。

    js中的删除操作真的很悲剧,delete关键词只能删除对象属性 delete obj.name; delete obj.sex; 而不能通过delete删除对象本身: delete obj; 除非delete也作为某个对象的属性成员,比如obj属于window: delete window.obj; 作者使用了Array.prototype.splice来删除Collection条目,add和remove方法还维护了一个length属性,使得collection表现起来就跟数组一样,并且支持mixed的Underscore方法。

    现在看下where方法。此方法循环所有的model,比较每个model和传入对象的属性是否相等。由于使用了Underscore的filter方法,实现相当便捷。

    2.5 链式API

    另外一点很nice的地方就是支持Underscore的chain方法。API看起来像下面这样的:

    var collection = new Backbone.Collection([
      { name: 'Tim', age: 5 },
      { name: 'Ida', age: 26 },
      { name: 'Rob', age: 55 }
    ]);
    
    collection.chain()
      .filter(function(item) { return item.get('age') > 10; })
      .map(function(item) { return item.get('name'); })
      .value();
    
    // Will return ['Ida', 'Rob']   
    

    有些Backbone方法可以返回this,因此也是支持链式的

    var collection = new Backbone.Collection();
    
    collection
        .add({ name: 'John', age: 23 })
        .add({ name: 'Harry', age: 33 })
        .add({ name: 'Steve', age: 41 });
    
    collection.pluck('name');
    // ['John', 'Harry', 'Steve']  
    

    2.6 总结

    使用Backbone已经有一阵了,但从没想过Backbone.Collection怎样实现链式调用的。 在调用chain方法后,有时候很难确定一个方法是否是支持链式调用的,比如pluck,因为Backbone的model使用get方法来获取属性,所以你得到的是undefined。

    3 Router, History, Views

     

    3.1 原文

    see

    3.3 Router and History

    Backbone.Router构造器像其他Backbone class一样,用Underscore的extend而来。Routes有route方法,如果路由参数为正则表达式,route会调用routeToRegExp方法。使用的是Underscore的isRegExp方法。 Backbone构建在Underscore基础上,可以方便的直接使用其类型检测方法。比如很多项目中会检查传入的参数是否为数组,你可以直接使用isArray方法。 路由功能很大程度上交给了Backbone.History实现。这两个类一起工作——一旦路由规则初始化,然后Backbone.history.start被调用。Backbone因支持hashURLs而知名,它还支持HTML5 history API History类做了很多兼容性工作,特别是在start方法里面:

    this.options          = _.extend({}, {root: '/'}, this.options, options);
    this._wantsHashChange = this.options.hashChange !== false;
    this._wantsPushState  = !!this.options.pushState;
    this._hasPushState    = !!(this.options.pushState && this.history && this.history.pushState);
    var fragment          = this.getFragment();
    var docMode           = document.documentMode;
    var oldIE             = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
    
    if (oldIE && this._wantsHashChange) {
    this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
    this.navigate(fragment);
    }   
    

    要使用原生的history API,设置pushState:true。否则,使用hash URLs,或则通过一个iframes。 当发生popstate 、onhashchange事件或setInterval检测到iframe发生变化时,会调用checkUrl方法 像其它Backbone方法一样,start也接受silent选项。 防止上面的事件发生时调用loadUrl方法 Backbone.Router通过调用Backbone.history.route添加routes。Router类处理用户提供的回调函数,将其添加到回调函数队列中 。当URL发生变化时,回调函数队列中的函数会被检索,然后调用。因为回调函数经过了Router的包装处理,可以通过调用手动trigger触发

    这种优雅的实现方式使得事件能绑定到任何地方,navigate方法传入trigger:true,改变url并触发相应事件非常容易。

    app.navigate('help/troubleshooting', { trigger: true, replace: true });
    

    调用navigate方法,内部是调用了History中的navigate方法。 history队列中的URLs加入和替换,这样就产生了历史记录。

    3.4 Views

    Backbone.View显得比较简单,用来帮助管理html代码片段和事件。_ensureElement方法保证DOM已经准备好,然后进行jQurey方式的事件委托。 最有意思的部分是delegateEvents,循环出所有的events属性,调用delegateEventSplitter正则表达式,匹配event的属性键值。这就是'click .button.edit':openEditDialog的内部实现。 实现时,在改变view时,非常小心的做了解除事件的处理。到处都是undelegateEvents的处理,富应用一定要注意防止内存泄漏啊!

    3.5 结论

    我自己写Backbone应用的时候,是没有使用router的,非常值得深入的研究下Backbone为跨浏览器支持所做的努力。 咋看起来Backbone.View只是一个简单的实现,但实际上做了不少工作来保证将事件绑定到正确的元素上。

    4 Inheritance, Sync

     

    4.1 原文

    5 Backbone.js : 内部原理总结

     

    5.1 原文

    see

    5.2 利用事件

    Backbone的所有class都继承自Backbone.Events

    Backbone.Model
    Backbone.Collection
    Backbone.Router
    Backbone.History
    Backbone.View   
    
    

    这意味着,使用Backbone来创建应用,事件将是一个核心要素。事件是处理UI操作的标准方式,view中要绑定事件,model和集合中的change事件。 并且,你能添加自定义事件。

    学习Backbone的时候,有必要了解内置的事件。在collection上不正确的绑定reset事件,可能会造成view的频繁渲染。掌握事件让你在使用Backbone时 更加具备生产力。

    5.3 Underscore.js

    Backbone依赖Underscore,记住这点。尤其是在处理数组和数据集合的时候,Underscore干这个尤其在行。熟悉Underscore的方法会帮助你在使用Backbone.Collection时更加高效。

    5.4 Views

    使用$符号很容易,当然也容易陷进去,应当尽量的避免使用它。Backbone缓存了视图的element,因此请使用this.$el。 设计视图的时候,应该遵循单一职责原理

    使用$.html()来渲染视图容器非常直接,但尽量不要这样做,保持view的层级会使代码调试和自动测试更加方便。

    有趣的是,Backbone没有多少与template相关的代码,只有一个 template 方法。在开发时,要使用远程模板代码,我用 RequireJS的文本依赖模块 来加载。生产环境,使用RequireJS提供的build工具来合并代码,减少HTTP请求。这样既能保持开发 时的测试和上线时的加载速度。

    5.5 API风格

    Backbone的API风格非常一致。即使是hisory的API都接受silent选项,silent选项用于阻止一些不需要事件回调函数的触发的情况。

    Backbone的collection拥有Underscore的链式API,非常方便顺手,但是正确的使用要小心咯

    5.6 测试

    到目前为止,我从整体上review了Backbone的代码。值得注意的是,其它技术,比如RequireJS和AMD模块,与Backbone协同良好。RequireJS 能很好的将工程拆分成很细的粒度。

    另外一点,Backbone没有强调测试。很不幸,测试Backbone工程很不方便。Addy Osmani 描述一种方式 用QUnit和SinonJS测试Backbone.js应用

    我总结出了下面这些测试Backbone应用的规则:

    1.测试期间整个应用应该都在运行 2.测试不应该依赖于HTML标签(或者尽量少的依赖) 3.测试不应该要求加载数据

    第二条是针对使用RequireJS加载模板文件,view中使用$()

    7 后记

    这只能算一篇笔记,而不是翻译。为准确的理解原作者的意思,请还是参照原文吧~


    Author: tom

    Date: 2012-08-31 22:35:56 CST

    HTML generated by org-mode 6.33x in emacs 23

  • 相关阅读:
    LeetCode OJ 112. Path Sum
    LeetCode OJ 226. Invert Binary Tree
    LeetCode OJ 100. Same Tree
    LeetCode OJ 104. Maximum Depth of Binary Tree
    LeetCode OJ 111. Minimum Depth of Binary Tree
    LeetCode OJ 110. Balanced Binary Tree
    apache-jmeter-3.1的简单压力测试使用方法(下载和安装)
    JMeter入门教程
    CentOS6(CentOS7)设置静态IP 并且 能够上网
    分享好文:分享我在阿里8年,是如何一步一步走向架构师的
  • 原文地址:https://www.cnblogs.com/wewe/p/2666117.html
Copyright © 2011-2022 走看看