(警告:我的回答变成了 tl;dr 论文)
我很早就遇到了一些同样的问题,并且为了构建一些非常复杂的应用程序做了功课,所以我将提供我的观点。
学习骨干的最大挑战是它是如此的无主见,并且可以(并且正在)以多种不同的方式使用,以至于很难弄清楚如何“正确”地做某事,或者至少在开始时以一种好的方式做事出去。使用主干的方法不止一种,但它的灵活性使它成为几乎所有应用程序的绝佳结构,希望我能提供一些指导。(我可能会在此处的每个句子中附加“IMO”)。
一、我对主干观点的理解
在主干应用程序中,有很多有用的方法来使用视图。我通常会在我的应用程序中看到一些重叠类型的视图:
我通常有一个或多个“根级”视图。根级视图通常是初始化、呈现和保留对处理页面特定部分的子视图的引用的地方。根el
级视图通常是“正文”或正文中的另一个高级元素。在某些情况下,根级视图有自己的 HTML 来观察和/或呈现。在其他情况下,根级视图可能根本没有el
,只管理子视图。我在全局“app”命名空间对象中保留对每个根级视图(通常只有一个)的引用。
除了“根级”视图之外,通常还有“子视图”。子视图由“父”视图初始化和渲染,该视图可以是根级视图或另一个子视图。父视图负责根据应用程序的需要初始化、渲染、隐藏、显示和/或销毁其子视图。有时,父视图可能会跟踪可变数量的子视图实例(例如,一个播放列表视图有 N 个歌曲视图)。通常,父母会保留对孩子的引用,但有时这是不必要的(更多内容见下文)。
除了“根级/父级/子级”范式之外,我倾向于将视图归为以下两类之一:(1) 静态:意味着一旦视图被初始化,视图和它的el
存在,即使有东西里面的变化;(2) 动态的,根据各种事件来来去去。通常,我的根级视图始终是静态的。它们通常还对应于现有的 DOM 元素,例如“body”或“#my-div”。子视图通常是动态的,但也可能是静态的。(提示:在声明静态视图时el: '#element-id'
使用现有的 DOM 元素el
。动态视图通常不指定现有的el
;它们使用tagName
id
andclassName
来描述动态视图将生成的元素。)
视图本质上具有 3 个功能:(1)当父母告知或响应事件时(或者在根级视图的情况下,当由路由器或“主”函数初始化时)呈现自己和他们的孩子等),(2)通过更新模型或集合或触发自定义 Backbone 事件来响应来自 DOM 元素内el
(但不在el
任何子视图内)的 UI 事件,以及(3)观察和响应 Backbone(模型,集合等)需要在其 el 中呈现或更改某些内容的事件(但不在任何子视图的 el 中)。有时有用的一个技巧是子视图可以触发自己的事件(this.trigger('customEvent')
)父视图可以观察到( childView.on('customEvent', this.handler, this)
)。
有关主干视图模式的其他有趣观点,请参阅:this和this。
现在在这种情况下,关于问题
1) 对垃圾收集、范围和内存泄漏的恐惧
如果您在父级的渲染(或其他)方法中将子视图实例化为本地变量并渲染它,然后函数超出范围,我可以理解您对垃圾收集的恐惧或视图将无法做它需要做的事情。不需要害怕垃圾收集器,只需要僵尸。如果您的视图有任何事件处理程序,无论是在“事件”声明中声明的 UI 事件处理程序,还是绑定到其他 Backbone 对象的事件,或其他基于 DOM 的事件侦听器,即使您没有,您的视图也不会被垃圾回收。不再有对它的引用——它仍将存在于内存中并响应事件。另一方面,如果一个视图没有任何事件处理程序,那么它唯一的工作就是渲染一个元素,所以谁在乎呈现它的 javascript 对象是否存在——它可能会被垃圾收集,因为它应该是。看this,以便更好地理解 js 垃圾收集的一般情况以及它与 Backbone.js 的关系。
更大的担忧是僵尸视图。如果要从 DOM 中删除视图并在您的应用程序中的某个时间点将其丢弃,请确保它们完全删除自己,或者父视图应保留对它们的引用并删除它们。并且不要重新创建和替换已经创建但未正确删除的视图。移除是通过在视图上调用 .remove() 来完成的,并取消绑定之前使用on(...)
using绑定的任何外部 Backbone 事件off(...)
。Backbone 的最新版本(1.0+)通过将“listenTo”和“stopListening”方法添加到 View 原型使这个问题更容易解决。如果您要动态地将视图添加到 DOM 或从 DOM 中添加视图,请理解并使用这些方法而不是开/关。 提示:设置一个像这样的 hacky jquery "remove" 事件可以很容易地使视图在从 DOM 中删除时自动删除和清理它们el
(如果您的应用程序流中没有事件可以达到同样的目的)。
2) 子视图是否应该作为父视图的数据成员来维护?
这取决于。我不认为父母观点出于有限目的而意识到他们的孩子观点违反了 MVC 的任何黄金原则。有时,如果/当您需要时,具有对特定子视图实例的成员引用的父级是管理子视图的好方法。正如我所指出的,有时父视图会响应需要它们渲染、重新渲染、隐藏或删除其子视图的事件。有时他们可能想监听子视图自己触发的事件。然而,父母不应该过多地介入他们孩子观点中的任何事情el
。
也就是说,不要过度使用这些类型的引用。很多时候,您不需要使用对子视图的引用,因为子视图可以照顾自己。正如我所提到的,视图一旦被渲染,应该只 A) 在它们的 el 中监视 UI 事件(但通常不在任何子视图的 el 中)并更新模型或集合或触发事件以响应这些 UI 事件,或者 B) 监视来自其他主干对象(通常是模型或集合或其他视图)的事件并采取行动(例如,更新他们自己的 UI 元素)作为响应。在许多情况下,视图可以自行处理甚至删除自己。如果另一个 View 或其他 Backbone 对象关心您的视图中发生的 UI 事件,请更新模型或触发视图上的事件并让他们观察它。同样地,如果您视图之外的某些内容需要在您的视图中更新渲染,请侦听模型的更改或等待相应的自定义事件。作为一般原则,观点应该是幸福的,不知道彼此,除了父母在有意义的时候照顾他们的孩子。
3)子视图是否应该保持对父视图的引用?
没有永不。我想不出一个场景,您需要通过对父级的引用来完成某些事情,而这无法通过更改父级正在观察的模型或触发事件来完成(例如,自定义事件说“嘿,X发生了”)在子视图本身或另一个基于“事件”的主干对象上。在 Backbone 中,我使用模型来表示我的数据和我的状态。因此,如果某个视图中发生的事情改变了我的应用程序的状态,那么我会更改模型的相应状态属性,并让其他视图(包括父视图)在他们关心时监听自动“更改”事件。我还使用一个全局“vent”类总线对象(只是一个扩展 Backbone.Events 的基本 javascript 对象)来触发和侦听整个应用程序中的事件,有时我会触发 View 本身的事件,让父对象知道发生了什么事。任何可行的方法,同时保持你的架构尽可能的清晰。
4)我真的不希望 top 开始到处扔听众。
好吧,我想关于主干的一件好事是您不必这样做,但要意识到观察者模式(即事件和侦听器)和松散耦合是主干 MVC 风格的核心(请注意,每个 Backbone 类都扩展了事件?),并且大多数人都相应地使用它。
参考?
我强烈推荐PeepCode 教程,除非你觉得你已经处于相当高级的水平。12块钱一件,但你需要从第一个开始,否则第二个和第三个不会很有用。
另外,这里有一个很好的概述。
结束