Marionette.js的历史舞台
本文略译自《The Case For Marionette.js》
Backbone.js不是一个完整的应用框架,用它来开发「大型前端应用」是不容易的。 Backbone 的设计思想是很棒的,它的极简设计提供极大的自由和灵活性,能满足各种开发场景,然而这种极简设计涉及的面变得很宽而没有针对性,结果,当你用Backbone开发大型应用时,相对于 Angular 或 Ember 这样的"不简单不灵活的"应用框架(opinionated frameworks),反而失去了效率上的优势。因为大型应用固有的很多任务和开发困难(具体什么困难?[注]),你都得自己(自由)判断解决,要么给 Backbone 添加第三方插件,要么自己实现。
注:嵌套的V,复合的M等,当应用scale up,需更多「构件」——更粗粒度的工作对象——提高开发效率,和降低维护的成本
Backbone 要用来开发大型应用,需要引入更多的构件工具,或完整的应用框架,本文介绍最流行的一支 Marionette.js
Why Marionette?
在过去的一年里,我一直在使用 Marionette,它非常有价值。Marionette引入很多粗粒度构件,真的对Backbone应用「规模增大的问题」有帮助。不过,在一开始使用它时,我还没有明白(相信很多初学者也一样),使用Marionette的「确切理由」是什么。
从故事中看价值
当我开始使用一款新工具软件时,我总是希望读到两种类型的文档。
- 第一种,是官方详细文档,例如有哪些api可用(可以传递给它们什么参数),它提供了哪些组件、特性或函数;
- 第二种类型,是我称为的“讲故事”的文章;
“讲故事”由于比较生动具体,所以它很容易让人明白新工具的「实际用途和价值」,对你理解新工具背后的原理,和使用方法也会有比较深刻的认识。
有了故事,我们才对技术产生了兴趣。有了工具技术文档,我们才可以将故事变成真实;
EM:第一种就是「工具」特性的描述,第二种就是「任务」的描述。
EM:我们是在学习一种工具,而事实上,工具和故事任务是一体两面,所以,通过工具可以间接是对故事任务有进一步的认识。
Marionette 很棒,可是这个工具缺少第二种关于“故事”的文章。官方没有,社区也很少,本文及后断系列尝试补充这方面的内容。
Marionette是什么?
我们先从官方文档的定义开始,看以下这个描述:
Backbone.Marionette is a composite application library for Backbone.js that aims to simplify the construction of large scale JavaScript applications.
Marionette 是一支扩展了Backbone.js功能的应用框架/代码库,由一集高级类对象结构组成,它的目标是为了简化开发大型JS应用。Marionette设计的背后结集了多种「应用开发的设计和实现模式」,这主要在使用Backbone过程中总结的,包括应用的对象组合架构(composite application architectures)、对象互讯的事件驱动架构(event-driven architectures)、统一的消息模式(messaging architectures)等。
Marionette提供是一集设计良好的工具,这些工具建造过程的原理和哲学,在网站上并没有记录下来。所以新用户并没有很好使用工具的指导。我在这里和后续的文章里,尝试提供这种高层的工具价值,和使用指导方面的信息。
要更好认识和使用Marionette,首先要做的,是认识到它针对的前端构架师要解决的问题。
JS APP构架师的设计任务
开发或制作「JS应用」有一个作决定(decision making)的「侧面」,只是使用 Backbone的话,你自己做决策的机会更多。Backbone为我们提供最基本MVC结构构件的决策,例如:
- 第一,将Ajax访问网络和业务数据相关的逻辑包装成 Models and Collections;
- 第二,将操作DOM包装一层很簿的View;
- 第三,将程序交互会话的状态包装成Router和URL;
- 第四,将各应用程序对象的异步调用包装成Event;
除此之外,留给了 Backbone开发者大量的设计空间,和问题[注]。问题和任务主要(不仅)有如下:
注:以下是有待“武断”的重要设计决策问题,在我看来,有人非议武断证明此领域(前端架构设计)尚不成熟发展,我以为可能存在一种科学的“武断”的架构设计。
你怎么渲染你的Views?
Backbone的V基类默认是没有渲染逻辑的,渲染的内容和方式你都要自己决定。例如,你有三种渲染方式[注]:templating system,jQuery操作DOM,或者直接.innerHTML()
将HTML字符输出。这些渲染方式你可以用一种,或者混着用,都可以。
注:其实这里的“渲染”是一种假渲染,因并不是真的在屏幕上绘制,只是以某种方式通知浏览器,例如使用JQuery或BOM API制作DOM元素,再挂接上DOM树,挂接技术细节待研究。
你怎么管理/控制程序对象之间的关系(你怎么制作复杂的程序对象)
Backbone提供一些管理「线性关系」的工具,例如提供了 Collection对象管理多个Models对象,但是,对于复杂的对象关系,你得自己想办法解决。例如嵌套的Models 或 Collections,嵌套的 View等。(EM:为什么会有复杂的对象,这个问题有趣)要实现这结复杂的程序对象,必须在Backbone之上,创建额外的管理逻辑。
你的 View 之间是怎么通信的?
View之间常常需要互相通信(why ?什么应用需求?)。例如,一个view需要操作页面上另一区域的内容时,它可以,第一,直接使用jQuery;第二,通过调用该区域所属的view的某个方法;第三,通过修改所属的view的model;第四,通过Router;第五,通过异步事件。以上都可以实现的,选用哪个呢?
你如何避免重复造轮子?
如果你没有经验,使用Backbone很容易重造轮子,写很多不断重复的代码(boilerplate code)。例如,当你制作view时,你会不断重复写「渲染逻辑」的代码、「管理逻辑」的代码和「事件逻辑」的代码。你可能希望尝试通过对象继承(inheritance),来解决这些重复代码问题,然而你最终的设计可能是脆弱(brittle)的,因为你每次想一调用一定特定代码,你都得访问View的原型。既要避免重造轮子,又要降低额外维护(复杂的继承)的开销,是一项挑战。
你如何管理View对象创建和销毁(生命周期)?
在整MVC结构里,V是最复杂的。有一个很重要问题,就是View的渲染。应该怎样安排View渲染才是最合理的呢?例如,谁负责渲染 View 界面(调用V对象render方法)?是创建它时它自己主动渲染吗?还是创建它的那个对象帮它渲染?这是一个问题,第二个问题是,渲染时是直接就挂接上DOM吗?还是分开另一步?第三个问题,当View从DOM卸载,甚至是整个删除,怎么清理内存(如果有必要)?
你如何规划应用程序的整个运行结构?
你的应用程序是怎样启动的?你要有一个核心的对象以中心式来启动所有对象吗?还是是一种分散式(distributed)结构?如果是中心式的,你是使用一个router来启动程序任务,还是其它特定的对象(some other object)来管理程序的运行?
你如何防止内存泄漏?
如果你要开发的是一单页应用程序(Single Page Application)或者你的应用有一持久交互会话的部分,你极有可能要面对「内存泄漏」问题。Backbone的使用不慎很容易产生“僵尸Views”,因为你在卸掉的一无用的View时很容易忽视一同卸载(unregister)创建时安装的事件处理的引用(V监听M变动的事件时,M会引用V而一直存在在内存中)。
以上只是部分比较典型常见的设计问题,需要你自由决策,事实上还有更多。自由意味着代价,如果你和我一样,一定也希望且认为社区有现成的设计经验和解决方案,我们可以借鉴。
构架决策Marionette能给我们什么?
Marionette 就是这种社区方案之一。Marionette 在实践中总结了一集高级对象和设计模式。下面我们看看Marionette具体为我们带来什么价值:
View的渲染
Marionette 在Backbone之上,将View基类加得很“厚”,用户不能重载Marionette.View的Render方法,只需通过提供Option的template,model等键值即可完成View实例的创建。简言之,Marionette.View不需手动Render,它是自动的。在内部实现上,Marionette使用Underscore的模板引擎,如果想定制,Marionette提供了View基类的生命事件勾子(life cycle events hooks)
View基类的生命事件
Marionette为 View 基类定义了一系生命事件,包括initialized, rendered, shown, refreshed, 和 destroyed。为了让用户能更 DRY 地使用View基类,使用View基类制作复杂交互功能(静态结构的动态时序上的),Marionette为 View 基类抽象掉的大量重复代码(boilerplate code),并定义了View“生命过程”的各个重要阶段,提供了生命事件以及事件勾子(一种类似虚函数,可重载)给用户进行定制。
嵌套的View和页面布局
Marionette提供一个通用的管理View实例的region对象,使用region对象,用户可以轻易的实现Views的嵌套(无深度限制),不用担心 swap out 子Views而产生内存问题;另外,可自动管理View存毁,region对象可用来创建页面布局。
高层的对象通信总线
Marionette可以使用 Backbone.Radio 作为通信中介,在多个Views间进通信,这样能降低它们的耦合度。Backbone.Radio是一种通用的富语义的通信机制。
开发复杂交互View 的辅助工具类
Marionette对 View 除了提供一个高级 V 层,还提供了进一步简化开发复杂 View 的辅助工具类——Marionette.Behavior。Behavior能对View高级交互部分的重复代码进行抽象,进一步把View类代码变得更加DRY;
防止僵尸或内存泄漏的设计
Marionette提供所有高级类对象设计了显式的销毁的生命事件(destroy phase),为了的尽可能扫除产生内存泄漏的可能性,销毁的生命事件勾子预留给用户进行自定的额外内存清理;
独立的Application对象表征应用程序主体
View需要被Page布局和管理,Page由Router导向,而的它们都是由 Application 触发生命和管理的。Application统一管理应用启动和运行所需的一切条件。
以上是一个不完全列表,但是是Marionette的核心特性。我会在后续文章分别深入介绍它们。