Backbone.js 开发人员最常犯的 8 个错误
已发表: 2022-03-11Backbone.js 是一个简约框架,旨在提供一组简单的数据结构和功能,您可以使用它们来创建结构化 Web 应用程序的前端。 开箱即用,Backbone.js 的组件提供了一个直观的环境,当您在后端处理模型和视图时,您可能已经熟悉该环境。 Backbone.js 中的模型和集合很简单,但它们具有一些非常有用的特性,例如可以轻松地将它们与 REST JSON API 集成的选项。 但它们也足够灵活,几乎可以适应任何实际用途。
在这个 Backbone.js 教程中,我们将看看自由开发者在第一次尝试学习 Backbone.js 时经常犯的一些常见错误以及避免这些错误的方法。
错误 #1:忽略 Backbone.js 的功能库
Backbone.js 可能是一个极简框架,但它(连同 Underscore.js)提供了大量特性和功能,可以轻松满足开发现代 Web 应用程序时出现的最基本和一些关键需求。 初学者开发人员经常犯的一个常见错误是,他们将 Backbone.js 视为另一种类似 MVC 的 Web 客户端框架。 尽管本节讨论了一些非常明显的事情,但对于 Backbone.js,不彻底探索框架是一个非常严重的错误。 该框架的规模可能很小,但这就是使其成为这种彻底探索的绝佳候选者的原因。 尤其是它的小且注释很好的源代码。
Backbone.js 提供了为您的 Web 应用程序提供可以从中受益的结构所需的最低要求。 凭借其可扩展性和大量插件,学习 Backbone.js 可用于构建一些令人惊叹的 Web 应用程序。 Backbone.js 的一些最明显的特性通过模型、集合和视图暴露出来。 路由器和历史组件提供了一种简单而优雅的机制来支持客户端路由。 尽管 Underscore.js 是 Backbone.js 的依赖项,但它很好地集成到了框架中,因为模型和集合都从这个惊人的 JavaScript 实用工具带中受益匪浅,并且也可供您使用。
该框架的源代码编写得非常好,并且注释非常好,以至于人们可能会在喝一杯咖啡时阅读它。 初学者可以从阅读源注释中受益匪浅,因为他们可以学到很多关于框架内部如何工作的知识,并且在涉及 JavaScript 时还可以采用一组简洁的最佳实践。
错误 #2:修改 DOM 以直接响应任意事件
当我们第一次开始学习 Backbone.js 时,我们倾向于做的事情是不做 Backbone.js 推荐的事情。 例如,我们倾向于像在简单网站上使用 jQuery 一样处理事件和查看更新。 Backbone.js 旨在通过适当的关注点分离为您的 Web 应用程序提供一个刚性结构。 我们通常倾向于使用 Backbone.js 来更新视图以响应任意 DOM 事件:
var AudioPlayerControls = Backbone.View.extend({ events: { 'click .btn-play, .btn-pause': function(event) { $(event.target).toggleClass('btn-play btn-pause') } }, // ... })
这是应该不惜一切代价避免的事情。 可能会提出一些晦涩难懂的例子,这可能是有意义的; 但在大多数情况下,有更好的方法来做到这一点。 事实上,我可以在这里举例说明的一种方法是使用模型来跟踪音频播放器的状态并使用该状态信息来呈现按钮(或更具体地说是它的类名):
var AudioPlayerControls = Backbone.View.extend({ events: { 'click .btn-play, .btn-pause': function(event) { this.model.set('playing', !this.model.get('playing')) } }, initialize: function() { this.listenTo(this.model, 'change', this.render) this.render() }, // ... })
<button class=”btn btn-<%- playing ? 'pause' : 'play' %>”></button>
在极少数情况下,从事件处理程序直接操作 DOM 是有意义的,但是从事件处理程序管理复杂的 DOM 操作所涉及的成本几乎不值得。 这是 Backbone.js 旨在解决的问题。 使用 Backbone.js 做这样的事情是错误的。
错误 #3:低估渲染成本
由于 Backbone.js 使得随意渲染和重新渲染 DOM 或响应事件变得非常容易,我们经常忽略这对 Web 应用程序的整体性能有多大影响。 有很多方法可以最终在我们的视图上破坏渲染方法。 通常这似乎并不多,因为现代网络浏览器正在成为高性能软件。 但是随着 Web 应用程序的增长,以及它处理的数据量的增长,性能下降变得越来越明显。
我们可以通过一个人为的示例看到这一点,我们从一小部分模型开始,并将其呈现到列表视图中:
var AudioPlayerPlaylist = Backbone.View.extend({ template: _.template('<ul> <% _.each(musics, function(m) { %> <li><%- m.title %></li> <% }) %> </ul>'), initialize: function() { this.listenTo(this.collection, 'add', this.render) }, // ... })
在这个 Backbone.js 示例中,只要将模型添加到集合中,我们就会重新渲染。 这将正常工作。 但是,由于每次将模型添加到列表时都会触发“添加”事件,因此想象一下从服务器获取大量模型列表。 render 方法将被连续调用多次,服务器响应中的每个模型一次。 足够大的模型足以使您的应用程序卡顿并破坏用户体验。 有时一个小的响应就足够了,这取决于正在渲染的视图的复杂性。
一个非常快速的解决方案是简单地不为正在添加的每个模型调用渲染方法。 在这种情况下,模型将被批量添加,您实际上可以做一些事情来使渲染方法仅在被调用但在指定时间内不被重新调用时触发。 Backbone.js 的依赖 Underscore.js 带有一个方便的实用函数:“_.debounce”。 您需要利用这一点的所有内容是使用以下代码更改事件绑定 JavaScript 行:
this.listenTo(this.collection, 'add', _.debounce(_.bind(this.render), 128))
这将导致每次“添加”事件发生时都会触发事件回调,但是,它将在实际调用渲染方法之前从最后一个事件等待 128 毫秒。
在大多数情况下,这将被视为类似快速修复的解决方案。 事实上,有更合适的方法来避免渲染抖动。 Trello 背后的开发人员曾经写过一篇博客文章,讨论了他们在使用 Backbone.js 时提高渲染性能的经验和方法。
错误 #4:让事件侦听器超出其使用范围
无论您使用什么 JavaScript 框架,或者您是否使用了一个框架,都可能会发生绑定未使用的事件侦听器的情况。 尽管 Backbone.js 可以很容易地避免这个问题,但在您的 Web 应用程序中留下潜在的漏洞以导致内存泄漏肯定是错误的。 Backbone.js 的“事件”组件当然是一个非常简洁的实现。 它允许 JavaScript 对象轻松实现基于事件的功能。 由于视图是我们大多数事件消费通常发生的地方,因此很容易在那里犯这个错误:
var AudioPlayerControl = Backbone.View.extend({ initialize: function() { this.model.on('change', _.bind(this.render, this)) // ... }, // ... })
这段代码中的事件绑定行与第一个示例中的没有太大区别。 我们在这里所做的只是将“this.listenTo(this.model, ...)”更改为“this.model.on(...)”。 由于根据我们使用其他一些 JavaScript 框架和库的经验,我们非常习惯于“.on()”调用来进行事件绑定,所以当我们开始使用 Backbone.js 时,我们通常倾向于使用“.on()”调用来绑定事件。 这会很好,只有当我们不再需要时调用“.off()”来取消绑定事件处理程序。 但我们很少这样做,它最终成为内存泄漏的来源。

Backbone.js 提供了一种简单的方法来解决这个问题。 这是通过使用“object.listenTo()”方法。 这使您正在调用“listenTo()”的对象能够跟踪它正在侦听的事件,并且还可以轻松地一次解除所有这些事件的绑定。 例如,一旦您在其上调用“remove()”,视图就会自动停止侦听所有绑定事件。
错误#5:创建单一视图
如果您考虑一下,Backbone.js 的极简主义为您希望如何构建 Web 应用程序的前端提供了极大的灵活性。 由于模型、集合和视图是组件的构建块,因此您必须使它们尽可能轻巧且尽可能具体。 就代码而言,通常是视图最终成为您的 Web 应用程序中最重要的方面。 但非常重要的是,您最终不要创建巨大的整体视图,最终尝试完成您的应用程序必须提供的所有功能。 与其制作一个包含所有逻辑的巨大“AudioPlayer”视图,不如将其拆分为多个逻辑视图,例如播放列表视图、控件视图、可视化器视图等。 您要确保哪种粒度可能取决于您尝试构建的应用程序。
这是因为在细粒度视图中,每个视图都执行特定的操作并且做得正确,使用 Backbone.js 开发 Web 应用程序变得轻而易举。 您的代码应该更易于维护,并且将来易于扩展或修改。 然后是另一个极端,你最终会过度使用它。 Backbone.js 视图旨在使您可以方便地使用模型或集合,这可能可以作为您应该如何构建应用程序的提示。 Ian Storm Taylor 在他的博客上分享了一些有价值的想法,您在实现视图时应该牢记这些想法。
错误 #6:没有意识到 Backbone.js 可以适应非 RESTful API
Backbone.js 可与基于 JSON 的 RESTful API 一起使用,开箱即用。 你所需要的只是 jQuery(或者可以直接替代它的东西,比如 Zepto)。 但是,Backbone.js 具有极强的可扩展性。 事实上,Backbone.js 甚至可以适应使用其他类型的 API 和其他类型的编码格式。
Backbone.js 中处理前端与后端服务交互的组件是“Sync”。 该组件公开了许多属性,您可以轻松覆盖这些属性以自定义 Backbone.js 与 API 端点交互的方式。 实际上,也可以将默认的同步机制替换为至少可以说不那么传统的东西,例如使用 localStorage 来持久化数据,而不是后端服务。
存在许多插件,可以轻松自定义 Backbone.js 的同步行为。 例如,名为 Backbone.dualStorage 的插件允许您同时使用后端服务和 localStorage 来持久化数据。 当您的应用程序离线时,该插件使用 localStorage 来保持为来自缓存数据的请求提供服务,并跟踪您稍后在线时可能与服务器同步的更改。
尽管将 Backbone.js 与设计为 RESTful 并与之兼容的后端一起使用更容易使用,但这并不意味着 Backbone.js 可以使用所有这些。 通过对默认 Backbone.js 同步机制进行一些更改,您可以使其适应各种后端服务 API 和编码格式。
值得一提的是,Backbone.js 的其他部分也很灵活,并且在某些方面是可选的。 例如,您不必使用 Underscore.js 附带的默认模板引擎。 您甚至不必使用 Backbone.js 的视图组件,并且可以根据需要将其完全替换为其他东西。
错误 #7:将数据存储在视图而不是模型中
作为初学者学习 Backbone.js,我们可能经常犯的一个错误是将数据作为属性直接存储在视图上。 该数据可能用于跟踪某些状态或某些用户选择。 这是应该避免的。
var AudioPlayerVisualizer = Backbone.View.extend({ events: { 'click .btn-color': function(event) { this.colorHex = $(event.target).data('color-hex') this.render() } }, // ... })
您始终可以创建一些没有端点的其他模型和集合。 这些可以帮助您存储不一定要保留在后端的数据,或者本质上可能是临时的。 将它们存储在模型中会给您带来能够监听变化的好处。 相关视图,甚至多个视图,可以观察这些模型并根据需要重新渲染自己。
想象一下,如果您实际上将状态跟踪变量存储在视图上,并且每次更改它们时都必须调用渲染方法。 就用户在屏幕上的体验而言,仅缺少一次调用此渲染方法可能会使您的应用程序处于损坏状态。 此外,对于小视图,您可能必须在多个视图对象上同步这些状态变量,然后还必须在它们上调用渲染方法。
错误 #8:使用 jQuery “.on()” 而不是委托事件
在我看来,Backbone.js 是一种处理 DOM 事件的绝妙方法。 不使用它会带来很多缺点。 jQuery 的“.on()”事件绑定功能感觉很方便,但从长远来看往往很麻烦。 例如,当元素从 DOM 中分离出来时,jQuery 会自动删除所有使用“.on()”绑定到元素的事件处理程序。 这意味着如果您从 DOM 中分离根元素并重新附加它,则您尝试从视图中绑定到的任何 DOM 事件都需要重新绑定。
var AudioPlayerControls = Backbone.View.extend({ events: { 'click .btn-play, .btn-pause': function() { /* ... */ }, 'click .btn-prev': function() { /* ... */ }, 'click .btn-next': function() { /* ... */ }, 'click .btn-shuffle': function() { /* ... */ }, 'click .btn-repeat': function() { /* ... */ } }, // ... })
当与该视图对应的元素重新附加到 DOM 时,您所要做的就是在视图上调用“delegateEvents()”来绑定所有这些事件。
请注意,了解这些事件是如何绑定的很重要。 Backbone.js 实际上将事件处理程序绑定到视图的根元素,而不是将事件绑定到选择器指定的元素上。 这在几乎所有情况下都可以正常工作,实际上更适合我们的大多数需求。 更改或替换视图的 DOM 子树中的子元素不需要 Backbone.js 将每个事件再次绑定到新元素上。 现有的听众只是继续工作。
但是,这会阻止某些事件被监听。 一个示例是您可能希望在“窗口”或子可滚动元素上侦听滚动事件。 如果是子元素,您可以为该元素创建一个子视图并在那里处理事件。
结论
Backbone.js 是一个非常紧凑但可扩展的框架,对于需要大量后台灵活性的 Web 应用程序来说,它是一个绝佳的选择。 与 Angular.js 和 Ember.js 等框架总是告诉你如何做你想做的事情不同,Backbone.js 后退一步,为你提供了一套强大的工具,让你决定如何使用他们。 我希望这个针对初学者的 Backbone.js 教程将帮助您避免一些常见的开发错误并用它构建一些令人惊叹的东西。