说明

前段时间略忙,终于找到时间看看backbone代码。

正如知友们说的那样,backbone简单、随性。 代码简单的看一眼,就能知道作者的思路。因为简单,所以随性,可以很自由的和其他类库大搭配使用,不太要求特别的格式。

本文会关注backbone实现的细节,整体框架在博客园的一位朋友已经总结的很好了。链接:点击这里

本文Bakcbone版本:

   // 定义Backbone版本
   Backbone.VERSION = '0.9.2'; 

照例提点问题,有目的的读源码。

  1. 怎么和模板引擎配合使用的。
  2. RESTful JSON 接口从服务器检索到的数据
  3. 通过源码,了解到平时使用时的哪些需要注意的细节。
  4. 当models中值被改变时自动触发一个”change”事件、所有用于展示models数据的views都会侦听到这个事件,然后进行重新渲染。如何实现?
  5. 如何兼容IE6+
  6. view model collection怎么关联的。

整体框架

  
   (function() {
        Backbone.Events        // 自定义事件
        Backbone.Model        // 模型构造函数和原型扩展
        Backbone.Collection    // 集合构造函数和原型扩展
        Backbone.Router        // 路由配置器构造函数和原型扩展
        Backbone.History        // 路由器构造函数和原型扩展
        Backbone.View            // 视图构造函数和原型扩展
        Backbone.sync            // 异步请求工具方法
        var extend = function (protoProps, classProps) { ... } // 自扩展函数
        Backbone.Model.extend = Backbone.Collection.extend =          Backbone.Router.extend = Backbone.View.extend = extend; // 自扩展方法
   }).call(this); 

本文的思路是按照这个框架,提炼每个部分的重要部分,解决上面提出的问题,并注释平时使用时需要注意的细节。

Events

  
  var Events = Backbone.Events = {
        // 该方法类似与DOM Level2中的addEventListener方法
        // events允许指定多个事件名称, 通过空白字符进行分隔(如空格, 制表符等)
        // 当事件名称为"all"时, 在调用trigger方法触发任何事件时, 均会调用"all"事件中绑定的所有回调函数
        on : function(events, callback, context) {
            var calls, event, node, tail, list;
            if(!callback)
                return this;
            // eventSplitter = /\s+/; 把多个事件名组成的字符串分开。
            events = events.split(eventSplitter);             // 所有事件都存在this._callbacks这个数组里面
            calls = this._callbacks || (this._callbacks = {});             while( event = events.shift()) {
                list = calls[event];
                node = list ? list.tail : {};
                node.next = tail = {};
                node.context = context;
                node.callback = callback;
                // 重新组装当前事件的回调列表, 列表中已经加入了本次回调事件
                calls[event] = {
                    tail : tail,
                    next : list ? list.next : node
                };
            }
            // 返回当前对象, 方便进行方法链调用
            return this;
        },
        // - 如果context为空, 则移除所有的callback指定的函数
        // - 如果callback为空, 则移除事件中所有的回调函数
        // - 如果events为空, 但指定了callback或context, 则移除callback或context指定的回调函数(不区分事件名称)
        // - 如果没有传递任何参数, 则移除对象中绑定的所有事件和回调函数
        off : function(events, callback, context) {
            // code...
            return this;
        },
        // 触发已经定义的一个或多个事件, 依次执行绑定的回调函数列表
        trigger : function(events) {
            var event, node, calls, tail, args, all, rest;
            // 当前对象没有绑定任何事件
            if(!( calls = this._callbacks))
                return this;
            // 获取回调函数列表中绑定的"all"事件列表
            all = calls.all;
            // 将需要触发的事件名称, 按照eventSplitter规则解析为一个数组
            events = events.split(eventSplitter);
            // 将trigger从第2个之后的参数, 记录到rest变量, 将依次传递给回调函数
            rest = slice.call(arguments, );             // 循环需要触发的事件列表
            while( event = events.shift()) {
                // 此处的node变量记录了当前事件的所有回调函数列表
                if( node = calls[event]) {
                    tail = node.tail;
                    // node变量的值, 按照事件的绑定顺序, 被依次赋值为绑定的单个回调事件对象
                    // 最后一次绑定的事件next属性, 与tail引用同一个对象, 以此作为是否到达列表末尾的判断依据
                    while(( node = node.next) !== tail) {
                        // 执行所有绑定的事件, 并将调用trigger时的参数传递给回调函数
                        node.callback.apply(node.context || this, rest);
                    }
                }
                if( node = all) {
                    tail = node.tail;
                    // 与调用普通事件的回调函数不同之处在于, all事件会将当前调用的事件名作为第一个参数传递给回调函数
                    args = [event].concat(rest);
                    // 遍历并执行"all"事件中的回调函数列表
                    while(( node = node.next) !== tail) {
                        node.callback.apply(node.context || this, args);
                    }
                }
            }             return this;
        }
    };
    // 绑定事件与释放事件的别名, 也为了同时兼容Backbone以前的版本
    Events.bind = Events.on;
    Events.unbind = Events.off; 

