backbond中的M,指的是模型,即存放数据以及数据相关逻辑的单位。在分析其结构之前,先看一下其调用过程。
<script> (function ($) { World = Backbone.Model.extend({ initialize: function(){ alert(‘Hey, you create me!‘); }, defaults: { name:‘张三‘, age: ‘38‘ } }); var wodld = new World({x:1}); var x = new World({y:2}); })(jQuery);
backbond 通过Backbone.Model.extend方法得到一个World类(为了不让World和其实例化结果混淆,这里把World称为类,实例化结果称为对象),再通过实例化World来获得实例对象,并调用类中的initialize方法。这看起来和java很相似,也就是一种面向对象的编程方法。
在前面的backbond架构分析中,我们知道Backbone.Model.extend就是extend函数,从extend入手分析,先看一下extend在内部的实现。
var extend = function(protoProps, staticProps) { var parent = this; var child; // 如果传入的对象中存在属性为construtor,那么将其构造函数作为child // 否则,child作为一个调用父类的方法 if (protoProps && _.has(protoProps, ‘constructor‘)) { child = protoProps.constructor; } else { child = function(){ return parent.apply(this, arguments); }; } // 调用underscore的extend方法 将传入的第二个参数添加进child _.extend(child, parent, staticProps); // 对原型链进行设置 通过创建一个surrogate来使得child的原型链获得parent原型链 // 如果直接赋值 即child.prototype = parent.prototype,那么对child.prototype的改造也会影响到parent.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. if (protoProps) _.extend(child.prototype, protoProps); // 最后,由于上面的改造原型链,需要将child的上一层原型改为parent child.__super__ = parent.prototype; return child; };
在extend中,最后返回的是一个函数,也就是上面例子中的World类,extend中的parent也就是Backbone.Model,即使得返回的函数的原型上具有Model和我们传入的属性。
接下来就是Model函数了,
var Model = Backbone.Model = function(attributes, options) { //设置属性 var attrs = attributes || {}; options || (options = {}); this.cid = _.uniqueId(‘c‘); this.attributes = {}; if (options.collection) this.collection = options.collection; if (options.parse) attrs = this.parse(attrs, options) || {}; attrs = _.defaults({}, attrs, _.result(this, ‘defaults‘)); this.set(attrs, options); this.changed = {}; //调用initialize函数 this.initialize.apply(this, arguments); };
我们知道,在js中使用new字符调用一个函数时,也就是创建了一个对象,this指向了这个对象并使该对象继承了构造函数的原型链,最后如果返回结果不是一个对象的话就返回这个对象。
那么在上面的例子中,最后通过了var world = new World({x:1});调用了World类,
而一开始我们在构造World类时并没有传入具有属性为constructor的对象,也就是说 World = function(){ return BackBond.Model.apply(this, arguments); };
其中的this就是新创建的对象,那么就在新创建的对象下调用了Backbond.Model,最后返回了这个对象,也就是我们上面的world对象。
最后我们在调试器打印出world对象。
最后,总结一下backbond具体的设计思路:
1: 定义Model函数,并在其原型上设置一系列方法。
2.1: 通过extend函数,获得一个函数(也就是我们创建的类),其原型继承了Model函数原型
2.2: 并根据我们传入的参数设置类为一个构造函数或者通过apply将上下文设置为我们的实例化对象来调用Model函数的函数(即初始化,并调用initlize函数,相当于java的构造函数)。
2.3: 最后返回类。
3: 实例化父类,获得对象。
这样的设计最终会使得我们像使用面向对象语言一样来使用Js。(类,构造函数,对象,继承...)。