因此,在这篇文章中,我们将介绍很多不同的设计模式供你在Backbone.js应用中使用,而且我们也会一同来看看对于开发者来说会产生很多普遍的有关性能伸缩的问题。
对象深度拷贝
JavaScript对待所有原生类型变量是传值。所以,当变量被引用时就传递了变量的值。
举个例子,上面的代码将变量helloWorldCopy的值设置为变量helloWorld的值。这样, 自从它的值被复制之后,所有修改helloWorldCopy的值不会修改helloWorld的值。JavaScript对待所有非原始类型的变量时传引用,这就意味着当变量传递的时候将会传递内存地址引用。
举个例子,上面的代码将设置helloWorldCopy为helloWorld的引用,而且,也许你会猜到任何修改helooWorldCopy的值都会直接导致helloWorld值的变化。如果你想要helloWorld的拷贝,你可以创建一个拷贝对象即可。
也许你会想到“为什么Backbone.js可以解释为所有的工作都是通过传递引用?”事实上,Backbone.js不会拷贝对象,这将意味着如果你从模型里调用.get()方法获得一个对象,任何给这个对象的修改都会直接修改原来的对象。让我们一起来看一个例子来阐明哪里会发生这样的情况。如果你有个如下的Person模型:
这样你就创建了一个新的person对象:
现在我们来对新对象的一些属性进行操作操作:
person.set('name', 'Phillip W.', { validate: true });
上面的代码成功的给person对象的name属性赋了值。现在我们在来操作person对象的地址属性。当然,在我们这样做之前先验证一下地址属性。
现在,让我们试图给地址属性设置一个不正确的ZIP代码。
这将会怎样呢?我们的验证出现了错误!为什么属性依旧被改变了?前边我们说过,Backbone.js不会拷贝模型属性;它会返回你所请求的一切。这样,你也许会猜到,如果你需要一个对象,你将得到这个对象的引用,对这个对象的任何操作都会直接改变模型里的对象。如果你要debug,这可能将把你带入到无底的兔子黑洞。
这个问题对于新的Backbone.js使用要引起注意,甚至对于老练的JavaScript程序员有时也会没有提防。这个问题在GitHub的Backbone.js讨论组中有很激烈的讨论。正如Jeremy Ashkenas指出,执行一个深的对象引用是个很难解决的难题,一个很深的对象引用是要花费很大代价的。
幸运的,jQuery 提供了一个深度拷贝功能来实现,$.extend. 如同, Underscore.js ,一个Backbone.js的依靠,提供_.extend 方法,但是我必须避免使用它,因为它没有执行一份是个深度的复制,Lo-Dash, Underscore.js的一个分叉版本,提供了对象一个深度克隆的_.clone 方法的选项。然而,我使用 $.extend 方法的模型使用的语法规则去执行一个任意对象的深度克隆。记得通过后,结果它执行的是一个深度的克隆方法
我们现在快速准确的复制一个theaddressobject?,并且我们能够更改它对于我们要点没有包括在内的我们不用担心会更改它原有的模型。你必须要意思到这个父工厂对于上面所有的事例因为它所有的地址对象成员都是不可变的(numbers, strings, etc.),与此同时这上面所有的事例工厂当你要深度复制对象里面包含的对象时你都必须小心的使用。你必须也要知道一个小小的性能影响都来自于执行一个深度的克隆,但是我从来没有看到过很明显的问题。然而,如果你要深度的克隆一个大对象或者成千上万的对象所有的立即复制,你将有可能做大量的性能分析。这将领导我们直接到下一个模式。
为对象创建外观
在现实世界中,需求经常变化,JavaScript对象符号(或者说JSON)也是一样,这些对象是由模型和集合所在的端点返回的。这或许会成为你的基础代码中的一个真正的大麻烦,如果你的视图与底层的数据模型是紧耦合的话。因此,我为所有对象创建了getters和setters。
支持这个模式的人非常多。如果任何底层的数据结构改变了,那么视图层并不需要更新许多;你将有一个数据的访问点,所以你不太可能忘记做一个深度拷贝,你的代码将会更易于维护更易于调试。负面因素在于这个模式可能导致模型或集合的一点点膨胀。
我们看一个例子来阐明这个模式。想像我们有一个Hotel模型,包含有rooms和目前可获得的rooms,而且我们希望可以通过床位大小来获得rooms。
现在我们假设明天你就要发布你的代码,而你又发现端点开发者忘记告诉你rooms的数据结构改变了,由一个对象变为一个数组。你的代码现在看起来会像下面这样。
我们仅仅更新了一个函数,以便将Hotel的结构转变为这个应用的其余部分所期望的结构,同时整个应用仍然像我们所期待的一样运作。如果这里没有一个getter,我们很可能不得不为rooms更新每个访问点。理想情况下,你会希望更新所有的函数,以适应新的数据结构,但如果你在时间方面有压力急于发布的话,这个模式将可以拯救你。
离题说一句,这个模式既可以被认为是装饰模式,因为它隐藏了创建对象拷贝的复杂性,也可以认为是桥接模式,因为它可以用来将数据转换为所期望的形式。一个好的经验是对任何对象元素使用getters 和setters 。
存储数据不是通过服务器保存
尽管Backbone.js有模型和集合映射的规定去具象状态的传输(or REST-ful)的端点,你将花大量的时间去找你想要的存储数据在你的模型或者不是在服务器上的连接。另外一些关于Backbone.js的文章,例如“Backbone.js Tips: Lessons From the Trenches” 是通过SupportBee的Prateek Dayal ,这个模式还有其他的描述。让我们一起来快速的看一个小例子来帮助我们说明它可能会派上用场。假设你有一个集合。
当使用者点击其中一个项目时,这个项目成为了被选中状态并且对于使用者作为选中项目是通过 aselectedclass 添加的是可视化的。以下这是一种方式:
这个模式也执行了单一职责原则,规定了每个类应该具有一个单一的职责,而且它的职责应该封装与这个类之中,因为模型与集合要处理数据,而视图要处理渲染。
路由中的参数
最好的演示这个模式工作方式是举个例子。比如说你需要对搜索页面进行排序,每个搜索页面都允许用户添加两个不同的过滤类型foo和bar,每个类型代表不同的观点。
因此,你的URL结构将会呈现如下:
现在,所有的路由都用的是同一个试图和模型,这样大多数人喜欢用同一个函数search()来实现。然而,你要是检查过Backbone.js代码的话,你会发祥它里面没有排序的参数映射;这些参数只是从左至右依次传入函数。这样,为了都能统一使用一个函数,你就要停止创建不同的函数正确的来为search()匹配参数。
你也许能想象的到,这个模式可以使路由功能很快膨胀。当我第一次遇到这个问题时,我试图创建了一些用正则表达式定义的解析函数来“神奇”的去匹配参数,当然这个是可以工作的-但这也是有约束条件的。这样,我废弃了这个想法(有时,我仍然可以用Backbone插件来解决)。我进入GitHub中的一个 议题,其中Ashkenas建议应该让所有的参数都和search函数匹配。
上面的代码现在转变为下面维护性更强的样子:
这种模式可以戏剧性的减少路由的过分膨胀。然而,需要注意到它不会服务于不能区别的参数。比如,如果你有两个作为ID的参数,如模式XXXX-XXXX,你不能区分哪个ID是对哪个参数的回应。
model.fetch() 不会清除你的模型
这通常会将那些Backbone.js的新手给绊倒:model.fetch()并不能丢掉你的模型,而是扩展了你的模型的属性。因此,如果你的模型具有属性x,y和z,你获取到y和z,那么x将仍然是模型中的那个x,只有y和z会被更新。下面的例子将这个概念形象化了。
PUTs 需要一个 ID 属性
这一条也经常将Backbone.js的新手绊倒。要想在调用.save()的时候让模型发送一个HTTP PUT请求,你的模型需要有一个ID属性集。记得HTTP PUT谓词是设计来做更新的吧,所以发送一个PUT请求,你的模型需要有一个ID,这么做是有意义的。在理想的世界里,你的所有模型都具有一个名为ID的完美的ID属性,但是你从端点接收到的JSON数据可能并不总是具有完美命名的IDs。
因此,如果你需要更新一个模型,请在保存之前确认模型上有ID。Backbone.js 的0.5以及更高版本允许你用id属性来更新模型的ID属性名称,如果你的端点返回的不是名为id的IDs的话。
如果困顿于使用的是版本低于0.5的Backbone.js,我建议你修改你的模型或集合的parse函数,以便将你期望的ID属性映射到属性ID。这里有一个快速上手的例子,说明了你应怎样修改parse函数来做到这一点。我们假设你有一个cars的集合,它的IDs是carID。
页面加载时创建模型数据
有时你会发现你的模型或者集合需要在页面加载时被初始化赋值。许多关于Backbone.js模式的文章,例如Rico Sta Cruz的 “Backbone 模式” 和 Katz的 “ 避免常见的Backbone.js陷阱” ,讨论了这种模式。这种模式实现很容易,只需在页面中内联一段脚本,通过你选择的服务端语言,将单个模型属性或者JSON形式的数据呈现出来。例如,在Rails语言中,我采用下面方法之一:
应用这种模式可以通过“立即的”渲染页面,改善你的搜索引擎排名,而且它也可以通过限制应用初始化HTTP请求的方式,大大缩短你的应用启动与运行所需要的时间。
处理失败的模型属性验证
很多时候,你会想知道是哪个模型属性验证失败了。例如,如果你有一个极其复杂的表单,你或许想知道哪个模型属性验证失败,这样你就可以将这个属性对应的输入字段高亮显示。不幸的是,提醒视图到底是哪个模型属性验证失败并没有直接集成于Backbone.js,但是你可以用一些不同的模式去处理这个问题。
返回一个错误对象一个给视图提醒哪个模型属性验证失败的模式是,返回一个对象,其中包含某种标志,它详细的记录了哪个属性验证为失败,就像下面这样:
这个模式的优点在于,你是在一个地方处理所有不合法的消息。缺点在于,如果你以不同的方式处理不合法的属性的话,你的invalid方法可能会成为一个很大的switch或者if语句。
广播自定义Error事件
我的一个朋友,Derick Bailey,推荐了一个可替代模式,就是为每个模型属性触发自定义的errors事件。这将允许你的视图能够针对单独的属性绑定到特定的error事件:
这个模式的优点在于,你的视图明确的绑定到它们所绑定到的error类型,而且如果你对每一种属性error有特定的指令的话,它可以清理你的视图部分代码,使之更易于维护。这个模式的一个不好的地方在于,如果在你处理不同的属性error时并没有太多的不同的话,你的视图可能会变得极为膨胀。
这两种模式都有其利弊,你应该考虑清楚哪个模式对你的应用案例是最优的。如果你按照同样的方式处理所有失败的验证,那么第一个方法可能是最好的;如果你对每个模型属性有特定的UI变化,那么后一种方法更好。
HTTP状态代码200所触发的错误
如果你的浏览器端模型或者集合收到了无效的JSON,尽管HTTP的状态代码是200,但浏览器端依然会触发一个“错误”事件。这种事件常发生于本地模拟JSON数据造成的。那么,一个好的方法就是读取经过 JSON 验证器验证了的模拟JSON数据文件。或者从你的IDE获得相应的 插件来及时获取格式错误的JSON信息。创建一个一般性错误显示模式
创建一个常见错误显示代码可以节省你的时间以及创建一个统一的模式来处理、可视化错误信息,而且它可以增加开发者的经验。我之前开发的每一个Backbone.js应用中我都会创建一个可以处理alert的视图:
上面的代码首先会检查是否已在视图代码中创建了指定视图in-page-alert div。如果没有,则接着查看一般性的在其它地方声明的body-alert div。这样可以让你发送具有一致性的错误信息以及当你忘记指定一个in-page-alert div时提供有用且可靠的信息。如下面的模式简化了让你怎样在你的试图中处理错误信息:
单页面应用中更新浏览器页面标题
这是一个比任何东西都重要的可用性问题。如果你正在开发一个单页面应用程序,谨记更新每个页面的标题。我写过一个的插件(Backbone.js Router Title Helper)来扩展 backbone.js router 的功能。它通过一个 Map 对象来控制路由,键来代表路由函数的名字,值则映射到页面的标题。
单页面应用中的缓存对象
当我们谈论单页面应用时,另一个叫缓存对象模式你将会经常用到!下面的例子直截了当而且简单:
这个模式可以加速你得应用,因为你不用重复初始化你得Backbone.js对象。然而,它会过多的消耗内存;所以,缓存对象就要在整个应用中使用。如果以前你用过Backbone.js开发过应用,也许你会问你自己,“ 我要重取数据该怎么做?”你可以每次在如下路径中触发后重取数据:
当你的应用从端点(如,一个收件箱)必须检索最新数据时上面的模式就可以工作。当然,如果你要拿的数据时凭借应用的某个状态(假设这个状态是通过URL和参数来决定的),甚至是在用户上一个页面应用的状态没有改变, 你可以重取数据。一个好的解决方案去重拿数据时当应用(参数)发生变化时:
JSDoc函数和Backbone.js类
我是文档注释和JSDoc的超级粉丝。我用JSDoc对所有的Backbone类添加了文档注释:
如果你对Backbone类进行如上添加文档注释,这样你可以给所有类和函数 添加参数、返回类型以及描述文档注释了。确保保持初始化函数作为一个声明的函数,这样可以帮助我们生成JSDoc。如果你想看看JSDoc的例子工程,那就在 HomeAway Calendar Widget下载例子。同时这里也有个 Grunt.js插件, grunt-jsdoc-plugin,这个也可以作为你构建文档注释时的一部分。
联系测试驱动的开发模式
我认为如果你用Backbone.js,你应该在开发模型和集合时遵循测试驱动开发(TDD)。我第一次用Jasmine.js创建模型和集合时遵循TDD进行单元测试,但失败了。一旦写下单元测试并且失败,我会对整个模型和集合进行重写。
通过这一点,我的所有Jasmine测试都通过了,而且我有信心我的模型和集合会和我期望的一样工作。自从我遵循TDD,我的视图层非常容易写而且非常简单。当你开始用TDD时,你得速度当然会很慢;但是一但你得脑海里一直想着TDD,你的编程效率和质量会神奇般的提高。
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。TEL:177 7030 7066 E-MAIL:11247931@qq.com