• backbone.Model 源码笔记


    backbone.Model

    backbone的model(模型),用来存储数据,交互数据,数据验证,在view里面可以直接监听model来达到model一改变,就通知视图.

    这个里面的代码是从backbone里面剥离出来,然后一点一点研究和调试出来的,可以单独运行,依赖underscore,jquery或者是zepto  event.js是剥离出来的Backbone.Events

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
        <title>backbone</title>
        <style type="text/css">
            *{padding:0;margin:0;}
            .wrap{width:960px; margin: 100px auto; padding: 20px 0;}
            ul{ list-style: none;}
        </style>
    </head>
    <body>
        <div class="wrap">
            <div id="a1"></div>
            <div id="a2"></div>
            <div id="a3"></div>
        </div>
    <script src="http://files.cnblogs.com/wtcsy/jquery.js"></script> 
    <script src="http://files.cnblogs.com/wtcsy/underscore.js"></script>
    <script src="http://files.cnblogs.com/wtcsy/events.js"></script>
    <script>
    (function(){
      // Backbone.Model
      // --------------
    
      // Backbone **Models** are the basic data object in the framework --
      // frequently representing a row in a table in a database on your server.
      // A discrete chunk of data and a bunch of useful, related methods for
      // performing computations and transformations on that data.
    
      // Create a new model with the specified attributes. A client id (`cid`)
      // is automatically generated and assigned for you.
        var Model = Backbone.Model = function(attributes, options) {
            var attrs = attributes || {};
            options || (options = {});
            //每个molde都有一个cid 唯一的标识
            this.cid = _.uniqueId('c');
            //这个是存放设置值得hash列表
            this.attributes = {};
            //看这个model是属于哪个collection
            if (options.collection) this.collection = options.collection;
            //格式化参数  默认是不做变化的,可以自己扩展parse方法实现
            if (options.parse) attrs = this.parse(attrs, options) || {};
            attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
            this.set(attrs, options);
            // 被改变了的值
            this.changed = {};
            this.initialize.apply(this, arguments);
        };
    
        _.extend(Model.prototype, Backbone.Events, {
            // A hash of attributes whose current and previous value differ.
            //存放 与之前attributes里面改变了的值
            changed: null,
    
            //验证失败后返回的信息
            // The value returned during the last failed validation.
            validationError: null,
    
            // The default name for the JSON `id` attribute is `"id"`. MongoDB and
            // CouchDB users may want to set this to `"_id"`.
            idAttribute: 'id',
    
            // The function that will generate an id for a model given that model's
            // attributes.
            generateId: function (attrs) {
              return attrs[this.idAttribute];
            },
    
            // Initialize is an empty function by default. Override it with your own
            // initialization logic.
            // 实例化一个model的时候总会被调用的方法
            initialize: function(){},
    
            // Return a copy of the model's `attributes` object.
            // 复制model.的attributes的属性
            toJSON: function(options) {
              return _.clone(this.attributes);
            },
    
            // Proxy `Backbone.sync` by default -- but override this if you need
            // custom syncing semantics for *this* particular model.
            sync: function() {
                return Backbone.sync.apply(this, arguments);
            },
    
            // Get the value of an attribute.
            get: function(attr) {
                return this.attributes[attr];
            },
    
            // Get the HTML-escaped value of an attribute.
            escape: function(attr) {
                return _.escape(this.get(attr));
            },
    
            // Remove an attribute from the model, firing `"change"`. `unset` is a noop
            // if the attribute doesn't exist.
            // 删除model上的数据 触发监听 change 和 unset的回调
            unset: function(attr, options) {
                return this.set(attr, void 0, _.extend({}, options, {unset: true}));
            },
    
            // Clear all attributes on the model, firing `"change"`.
            clear: function(options) {
                var attrs = {};
                for (var key in this.attributes) attrs[key] = void 0;
                return this.set(attrs, _.extend({}, options, {unset: true}));
            },
    
            // Determine if the model has changed since the last `"change"` event.
            // If you specify an attribute name, determine if that attribute has changed.
            // 查看某个是属性值  是否被修改了
            hasChanged: function(attr) {
                if (attr == null) return !_.isEmpty(this.changed);
                return _.has(this.changed, attr);
            },                        
    
            // Return an object containing all the attributes that have changed, or
            // false if there are no changed attributes. Useful for determining what
            // parts of a view need to be updated and/or what attributes need to be
            // persisted to the server. Unset attributes will be set to undefined.
            // You can also pass an attributes object to diff against the model,
            // determining if there *would be* a change.
            changedAttributes: function(diff) {
                if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
                var val, changed = false;
                var old = this._changing ? this._previousAttributes : this.attributes;
                for (var attr in diff) {
                    if (_.isEqual(old[attr], (val = diff[attr]))) continue;
                    (changed || (changed = {}))[attr] = val;
                }
                return changed;
            },
    
            // Get the previous value of an attribute, recorded at the time the last
            // `"change"` event was fired.
            //取改变了attribute之前的某个属性值
            previous: function(attr) {
                if (attr == null || !this._previousAttributes) return null;
                return this._previousAttributes[attr];
            },
    
            // Get all of the attributes of the model at the time of the previous
            // `"change"` event.
            //获取改变了attribute之前
            previousAttributes: function() {
                return _.clone(this._previousAttributes);
            },
            
            // Set a hash of model attributes on the object, firing `"change"`. This is
            // the core primitive operation of a model, updating the data and notifying
            // anyone who needs to know about the change in state. The heart of the beast.    
            set: function(key, val, options) {
                var attr, attrs, unset, changes, silent, changing, prev, current;
                if (key == null) return this;
    
                // Handle both `"key", value` and `{key: value}` -style arguments.
                // 根据传参的不同 统一成key : value的形式
                if (typeof key === 'object') {
                    attrs = key;
                    options = val;
                } else {
                    (attrs = {})[key] = val;
                }
    
    
                options || (options = {});
    
                // Run validation.
                // 如果需要验证数据格式, 进行验证, 验证不通过 则返回
                
                if (!this._validate(attrs, options)) return false;
    
                // Extract attributes and options.
                // unset表示删除
                // changes 是存放改变值得数组
                // changing 属性值是否正在改变中 
                unset           = options.unset;
                silent          = options.silent;
                changes         = [];
                changing        = this._changing;
                this._changing  = true;
    
                //如果不是在改变值得进行中 复制this.attributes 到 this._previousAttributes
                if (!changing) {
                    this._previousAttributes = _.clone(this.attributes);
                    this.changed = {};
                }
                current = this.attributes, prev = this._previousAttributes;
    
                // For each `set` attribute, update or delete the current value.
                for (attr in attrs) {
                    val = attrs[attr];
                    // 如果设置的属性的值,和当前的值不一样  放到changes里面去
                    if (!_.isEqual(current[attr], val)) changes.push(attr);
                    // 如果设置的值和之前的值 一样 this.changed删除掉该属性  不一样 添加到this.changed里面去
                    if (!_.isEqual(prev[attr], val)) {
                        this.changed[attr] = val;
                    } else {
                        delete this.changed[attr];
                    }
                    unset ? delete current[attr] : current[attr] = val;
                }
    
                var prevId = this.id;
                this.id = this.generateId(current);
                if (prevId !== this.id) this.trigger('change-id', this, prevId, options);
    
                // Trigger all relevant attribute changes.
                // 触发改变了属性值的相关的回调事件
                if (!silent) {
                    if (changes.length) this._pending = options;
                    for (var i = 0, length = changes.length; i < length; i++) {
                        this.trigger('change:' + changes[i], this, current[changes[i]], options);
                    }
                }
    
                // You might be wondering why there's a `while` loop here. Changes can
                // be recursively nested within `"change"` events.
                // 
                if (changing) return this;            
                if (!silent) {
                    while (this._pending) {
                        options = this._pending;
                        this._pending = false;
                        this.trigger('change', this, options);
                    }
                }
                this._pending = false;
                this._changing = false;
                return this;
            },
    
            // **parse** converts a response into the hash of attributes to be `set` on
            // the model. The default implementation is just to pass the response along.
            parse: function(resp, options) {
                return resp;
            },        
            // Check if the model is currently in a valid state.
            isValid: function(options) {
                return this._validate({}, _.extend(options || {}, { validate: true }));
            },
    
            // Run validation against the next complete set of model attributes,
            // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
            _validate: function(attrs, options) {
                //在实例化的时候需要传入验证函数validate
                //然后每次设置值的时候都进行验证
                //验证失败 触发invalid的回调事件
                if (!options.validate || !this.validate) return true;
                attrs = _.extend({}, this.attributes, attrs);
                var error = this.validationError = this.validate(attrs, options) || null;
                if (!error) return true;
                this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
                return false;
            },
    
            // Fetch the default set of models for this collection, resetting the
            // collection when they arrive. If `reset: true` is passed, the response
            // data will be passed through the `reset` method instead of `set`.
            fetch: function(options) {
                options = options ? _.clone(options) : {};
                if (options.parse === void 0) options.parse = true;
                var success = options.success;
                var collection = this;
                options.success = function(resp) {
                    var method = options.reset ? 'reset' : 'set';
                    collection[method](resp, options);
                    if (success) success(collection, resp, options);
                    collection.trigger('sync', collection, resp, options);
                };
                wrapError(this, options);
                return this.sync('read', this, options);
            },
    
            // Set a hash of model attributes, and sync the model to the server.
            // If the server returns an attributes hash that differs, the model's
            // state will be `set` again.
            save: function(key, val, options) {
                var attrs, method, xhr, attributes = this.attributes;
    
                // Handle both `"key", value` and `{key: value}` -style arguments.
                if (key == null || typeof key === 'object') {
                    attrs = key;
                    options = val;
                } else {
                    (attrs = {})[key] = val;
                }
    
                options = _.extend({validate: true}, options);
    
                // If we're not waiting and attributes exist, save acts as
                // `set(attr).save(null, opts)` with validation. Otherwise, check if
                // the model will be valid when the attributes, if any, are set.
                if (attrs && !options.wait) {
                    if (!this.set(attrs, options)) return false;
                } else {
                    if (!this._validate(attrs, options)) return false;
                }
    
                // Set temporary attributes if `{wait: true}`.
                if (attrs && options.wait) {
                    this.attributes = _.extend({}, attributes, attrs);
                }
    
                // After a successful server-side save, the client is (optionally)
                // updated with the server-side state.
                if (options.parse === void 0) options.parse = true;
                var model = this;
                var success = options.success;
                options.success = function(resp) {
                    // Ensure attributes are restored during synchronous saves.
                    model.attributes = attributes;
                    var serverAttrs = model.parse(resp, options);
                    if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
                    if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
                        return false;
                    }
                    if (success) success(model, resp, options);
                    model.trigger('sync', model, resp, options);
                };
                wrapError(this, options);
    
                method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
                if (method === 'patch') options.attrs = attrs;
                xhr = this.sync(method, this, options);
    
                // Restore attributes.
                if (attrs && options.wait) this.attributes = attributes;
    
                return xhr;
            },
            // A model is new if it has never been saved to the server, and lacks an id.
            isNew: function() {
              return this.id == null;
            }
        });
    
    
        // Helper function to correctly set up the prototype chain, for subclasses.
        // Similar to `goog.inherits`, but uses a hash of prototype properties and
        // class properties to be extended.
          //第一个参数是要扩展到原型上的对象, 第2个参数是静态方法扩展到构造函数上去的
        var extend = function(protoProps, staticProps) {
            var parent = this;
            var child;
    
            // The constructor function for the new subclass is either defined by you
            // (the "constructor" property in your `extend` definition), or defaulted
            // by us to simply call the parent's constructor.
            if (protoProps && _.has(protoProps, 'constructor')) {
                child = protoProps.constructor;
            } else {
                child = function(){ return parent.apply(this, arguments); };
            }
    
            // Add static properties to the constructor function, if supplied.
            //将静态方法和 parent上的静态方法一起扩展到child上面去
            _.extend(child, parent, staticProps);
    
            // Set the prototype chain to inherit from `parent`, without calling
            // `parent`'s constructor function.
            //创建一个新的构造含糊Surrogate ; 
            //this.constructor = child的意思是  Surrogate实例化后的对象  让对象的构造函数指向child
            // Surrogate的原型就是parent的原型
            // 然后实例化给child的原型,
            // 这里不是直接从new parent给child.prototype 而是创建一个新的构造函数,我也不知道为啥要这样
            var Surrogate = function(){ this.constructor = child; };
            Surrogate.prototype = parent.prototype;
            child.prototype = new Surrogate;
    
            // Add prototype properties (instance properties) to the subclass,
            // if supplied.
            // 把第一个参数上的属性扩展到child.prototype
            if (protoProps) _.extend(child.prototype, protoProps);
    
            // Set a convenience property in case the parent's prototype is needed
            // later.
            // 拿一个属性引用父的原型, 以免以后要用到.
            child.__super__ = parent.prototype;
    
            return child;
        };
    
        Model.extend = extend;
    
    
        // Backbone.sync
        // -------------
    
        // Override this function to change the manner in which Backbone persists
        // models to the server. You will be passed the type of request, and the
        // model in question. By default, makes a RESTful Ajax request
        // to the model's `url()`. Some possible customizations could be:
        //
        // * Use `setTimeout` to batch rapid-fire updates into a single request.
        // * Send up the models as XML instead of JSON.
        // * Persist models via WebSockets instead of Ajax.
        //
        // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
        // as `POST`, with a `_method` parameter containing the true HTTP method,
        // as well as all requests with the body as `application/x-www-form-urlencoded`
        // instead of `application/json` with the model in a param named `model`.
        // Useful when interfacing with server-side languages like **PHP** that make
        // it difficult to read the body of `PUT` requests.
    
        // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
        var methodMap = {
            'create': 'POST',
            'update': 'PUT',
            'patch':  'PATCH',
            'delete': 'DELETE',
            'read':   'GET'
        };
        // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
        // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
        // set a `X-Http-Method-Override` header.
        Backbone.emulateHTTP = false;
    
        // Turn on `emulateJSON` to support legacy servers that can't deal with direct
        // `application/json` requests ... will encode the body as
        // `application/x-www-form-urlencoded` instead and will send the model in a
        // form param named `model`.
        Backbone.emulateJSON = false;    
    
        Backbone.sync = function(method, model, options) {
            var type = methodMap[method];
    
            // Default options, unless specified.
            _.defaults(options || (options = {}), {
                emulateHTTP: Backbone.emulateHTTP,
                emulateJSON: Backbone.emulateJSON
            });
    
            // Default JSON-request options.
            var params = {type: type, dataType: 'json'};
    
            // Ensure that we have a URL.
            if (!options.url) {
                params.url = _.result(model, 'url') || urlError();
            }
    
            // Ensure that we have the appropriate request data.
            if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
                params.contentType = 'application/json';
                params.data = JSON.stringify(options.attrs || model.toJSON(options));
            }
    
            // For older servers, emulate JSON by encoding the request into an HTML-form.
            if (options.emulateJSON) {
                params.contentType = 'application/x-www-form-urlencoded';
                params.data = params.data ? {model: params.data} : {};
            }
    
            // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
            // And an `X-HTTP-Method-Override` header.
            if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
                params.type = 'POST';
                if (options.emulateJSON) params.data._method = type;
                var beforeSend = options.beforeSend;
                options.beforeSend = function(xhr) {
                    xhr.setRequestHeader('X-HTTP-Method-Override', type);
                    if (beforeSend) return beforeSend.apply(this, arguments);
                };
            }
    
            // Don't process data on a non-GET request.
            if (params.type !== 'GET' && !options.emulateJSON) {
                params.processData = false;
            }
    
            // If we're sending a `PATCH` request, and we're in an old Internet Explorer
            // that still has ActiveX enabled by default, override jQuery to use that
            // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
            if (params.type === 'PATCH' && noXhrPatch) {
                params.xhr = function() {
                    return new ActiveXObject("Microsoft.XMLHTTP");
                };
            }
    
            // Pass along `textStatus` and `errorThrown` from jQuery.
            var error = options.error;
            options.error = function(xhr, textStatus, errorThrown) {
                options.textStatus = textStatus;
                options.errorThrown = errorThrown;
                if (error) error.apply(this, arguments);
            };
    
            // Make the request, allowing the user to override any Ajax options.
            var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
            model.trigger('request', model, xhr, options);
            return xhr;        
        };
        Backbone.$ = $;
        // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
        // Override this if you'd like to use a different library.
        Backbone.ajax = function() {
            return Backbone.$.ajax.apply(Backbone.$, arguments);
        };
    
    
        var wrapError = function(model, options) {
            var error = options.error;
            options.error = function(resp) {
            if (error) error(model, resp, options);
                model.trigger('error', model, resp, options);
            };
        };    
    })();
    
    </script>
    </body>
    </html>
    View Code

    Model的extend

    Model的extend可以创建一个新的模型,扩展你所需要的方法和属性,这个方法在Model,View,Collection上都有.

    这里得介绍下constructor,虽然网上已经有很多介绍了。 当一个构造函数入a = function(){}; a被实例化b,b.constructor就指向a,也就是说constructor指向它的构造函数的.但是这个属性是可以修改的

    extend 这个函数依赖underscore

        // Helper function to correctly set up the prototype chain, for subclasses.
        // Similar to `goog.inherits`, but uses a hash of prototype properties and
        // class properties to be extended.
          //第一个参数是要扩展到原型上的对象, 第2个参数是静态方法扩展到构造函数上去的
        var extend = function(protoProps, staticProps) {
            var parent = this;
            var child;
    
            // The constructor function for the new subclass is either defined by you
            // (the "constructor" property in your `extend` definition), or defaulted
            // by us to simply call the parent's constructor.
            if (protoProps && _.has(protoProps, 'constructor')) {
                child = protoProps.constructor;
            } else {
                child = function(){ return parent.apply(this, arguments); };
            }
    
            // Add static properties to the constructor function, if supplied.
            //将静态方法和 parent上的静态方法一起扩展到child上面去
            _.extend(child, parent, staticProps);
    
            // Set the prototype chain to inherit from `parent`, without calling
            // `parent`'s constructor function.
            //创建一个新的构造含糊Surrogate ; 
            //this.constructor = child的意思是  Surrogate实例化后的对象  让对象的构造函数指向child
            // Surrogate的原型就是parent的原型
            // 然后实例化给child的原型,
            // 这里不是直接从new parent给child.prototype 而是创建一个新的构造函数,我不知道为啥要这样
            var Surrogate = function(){ this.constructor = child; };
            Surrogate.prototype = parent.prototype;
            child.prototype = new Surrogate;
    
            // Add prototype properties (instance properties) to the subclass,
            // if supplied.
            // 把第一个参数上的属性扩展到child.prototype
            if (protoProps) _.extend(child.prototype, protoProps);
    
            // Set a convenience property in case the parent's prototype is needed
            // later.
            // 拿一个属性引用父的原型, 以免以后要用到.
            child.__super__ = parent.prototype;
    
            return child;
        };
    
        Model.extend = extend;

    Model的set  set(attributes, [options])

    在调用set的方法,流程如下,在set的时候,看是否需要验证,需要验证则验证,验证成功就继续往下执行。然后再copy一个attributes的属性,赋值给this._previousAttributes,然后比较传入的参数跟attributes,这里有2中如果值一样则从this.changed里面删除,不一样则添加,this.changed总是保存这次跟上次之间值发生变化的那些属性. 然后再看时候设置了unset属性,如果设置了该属性从attributes删除该值,否则在attributes修改或者添加该值 然后看那些属性的值发生了变化,变化的属性值触发change:key的监听回调(key是属性名),然后再触发change监听的回调

    在调用set方法的时候大概用到了一下几个属性

    _previousAttributes

    changed

    _changing

    changed

    _previousAttributes  这个属性总是记录model修改之前的数据

    changed                  这个属性总是记录这次set操作后,对于上次的数据,修改了哪些数据,就是保存修改的数据  changed顾名思义,已改变的

    _changing               这个属性如果为true表示正在进行set中,false表示没有对model进行数据的操作

    set方法可以传入2个参数,也可以传入3个参数,其实传入就是支持对象的入参,和key,value的入参,如果第一个参数是对象就当做2个参数来处理,如果第一个参数是字符串,就会当成3个参数来处理,另外一个参数options,里面有几个属性会经常用到unset,silent

    unset 表示把这个属性取消

    silent 表示把是否触发绑定该属性监听的回调,不设置或者设置成false都会触发change:attr,设置为true表示不触发监听回调

    一个例子

    var m = new Backbone.Model();
    m.set({
        name : "xxoo",
        age  : 18,
        info : "wa haha!!!"
    });
    //监听age的变化如果  当age改变时  小于18和大于等于18弹出的结果是不一样的
    m.on("change:age",function(model,val){
        if(val>=18){
            alert("cheng nian le -_-");
        }else{
            alert("ni hai xiao ^_^");
        }
    });
    m.set({age:12}); //ni hai xiao ^_^
    m.set({age:20}) //cheng nian le -_-
    
    //如果改变age 的时候不想触发监听的函数 可以设置silent
    m.set({age:20},{silent:true})
    
    
    //取消一个属性
    m.set({info:1},{unset:true})
    console.log(m)
    
    //如果想监听所有的属性的变化可以用直接绑定chagne
    m.on("change",function(model){console.log(model)})
    
    m.set({age:1})
    m.set({name:2})

    Model的unset  model.unset(attribute, [options])  zzzzz

    就是删除一个属性,实现其实很简单就是调用set方法,把options里面的silent设置成true

                 var m = new Backbone.Model()
                 m.set("haha",123)
                 m.unset("haha")
                 //一次只能删除一个  参数不能传递对象 如果要删除多个就调用多次unset,如果要删除所有的,就调用clear

    Model的validate和_validate

    验证,在设置值得时候会进行验证,设置值得时候必须带上属性validate比如,m.set("test",11,{"validate":true})。validate是用户自己设定的验证函数,_validate是进行验证时的操作,验证失败后会触发invalid监听的回调

    var testModel = Backbone.Model.extend({
        validate:function(obj){
            if(obj.test>10){
                return "测试值不能大于10";
            }
            if(obj.age<18){
                return "不能小鱼18岁";
            }
        }
    })
    var m = new testModel()
    m.on("invalid",function(model,errText){alert(errText)}); //对设置值失败的统一处理函数,只要绑定了invalid,设置值失败了都会进这个地方
    m.set("test",11,{"validate":true})  //这个设置会失败的 
    m.set("test",9,{"validate":true}) //设置会成功
    m.set("test",2)//该值不会进行验证,因为没有设置{"validate":true}
    
    m.set("age",12,{"validate":true}) //这个测试当验证失败的时候他们回调用invalid的监听

     Model的一些属性和方法  cid    changed    _previousAttributes  hasChanged

    每个modl实例化的时候都会创建一个cid 保证model的唯一性.

    Model里面有一些属性和方法是专门对比上一次和修改后之前的差异的 

    changed 是属性 一个object  保存上一次和这次相比发生变化的值

    _previousAttributes 是一个对象  保存上一次的attributes

    hasChanged  是一个方法 判断一个属性,这一次跟上一次是否发生了变化

    var m = new Backbone.Model();
    m.set({
        a:1,
        b:1,
        c:1,
        d:1,
        e:1
    });
    
    m.set({
        a:2,
        b:3
    });
    
    //打印出来的只有a,b因为只有a,b发生了变化
    console.log(m.changed)
    
    //做个对比可以看到 _previousAttributes保存的修改前的值
    console.log(m._previousAttributes)
    console.log(m.attributes)
    
    //可以看到a返回的true  c则是false
    m.hasChanged("a")
    m.hasChanged("c")

    Model提供了跟后台交互的方法snyc fetch save destory方法 依赖jquery或者zepto,用到的也是$.ajax,基本上就是在做了一些事件的监听, 在工作中我都不用到

  • 相关阅读:
    JavaScript基础知识-流程控制之for循环
    JavaScript基础知识-流程控制之while循环
    HAProxy的高级配置选项-基于cookie实现的session保持实战案例
    HAProxy的四层与七层的区别及透传IP实战案例
    HAProxy基础配置-haproxy常见的调度算法
    HAProxy基础配置-配置haproxy的日志
    HAProxy基础配置-配置多进程多线程案例
    HAProxy基础配置-修改haproxy运行时的用户身份
    HAProxy基础配置-基于http的反向代理
    HAProxy基础配置-haproxy的配置文件说明
  • 原文地址:https://www.cnblogs.com/wtcsy/p/3841663.html
Copyright © 2020-2023  润新知