Model

Model 比较常用,很多细节,所以列出了几个重要的函数,注释了一些重要细节。

  
   _.extend(Model.prototype, Events, {
         // code..
        // 设置模型中的数据, 如果key值不存在, 则作为新的属性添加到模型, 如果key值已经存在, 则修改为新的值
        set : function(key, value, options) {
            var attrs, attr, val;             // 参数形式允许key-value对象形式, 或通过key, value两个参数进行单独设置
            // 如果key是一个对象, 则认定为使用对象形式设置, 第二个参数将被视为options参数
            if(_.isObject(key) || key == null) {
                attrs = key;
                options = value;
            } else {
                attrs = {};
                attrs[key] = value;
            }             // options配置项必须是一个对象, 如果没有设置options则默认值为一个空对象
            options || ( options = {});
            if(!attrs)
                return this;
            // 如果被设置的数据对象属于Model类的一个实例, 则将Model对象的attributes数据对象赋给attrs
            // 一般在复制一个Model对象的数据到另一个Model对象时, 会执行该动作
            if( attrs instanceof Model)
                attrs = attrs.attributes;             // 一般在复制一个Model对象的数据到另一个Model对象时, 但仅仅需要复制Model中的数据而不需要复制值时执行该操作
            if(options.unset)
                for(attr in attrs)
                attrs[attr] =
                void ;             // 如果设置了validate() 函数,则需要验证
            if(!this._validate(attrs, options))
                return false;             // 如果设置的id属性名被包含在数据集合中, 则将id覆盖到模型的id属性
            // 这是为了确保在自定义id属性名后, 访问模型的id属性时, 也能正确访问到id
            if(this.idAttribute in attrs)
                this.id = attrs[this.idAttribute];             var changes = options.changes = {};
            // now记录当前模型中的数据对象
            var now = this.attributes;
            // escaped记录当前模型中通过escape缓存过的数据
            var escaped = this._escapedAttributes;
            // prev记录模型中数据被改变之前的值
            var prev = this._previousAttributes || {};             // code..             // 如果没有配置silent参数,则需要触发change函数。
            if(!options.silent)
                this.change(options);
            return this;
        },
        // 从服务器获取默认的模型数据, 获取数据后使用set方法将数据填充到模型, 因此如果获取到的数据与当前模型中的数据不一致, 将会触发change事件
        fetch : function(options) {
            options = options ? _.clone(options) : {};
            var model = this;
            var success = options.success;
            // 当获取数据成功后填充数据并调用自定义成功回调函数
            options.success = function(resp, status, xhr) {
                // 如果填充数据时验证失败, 则不会调用自定义success回调函数
                if(!model.set(model.parse(resp, xhr), options))
                    return false;
                // 调用自定义的success回调函数
                if(success)
                    success(model, resp);
            };
            // 请求发生错误时通过wrapError处理error事件
            options.error = Backbone.wrapError(options.error, model, options);
            // 所有的读取数据(Model, Collection)都是通过sync提供的HTTP方法操作
            return (this.sync || Backbone.sync).call(this, 'read', this, options);
        },
        // 保存模型中的数据到服务器
        save : function(key, value, options) {
            // attrs存储需要保存到服务器的数据对象
            var attrs, current;             // 支持设置单个属性的方式 key: value
            // 支持对象形式的批量设置方式 {key: value}
            if(_.isObject(key) || key == null) {
                // 如果key是一个对象, 则认为是通过对象方式设置
                // 此时第二个参数被认为是options
                attrs = key;
                options = value;
            } else {
                // 如果是通过key: value形式设置单个属性, 则直接设置attrs
                attrs = {};
                attrs[key] = value;
            }
            // 配置对象必须是一个新的对象
            options = options ? _.clone(options) : {};             // 如果在options中设置了wait选项, 则被改变的数据将会被提前验证, 且服务器没有响应新数据(或响应失败)时, 本地数据会被还原为修改前的状态
            // 如果没有设置wait选项, 则无论服务器是否设置成功, 本地数据均会被修改为最新状态
            if(options.wait) {
                // 对需要保存的数据提前进行验证
                if(!this._validate(attrs, options))
                    return false;
                current = _.clone(this.attributes);
            }             var model = this;
            // 在options中可以指定保存数据成功后的自定义回调函数
            var success = options.success;
            options.success = function(resp, status, xhr) {
                var serverAttrs = model.parse(resp, xhr);
                if(options.wait) {
                    delete options.wait;
                    serverAttrs = _.extend(attrs || {}, serverAttrs);
                }
                // 如果调用set方法时验证失败, 则不会调用自定义的success回调函数
                if(!model.set(serverAttrs, options))
                    return false;
                if(success) {
                    // 调用响应成功后自定义的success回调函数
                    success(model, resp);
                } else {
                    // 如果没有指定自定义回调, 则默认触发sync事件
                    model.trigger('sync', model, resp, options);
                }
            };
            // 请求发生错误时通过wrapError处理error事件
            options.error = Backbone.wrapError(options.error, model, options);             var method = this.isNew() ? 'create' : 'update';
            var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
            // 如果设置了options.wait, 则将数据还原为修改前的状态
            // 此时保存的请求还没有得到响应, 因此如果响应失败, 模型中将保持修改前的状态, 如果服务器响应成功, 则会在success中设置模型中的数据为最新状态
            if(options.wait)
                this.set(current, silentOptions);
            return xhr;
        },
        // code..     });

Collection

  var Collection = Backbone.Collection = function(models, options) {
        options || ( options = {});
        if(options.model)
            this.model = options.model;
        // 如果设置了comparator属性, 则集合中的数据将按照comparator方法中的排序算法进行排序(在add方法中会自动调用) 
        // 当然也可以服务器做好了传回来,但是如果前后属于不同团队就不好做了。
        if(options.comparator)
            this.comparator = options.comparator;         // 实例化时重置集合的内部状态(第一次调用时可理解为定义状态)
        this._reset();
        this.initialize.apply(this, arguments);         // 首次调用时设置了silent参数, 因此不会触发"reset"事件
        if(models)
            this.reset(models, {
                silent : true,
                parse : options.parse
            });
    };
    _.extend(Collection.prototype, Events, {
        // 定义集合的模型类, 模型类必须是一个Backbone.Model的子类
        model : Model,
        initialize : function() {
        },
        // 返回一个数组, 包含了集合中每个模型的数据对象
        toJSON : function(options) {
            return this.map(function(model) {
                return model.toJSON(options);
            });
        },
        // 默认会触发"add"事件, 如果在options中设置了silent属性, 可以关闭此次事件触发
        // 传入的models可以是一个或一系列的模型对象(Model类的实例), 如果在集合中设置了model属性, 则允许直接传入数据对象(如 {name: 'test'}), 将自动将数据对象实例化为model指向的模型对象
        add : function(models, options) {
            var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
            options || ( options = {});
            // models必须是一个数组, 如果只传入了一个模型, 则将其转换为数组
            models = _.isArray(models) ? models.slice() : [models];             // 遍历需要添加的模型列表, 遍历过程中, 将执行以下操作:
            // - 将数据对象转化模型对象
            // - 建立模型与集合之间的引用
            // - 记录无效和重复的模型, 并在后面进行过滤
            for( i = , length = models.length; i < length; i++) {                 // 当前模型的cid和id
                cid = model.cid;
                id = model.id;
                // dups数组中记录了无效或重复的模型索引(models数组中的索引), 并在下一步进行过滤删除
                if(cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
                    dups.push(i);
                    continue;
                }
                cids[cid] = ids[id] = model;
            }
            // 从models中删除无效或重复的模型, 保留目前集合中真正需要添加的模型列表
            i = dups.length;
            while(i--) {
                models.splice(dups[i], );
            }             // code ...             // 遍历新增加的模型列表
            for( i = , length = this.models.length; i < length; i++) {
                if(!cids[( model = this.models[i]).cid])
                    continue;
                options.index = i;
                // 触发模型的"add"事件, 因为集合监听了模型的"all"事件, 因此在_onModelEvent方法中, 集合也将触发"add"事件
                // 详细信息可参考Collection.prototype._onModelEvent方法
                model.trigger('add', model, this, options);
            }
            return this;
        },
        // 如果没有设置options.silent参数, 将触发模型的remove事件, 同时将触发集合的remove事件(集合通过_onModelEvent方法监听了模型的所有事件)
        remove : function(models, options) {
            var i, l, index, model;
            options || ( options = {});
            models = _.isArray(models) ? models.slice() : [models];
            // 遍历需要移除的模型列表
            for( i = , l = models.length; i < l; i++) {
                model = this.getByCid(models[i]) || this.get(models[i]);
                if(!model)
                    continue;
                delete this._byId[model.id];
                delete this._byCid[model.cid];
                index = this.indexOf(model);
                this.models.splice(index, );
                this.length--;
                // 如果没有设置silent属性, 则触发模型的remove事件
                if(!options.silent) {
                    options.index = index;
                    model.trigger('remove', model, this, options);
                }
                this._removeReference(model);
            }
            return this;
        },
        push : function(model, options) {
            model = this._prepareModel(model, options);
            this.add(model, options);
            return model;
        },
        // code ..
   });

Router & History

  
  var Router = Backbone.Router = function(options) {
        options || ( options = {});
        if(options.routes)
            this.routes = options.routes;
        this._bindRoutes();
        this.initialize.apply(this, arguments);
    };
    _.extend(Router.prototype, Events, {
        // 将一个路由规则绑定给一个监听事件, 当URL片段匹配该规则时, 会自动调用触发该事件
        route : function(route, name, callback) {
            // 创建history实例, Backbone.history是一个单例对象, 只在第一次创建路由器对象时被实例化
            Backbone.history || (Backbone.history = new History);
            // code ...
            Backbone.history.route(route, _.bind(function(fragment) {
                var args = this._extractParameters(route, fragment);
                // 调用callback路由监听事件, 并将参数传递给监听事件
                callback && callback.apply(this, args);                 this.trigger.apply(this, ['route:' + name].concat(args));
                // 触发history实例中绑定的route事件, 当路由器匹配到任何规则时, 均会触发该事件
                Backbone.history.trigger('route', this, name, args);             }, this));
            return this;
        },
       // code ..
    });     // History一般不会被直接调用, 在第一次实例化Router对象时, 将自动创建一个History的单例(通过Backbone.history访问)
    var History = Backbone.History = function() {
        this.handlers = [];
        // checkUrl方法用于在监听到URL发生变化时检查并调用loadUrl方法
        _.bindAll(this, 'checkUrl');
    };     _.extend(History.prototype, Events, {
        // 当用户使用低版本的IE浏览器(不支持onhashchange事件)时, 通过心跳监听路由状态的变化
        // interval属性设置心跳频率(毫秒), 该频率如果太低可能会导致延迟, 如果太高可能会消耗CPU资源(需要考虑用户使用低端浏览器时的设备配置)
        interval : ,
        // 获取location中Hash字符串(锚点#后的片段)
        getHash : function(windowOverride) {
            // 如果传入了一个window对象, 则从该对象中获取, 否则默认从当前window对象中获取
            var loc = windowOverride ? windowOverride.location : window.location;
            // 将锚点(#)后的字符串提取出来并返回
            var match = loc.href.match(/#(.*)$/);
            return match ? match[] : '';
        },
        // 根据当前设置的路由方式, 处理并返回当前URL中的路由片段
        getFragment : function(fragment, forcePushState) {
            // fragment是通过getHash或从URL中已经提取的待处理路由片段(如 #/id/1288)
            if(fragment == null) {
                if(this._hasPushState || forcePushState) {
                    // 使用了pushState方式进行路由
                    fragment = window.location.pathname;
                    // search记录当前页面后的参数内容
                    var search = window.location.search;
                    // 将路径和参数合并在一起, 作为待处理的路由片段
                    if(search)
                        fragment += search; 
                } else {
                    // 使用了hash方式进行路由
                    // 通过getHash方法获取当前锚点(#)后的字符串作为路由片段
                    fragment = this.getHash();
                }
            }
            if(!fragment.indexOf(this.options.root))
                fragment = fragment.substr(this.options.root.length);
            // 如果URL片段首字母为"#"或"/", 则去除该字符
            return fragment.replace(routeStripper, '');
        },
        // 该方法作为整个路由的调度器, 它将针对不同浏览器监听URL片段的变化, 负责验证并通知到监听函数
        start : function(options) {             // (如果手动设置了options.pushState为true, 且浏览器支持pushState特性, 则会使用pushState方式)
            this._wantsHashChange = this.options.hashChange !== false;
            // _wantsPushState属性记录是否希望使用pushState方式来记录和导航路由器
            // pushState是HTML5中为window.history添加的新特性, 如果没有手动声明options.pushState为true, 则默认将使用hash方式
            this._wantsPushState = !!this.options.pushState;             // _hasPushState属性记录浏览器是否支持pushState特性
            this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);             var fragment = this.getFragment();
            // documentMode是IE浏览器的独有属性, 用于标识当前浏览器使用的渲染模式
            var docMode = document.documentMode;
            var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= ));             if(oldIE) {
                // 如果用户使用低版本的IE浏览器, 不支持popstate和onhashchange事件
                // 向DOM中插入一个隐藏的iframe, 并通过改变和心跳监听该iframe的URL实现路由
                this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[].contentWindow;
                this.navigate(fragment);
            }             // 开始监听路由状态变化
            if(this._hasPushState) {
                // 如果使用了pushState方式路由, 且浏览器支持该特性, 则将popstate事件监听到checkUrl方法
                $(window).bind('popstate', this.checkUrl);
            } else if(this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
                // 如果使用Hash方式进行路由, 且浏览器支持onhashchange事件, 则将hashchange事件监听到checkUrl方法
                $(window).bind('hashchange', this.checkUrl);
            } else if(this._wantsHashChange) {
                // 对于低版本的浏览器, 通过setInterval方法心跳监听checkUrl方法, interval属性标识心跳频率
                this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
            }
            // code ..
        },
        // 停止history对路由的监控, 并将状态恢复为未监听状态
        stop : function() {
            // 解除对浏览器路由的onpopstate和onhashchange事件的监听
            $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
            // 停止对于低版本的IE浏览器的心跳监控
            clearInterval(this._checkUrlInterval);
            // 恢复started状态, 便于下次重新调用start方法
            History.started = false;
        },
        // 该方法在onpopstate和onhashchange事件被触发后自动调用, 或者在低版本的IE浏览器中由setInterval心跳定时调用
        checkUrl : function(e) {
            // 获取当前的URL片段
            var current = this.getFragment();
            // 对低版本的IE浏览器, 将从iframe中获取最新的URL片段并赋给current变量
            if(current == this.fragment && this.iframe)
                current = this.getFragment(this.getHash(this.iframe));
            // 如果当前URL与上一次的状态没有发生任何变化, 则停止执行
            if(current == this.fragment)
                return false;             // 执行到这里, URL已经发生改变, 调用navigate方法将URL设置为当前URL
            if(this.iframe)
                this.navigate(current);
            // 调用loadUrl方法, 检查匹配的规则, 并执行规则绑定的方法
            this.loadUrl() || this.loadUrl(this.getHash());
        },
        // code ..     });

View

  
  // Backbone.View 视图相关
    var View = Backbone.View = function(options) {
        // 为每一个视图对象创建一个唯一标识, 前缀为"view"
        this.cid = _.uniqueId('view');
        this._configure(options || {});
        this._ensureElement();
        this.initialize.apply(this, arguments);
        this.delegateEvents();
    };     // viewOptions列表记录一些列属性名, 在构造视图对象时, 如果传递的配置项中包含这些名称, 则将属性复制到对象本身
    var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
    _.extend(View.prototype, Events, {
        // 如果在创建视图对象时, 没有设置指定的el元素, 则会通过make方法创建一个元素, tagName为创建元素的默认标签
        tagName : 'div',
        // code ..
        // 移除当前视图的$el元素
        remove : function() {
            // 通过调用jQuery或Zepto的remove方法, 因此在第三方库中会同时移除该元素绑定的所有事件和数据
            this.$el.remove();
            return this;
        },
        // 该方法用于在内部创建this.el时自动调用
        make : function(tagName, attributes, content) {
            var el = document.createElement(tagName);
            if(attributes)
                $(el).attr(attributes);
            if(content)
                $(el).html(content);
            return el;
        },
        // 为视图对象设置标准的$el及el属性, 该方法在对象创建时被自动调用
        setElement : function(element, delegate) {
            // this.$el 存放Jquery或其他库的示例对象
            this.$el = ( element instanceof $) ? element : $(element);
            // this.el存放标准的DOM对象
            this.el = this.$el[];
            // code ...
            return this;
        },
        // 为视图元素绑定事件
        // events参数配置了需要绑定事件的集合, 格式如('事件名称 元素选择表达式' : '事件方法名称/或事件函数'):
        // {
        //     'click #title': 'edit',
        //     'click .save': 'save'
        //     'click span': function() {}
        // }
        // 该方法在视图对象初始化时会被自动调用, 并将对象中的events属性作为events参数(事件集合)
        delegateEvents : function(events) {
            if(!(events || ( events = getValue(this, 'events'))))
                return;
            // 取消当前已经绑定过的events事件
            this.undelegateEvents();
            for(var key in events) {
                // code ...                
                // 解析事件表达式(key), 从表达式中解析出事件的名字和需要操作的元素
                // 例如 'click #title'将被解析为 'click' 和 '#title' 两部分, 均存放在match数组中
                var match = key.match(delegateEventSplitter);
                // eventName为解析后的事件名称
                // selector为解析后的事件元素选择器表达式
                var eventName = match[], selector = match[];                 method = _.bind(method, this);
                // 设置事件名称, 在事件名称后追加标识, 用于传递给jQuery或Zepto的事件绑定方法
                eventName += '.delegateEvents' + this.cid;
                if(selector === '') {
                    this.$el.bind(eventName, method);
                } else {
                    this.$el.delegate(selector, eventName, method);
                }
            }
        },         // 在实例化视图对象时设置初始配置
        // 将传递的配置覆盖到对象的options中
        // 将配置中与viewOptions列表相同的配置复制到对象本身, 作为对象的属性
        _configure : function(options) {
            // 如果对象本身设置了默认配置, 则使用传递的配置进行合并
            if(this.options)
                options = _.extend({}, this.options, options);
            // 遍历viewOptions列表
            for(var i = , l = viewOptions.length; i < l; i++) {
                // attr依次为viewOptions中的属性名
                var attr = viewOptions[i];
                // 将options配置中与viewOptions相同的配置复制到对象本身, 作为对象的属性
                if(options[attr])
                    this[attr] = options[attr];
            }
            this.options = options;
        },
        _ensureElement : function() {             if(!this.el) {
                // 如果没有设置el属性, 则创建默认元素
                var attrs = getValue(this, 'attributes') || {};
                if(this.id)
                    attrs.id = this.id;
                if(this.className)
                    attrs['class'] = this.className;
                // 通过make方法创建元素, 并调用setElement方法将元素设置为视图所使用的标准元素
                this.setElement(this.make(this.tagName, attrs), false);
            } else {
                // 如果设置了el属性, 则直接调用setElement方法将el元素设置为视图的标准元素
                this.setElement(this.el, false);
            }
        }
   });

Backbone.sync

var methodMap = {
        'create' : 'POST',
        'update' : 'PUT',
        'delete' : 'DELETE',
        'read' : 'GET'
    };
    // Async用于在Backbone中操作数据时, 向服务器发送请求同步数据状态, 以建立与服务器之间的连接
    // sync发送默认通过第三方库(jQuery, Zepto等) $.ajax方法发送请求, 因此如果要调用状态同步相关的方法, 需要第三方库支持
    // Model Collection save 或者fetch都用这个这个类。
    Backbone.sync = function(method, model, options) {
        // 根据CRUD方法名定义与服务器交互的方法(POST, GET, PUT, DELETE)
        var type = methodMap[method];         // params将作为请求参数对象传递给第三方库的$.ajax方法
        var params = {
            // 请求类型
            type : type,
            // 数据格式默认为json
            dataType : 'json'
        };         // 如果在发送请求时没有在options中设置url地址, 将会通过模型对象的url属性或方法来获取url
        if(!options.url) {
            params.url = getValue(model, 'url') || urlError();
        }         if(!options.data && model && (method == 'create' || method == 'update')) {
            params.contentType = 'application/json';
            params.data = JSON.stringify(model.toJSON());
        }
        if(Backbone.emulateHTTP) {
            // 如果操作类型为PUT或DELETE
            if(type === 'PUT' || type === 'DELETE') {
                // 将操作名称存放到_method参数发送到服务器
                if(Backbone.emulateJSON)
                    params.data._method = type;
                // 实际以POST方式进行提交, 并发送X-HTTP-Method-Override头信息
                params.type = 'POST';
                params.beforeSend = function(xhr) {
                    xhr.setRequestHeader('X-HTTP-Method-Override', type);
                };
            }
        }
        // 通过第三方库的$.ajax方法向服务器发送请求同步数据状态
        return $.ajax(_.extend(params, options));     };

使用

  
  var extend = function(protoProps, classProps) {
        // child存储已经实现继承自当前类的子类(Function)
        // protoProps设置子类原型链中的属性
        // classProps设置子类的静态属性
        var child = inherits(this, protoProps, classProps);
        // 将extend函数添加到子类, 因此调用子类的extend方法便可实现对子类的继承
        child.extend = this.extend;
        // 返回实现继承的子类
        return child;
    };
    // 为Model, Collection, Router和View类实现继承机制 每次使用只需要 Backbone.View.extend({...});
   Model.extend = Collection.extend = Router.extend = View.extend = extend; 

说明

本次分析基本上对翻译源码注释,中间省略了一些个人认为对理解代码实现和平时应用关系不大的代码。

BackBone 源码解读及思考的更多相关文章

  1. RequireJs 源码解读及思考

    写在前面: 最近做的一个项目,用的require和backbone,对两者的使用已经很熟悉了,但是一直都有好奇他们怎么实现的,一直寻思着读读源码.现在项目结束,终于有机会好好研究一下. 本文重要解读r ...

  2. Backbone源码解读(一)事件模块

    Backbone源码浅读: 前言: Backbone是早起的js前端MV*框架之一,是一个依赖于underscore和jquery的轻量级框架,虽然underscore中基于字符串拼接的模板引擎相比如 ...

  3. 温故而知新 Volley源码解读与思考

    相比新的网络请求框架Volley真的很落后,一无是处吗,要知道Volley是由google官方推出的,虽然推出的时间很久了,但是其中依然有值得学习的地方.  从命名我们就能看出一些端倪,volley中 ...

  4. Spark Streaming源码解读之流数据不断接收和全生命周期彻底研究和思考

    本节的主要内容: 一.数据接受架构和设计模式 二.接受数据的源码解读 Spark Streaming不断持续的接收数据,具有Receiver的Spark 应用程序的考虑. Receiver和Drive ...

  5. 15、Spark Streaming源码解读之No Receivers彻底思考

    在前几期文章里讲了带Receiver的Spark Streaming 应用的相关源码解读,但是现在开发Spark Streaming的应用越来越多的采用No Receivers(Direct Appr ...

  6. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  7. underscore 源码解读之 bind 方法的实现

    自从进入七月以来,我的 underscore 源码解读系列 更新缓慢,再这样下去,今年更完的目标似乎要落空,赶紧写一篇压压惊. 前文 跟大家简单介绍了下 ES5 中的 bind 方法以及使用场景(没读 ...

  8. 线程本地变量ThreadLocal源码解读

      一.ThreadLocal基础知识 原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板 ...

  9. SDWebImage源码解读之SDWebImageDownloader

    SDWebImage源码解读之SDWebImageDownloader 第八篇 前言 SDWebImageDownloader这个类非常简单,作者的设计思路也很清晰,但是我想在这说点题外话. 如果有人 ...

随机推荐

  1. Django——Session源码分析

    首先我们导入django.contrib.sessions.middleware这个中间件,查看里面的Session源码 from django.contrib.sessions.middleware ...

  2. python之路:进击的小白

    1.hello world print("hello world") 2.变量定义的规则 变量名只能是 字母.数字或下划线的任意组合 变量名的第一个字符不能是数字 以下关键字不能声 ...

  3. 常见Web源码泄露总结

    来自:http://www.hacksec.cn/Penetration-test/474.html 摘要 背景 本文主要是记录一下常见的源码泄漏问题,这些经常在web渗透测试以及CTF中出现. .h ...

  4. CKEditor & CKFinder集成

    CKEditor集成 CKEditor(原名FckEditor): 著名的HTML编辑器(可在线编辑HTML) 配置: ①将CKEditor中的(adapters images lang plugin ...

  5. Linux权限管理 chattr命令、lsattr命令、sudo命令

    chattr命令 chattr命令用来修改文件系统的权限属性 chatrr 只有 root 用户可以使用,用来修改文件系统的权限属性,建立凌驾于 rwx 基础权限之上的授权. chatrr 命令格式如 ...

  6. 【HackerRank】Gem Stones

    Gem Stones John has discovered various rocks. Each rock is composed of various elements, and each el ...

  7. latin-1

    Latin1是ISO-8859-1的别名,有些环境下写作Latin-1.ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII ...

  8. Python的return self和return一个新的对象区别

    目的:设计一个有理数相加.如3/5 + 7/15 = 80/75 return self 输入: class Rational0: def __init__(self, num, den=1): se ...

  9. Docker 三剑客

    Docker三剑客: Docker-Machine Docker Machine is a tool that lets you install Docker Engine on virtual ho ...

  10. mongodb index 的background 及集群的索引建立

    在数据库建立索引时,默认时"foreground" 也就是前台建立索引,但是,当你的数据库数据量很大时,在建立索引的时会读取数据文件,大量的文件读写会阻止其他的操作,此时在建立索引 ...