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

  • 相关阅读:
    JS 位数不够自动左补0
    oracle 不同表空间的数据迁移
    Vue 学习
    c# 之Web.config
    c# 之泛型
    WritableWorkbook操作Excel
    MIME类型
    Excel 批量出来数据
    Excel的用到的常规的技巧
    得到Xml中 元素的值
  • 原文地址:https://www.cnblogs.com/wewe/p/2666117.html
Copyright © 2011-2022 走看看