单页应用详解 ——(1)现代web应用,概述

单页应用详解(Single page apps in depth

(1).现代web应用,概述(Modern web applications: a overview)

英文原文:http://singlepageappbook.com/goal.html

我们为什么要做单页应用?主要的原因还在于它可以让用户感觉使用本地应用一样的体验。

而通过其它方式可能比较难实现。在一个页面里使用多种组件提供丰富的交互行为,那就意味着这些组件有很多中间状态(比如菜单的打开,菜单项X被选中,菜单项Y被选中,菜单项被单击)。服务器端的渲染就比较难实现这些中间状态——微小的视图状态在URL中不太好映射。

单页应用在不与服务端通讯获取HTML的情况下就有非常的好的能力去重新绘制UI的任何部分。将一个应用分离成数据层,处理数据的模型层,从模型层读取数据的视图层,这三个层次来实现。

大多项目一开始就抱有极大的野心,而且对手头上的事情还未完全理解。我们的实现往往超出我们的理解。可能我们还没有完全理解清楚问题就开始写代码,所以通常代码要比十几需要的复杂的多,因为我们缺乏对问题的理解。

好的代码是在多次解决同一个问题中或者重构中得出来的。通常,这得益于注意到循环模式,并且用一种可以处理同样事情的一贯方式去替换它们——替换很多特例代码(case-specific code),事实上我们没有看到一个简单的机制可以做到同样事情。

(相当蛋疼啊,这几段貌似跑题啊~~~,哎,英语不太好,我看了都要吐血了。。。。)


单页应用中使用的架构体现了下面这个过程的结果:

如果你要用jQuery这种特定的方式做开发,你就会按这种标准的机制去写代码(比如UI的更新等等)

程序员们都比较痴迷轻松,而不是简单(非常感谢Rich Hickey指出这点),或者说更着迷变成的体验而不是程序的结果。这就导致没有必要去讨论分号,或者是否需要一个预处理器去消除大括号。我们还在讨论编程,好像编写代码是 最困难的一部分一样。并非如此——维护代码才是难的。

要编写可维护的代码,我们必须保持事情的简单。这是一个永恒不变的架构,为了解决一个不值得的问题去增加代码的复杂性(过多交叉和依赖)很简单,用一种不考虑降低复杂度的方式去解决一个问题也很简单。后面提到的命名空间就是一个例子。

考虑到这点,我们来看看当代web应用的结构和下面这三种观点:

架构:应用由哪些部分组成?不同的部分如何通讯?如何处理相互间的依赖?

资源打包:我们的应用如何组织成文件?文件如何映射到逻辑的模块?模块式如何加载到浏览器的?模块又是如何为单元测试载入?

运行时状态:当应用载入到浏览器时,哪些部分是在内存中?我们如何在状态之间执行转换,并且如何查看当前状态以便于调试?


一个现代web应用的架构

当代单页应用的通常架构:


更多特性:

只写入DOM。没有任何状态和数据时从DOM中读取的。在应用中输出HTML并且操作html元素,但是不从DOM读取任何东西。把状态保存到DOM中将很快导致难以管理。更好的方式是在一个地方统一保存数据,构建UI的时候读取数据渲染出来,尤其是当同一份数据会在不同的UI部分展现时。

将模型(models)作为单一的真实数据源。在内存中用一系列的模型来表示应用中所有的状态或者数据,而不是把数据保存在DM或者随机的对象上。

视图(views)观察模型(models)的变化。view应该反映model的内容。当多个view依赖一个model(比如,当一个模型改变时,需要重绘这些视图)时,我们不想手工的去跟踪每个依赖的视图。所以应该有个change的事件机制贯穿整个view,可以接收到model change的消息通知,然后自己处理重绘。

小的外部扩展可以解耦模块。为了不污染全局环境,我们应该创建小的没有互相依赖的子系统。依赖会导致代码变得难以测试。细小的外部扩展可以使内部重构变得容易,因为很多东西可以改变,只要外部接口保持一致。

最小化DOM依赖代码。为什么?任何依赖DOM的代码都需要测试浏览器的兼容性。通过同一种方式去处理这些烦人的问题,将会使有限的一部分代码需要做浏览器兼容测试。不兼容性问题是DOM的实现原因,并不是javascript的实现原因,所以尽量最小化编写依赖DOM实现的代码。


控制器(controllers)必须灭亡

在上面这个图中我不使用控制器的原因是,我不喜欢这个词,所以你将不会看我在这本书中用这个词。原因很简单:它仅仅是我们通常所讲的在服务器端“MVC”模式带到单页应用中的一个占位词。

大部分当前的单页应用框架任然使用“控制器”,但我发现它没什么意义,除了“把胶水代码放这”之外。我们来看下它的解释:

“控制器处理给DOM添加事件和响应事件,渲染模板,保持视图和模型的同步”。

什么?或许我们可以分开来看这些问题?

单页应用需要一个更好的词,因为它比服务器端的应用有更多复杂的状态转换:

  • 有DOM事件会出发views的细小的状态变化

  • 有model的事件当model的值被改变时

  • 视图切换时有应用的状态(application state)改变

  • 在实时应用中会有全局的状态改变,比如下线

  • 有些后台操作会有延迟的ajax请求响应

这些东西都要通过某种方式交杂在一起,而用这”控制器“这个词来描述协调这些东西显得非常的贫乏。


我们非常清晰的需要一个model来处理数据,一个view来处理UI的变化,但是连接层由几个独立的问题组成。知道了那些有控制器的的框架没有告诉你任何如何解决这些问题的信息,所以我希望鼓励大家使用更多的具体的说法。

这就是为什么这本书没有关于控制器的章节。但是我通过视图层和模型层来解决这些问题。这里的解决方式有它们自己的名词,比如事件绑定(event bindings),change事件,初始化(initializers)等等。


资源打包(或者详细的说,为浏览器做代码打包)

资源打包,就是将js应用程序代码存放在一个或多个(包)可通过script标签载入的文件里。

貌似没有人强调如何把这件事做好是多么的关键。资源打包不是让load的时间有多快,而是让程序模块化,避免变得不可测试和混乱。然后,人们常以为它是关于性能的问题,所以可做可不做。

如果其中的某一部分影响了你的代码的可测试性和可重构性,你将如何把你的代码分割成模块和执行模块架构。“资源打包”就是拆分成模块,控制运行时的状态不至于变得混乱。对比下下面的方式:

混乱和随意(没有模块)

  • 任何碎片都是在全局环境下

  • 变量名是全局的

  • 完全便利命名空间

  • 加载顺序很重要,因为任何部分都可以被覆盖或改变其它部分

  • 隐式的依赖于全局环境

  • 文件和模块之间没有什么有意的关联

  • 只能运行于浏览器中,因为依赖都是不独立的。

打包和模块(模块化)

  • 每个包(package)会暴露出一个公开的接口

  • 变量在包里面是局部的

  • 实现细节对包外面是不可访问的

  • 因为有了打包的封装,可以不用关心加载先后顺序

  • 显式的申明依赖关系

  • 每个文件包含一个模块

  • 可以脱离浏览器在命令行执行

默认将一个js文件暴露在全局命名空间下,并且期望结果运行的非常好是非常糟糕的做法,会让单元测试、重构变得更加困难。非常糟糕的模块化导致依赖全局域环境和全局的变量,很难建立起测试

另外,隐式的依赖在重构的时候很难知道你的代码依赖于哪些模块,你基本上靠其他人遵循一些好的约定(不要依赖我认为是内部细节的部分)。显式申明使用公开的接口,这就意味着重构时的痛苦要少的多,因为别人只依赖你暴露的接口。同时也鼓励改进公开接口和更多的内部实现。在可维护性和模块化的章节会讲述更多关于如何去做的细节。


运行时状态

看待现代单页应用的第三种方式就是去看它的运行时状态。运行时状态指的是,应用程序在浏览器运行时看起来是什么样的——变量保存的是什么信息,从一个活动到另一个活动会有哪些步骤。

这里有三个有趣的关系:

URL<->状态。单页应用跟url之间有一种精神分裂般的关系。一方面,单页应用的存在时为了让用户有更丰富的交互体验。更多的交互就意味着在同一个URL里有更多的视图状态。另一方面,我们又要记录URL,可以后退到到上一个交互行为。

为了能支持书签,在某些URL中我们可能需要降低支持的细节程度。如果每个页面都有一个主要的活动(activity)(在URL中的某部分细节来标识),这样页面就可以从书签中重新打开,一定程度上展现出原来的界面。一些次要的活动(比如,webmail应用中的聊天)恢复到载入时的默认状态,因为它们保存到书签中也没有多大意义。

定义<->初始化。很多朋友仍把这2个东西混在一起,这是不好的。可重用的组件应该被定义成未被实例化的,这样就可以重用和测试。否则的话我们如何执行应用的各种状态的初始化和实例化?

我认为有3种通常的方法:1.每个模块定义一个function接受一些输入(比如id),初始化对应的视图和对象;2.有一个全局的启动文件,通过路由从所有全局的状态中载入对应的状态;3.把所有东西包裹的像糖一样,让初始化顺序变得无形。

我喜欢第一种方式,第二种方式常见于围绕一个点有机地增长的应用。第三种方式常见于框架中,特别是涉及视图层的。

我喜欢第一种方式的原因是,我认为状态(比如对象的实例和变量)是比较讨厌的,应该隔离在一个文件中(每个子系统的状态应该是在局部的,而不是全局的,后面会更详细讲述)。纯数据是非常简单的,定义也是一样的。当我们有很多依赖时或者当事情变得复杂难以看清状态时,就难以分析原因并且不愉快。

第一种方式的另一个好处就是,每个页面重新载入时不需要把整个应用重新载入。每个活动的初始化都靠自身完成,你可以单独测试app的一部分而不用载入真个app。同样,你可以更灵活的在初始视图激活之后预载入app的剩余部分。这也就意味着初始载入时间不会随着模块的增多而变长。

HTML元素 <->view对象,HTML events <->view变化

最后,有这样一个问题,我们能看清多少我们所使用的框架在运行时的状态。我还没见过有框架能明确的解决这个问题(当然,尽管还是有窍门)。当我运行应用的时候,通过选择特定的html元素,我能知道发生了什么吗?当我看到某个HTML元素,点击它或者做其它事件时我能知道将会发生什么吗?

简单的实现方式就是更好的实现方式,因为从一个HTML元素或者事件到视图对象或者事件的处理器之间传播的距离更短。我希望框架会更注重这方面信息。


这仅仅是个开始

总的来说,从三个角度分析了下,架构师的角度,文件系统的角度,浏览器的角度。


【个人认为这篇文章写的条例不是很清楚,有些部分太罗嗦了,对单页应用和前端MVC没有一些基本概念和知识貌似还比较难看到,没有达到初级的overview的效果。。各位看官凑合看下吧。。。】


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值