3.2 前端开发模式
3.2.1 抽象DOM模式
AJAX技术为前端提供了局部刷新技术,使业界更加注重用户的交互,因此出现了各种对DOM进行操作的JS框架。相比JavaScript原生API,这些框架提供了丰富的DOM选择器,封装了更加方便的DOM操作和AJAX交互操作接口,屏蔽了各种浏览器的差异。典型代表如jQuery。DOM交互框架使JS的编写变得简单,生产力得到了极大的提高。
早期的JS框架以满足PC端需求为主要工作目标,移动互联网快速发展,前端需要适应各种屏幕分辨率、满足各种手势操作的需求,对前端的要求越来越高。早期JS框架主要解决了DOM交互的问题,当前端功能较多时,从后端获取的数据和HTML展示与用户操作相关的代码混合在一起,层次不清,较难开发和维护。
3.2.2 MVC模式
参照后端的主流架构模式,在前端也出现了MVC模式,即前端分为数据层(Model)、视图层(View)、控制器层(Controller)。图3-1表示了前端MVC模式与服务端的关系。从前端到服务端的整个架构看,主要的业务处理逻辑依然在服务端处理,前端主要负责展示。前端的MVC架构是对服务端View层次在浏览器或客户端上的处理逻辑的进一步细化。
图3-1 前端MVC模式与服务端
在前端自有的MVC架构中,View层包括了页面展示和事件处理,Model层主要负责前端数据处理,包括数据格式处理、与服务端的网络通信等。而Controller主要负责View与Model的耦合,处理业务逻辑。
3.2.3 MVP模式
在前端的MVC模式中,View层功能内容较多,可以直接与Model层交互。如对输入框复制等功能是可以直接调用Model层数据的。
在MVP模式中,使用Presenter替代了Controller。MVP与MVC的本质区别是:在MVP中,View并不直接使用Model,它们之间的通信是通过Presenter(对应MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过Controller。前端MVC模式是一个相互连接的三角形结构,而MVP模式是一个上下垂直的层次结构。前端的MVP模式的层次关系与服务端的MVC模式更加相似。MVP模式剥离了视图与状态的关系,使视图从特定的业务场景中抽离。MVC模式与MVP模式的区别如图3-2所示。
图3-2 前端MVC与MVP模式
3.2.4 MVVM模式
MVVM模式(Model-View-ViewModel)是MVP模式的进一步改进,其中的Model和View与MVP模式一致,ViewModel是一个同步View和Model的对象。
MVVM的核心思想是数据双向绑定(data-binding),View和Model之间通过ViewModel进行交互,没有联系。交互是双向的,View数据的变化会同步到Model中,而Model数据的变化也会反映到View上。交互还是自动的,无需人为干涉,View与Model之间的双向状态维护由ViewModel来统一管理。
图3-3清晰地表达了MVVM模式的结构。从View侧看,ViewModel中的DOM Listeners工具会监测页面上DOM元素的变化,并将变化传递给Model,同步更改Model数据;从Model侧看,当更新Model中的数据时,Data Bindings工具会更新页面中的DOM元素。
图3-3 Vue的MVVM模式
相比MVC或MVP模式,MVVM要实现自动双向绑定对框架要求更高,封装更深。MVVM框架通过HTML自定义属性与Model建立对应关系,如<input epub:type="text"v-model="message">与model中的“message”变量相对应。通过劫持Model数据的get和set方法,当Model发生变更时Data Bindings工具会扫描DOM树中与Model相对应的元素,更新元素的innerHTML。反之,DOM Listeners会监听HTML元素的change、keyup、select等事件,通过事件触发改变HTML元素对应的Model数据。
前端主流JS框架中VUE与AngularJS都实现了MVVM模式。
在MVVM模式下,页面的变化由数据驱动,反之用户操作又会驱动数据变化,框架自动管理交互操作,解放了大量的内容渲染和事件绑定的DOM操作逻辑,使开发人员更加关注业务逻辑。
对比Web应用、App原生应用以及后端应用的MV*架构,会发现它们在实现中有很多差异。前端应用中主要是处理用户交互,所以前端架构中Vi e w层较重。同时编程语言不同,功能分工也有所差异,如Android原生开发的View层含有很多事件处理程序,类似于Spring MVC的Controller层,而JS页面的MVC功能分工与后端更加相似。从MVC到MVVM,更多的是掌握一种理念和思路,在前端架构中很难完全满足View层不直接访问Model层的要求。
3.2.5 Virtual DOM
Virtual DOM是一种用自定义的JavaScript对象代替DOM树的结构。
使用Virtual DOM更新页面时,首先用Virtual DOM树构建一个真正的DOM结构。当DOM文档状态变化时,会新建一棵Virtual DOM树,并将新树与旧树进行对比,记录两棵树的差异。最后将对比后的差异修改到真实的DOM树上。Virtual DOM本质是用户JS与DOM之间的缓冲,采用了DocumentFragment的机制,Virtual DOM记录下多次DOM操作,一次提交,避免多次渲染。基于Virtual DOM的页面更新过程如图3-4所示。
图3-4 基于Virtual Dom的页面更新
真正的DOM对象有非常庞大的结构,而自定义的JavaScript对象较为简单,只需要具有节点类型tag、属性attributes和子节点children就可以。每次真正的DOM操作都会导致浏览器进行页面渲染,这是一个非常繁重的工作。DocumentFragment对象是标准语法,与document级别相同。JS构造DocumentFragment时不影响Document,不会进行页面渲染。当DocumentFragment节点全部构造完成后,再将DocumentFragment对象添加到Document中,所有的节点都会一次性渲染出来,避免了逐个修改Document导致的浏览器反复渲染,减少浏览器负担,提高页面渲染速度。
分析记录两棵DOM树差异的过程也叫DOM Dif,是Virtual DOM的核心。大量基于Virtual DOM的应用证实了DOM Dif算法是高效的。React与Vue框架都以Virtual DOM为核心,提高页面渲染效率。Virtual DOM将用户操作与页面渲染隔离,有利于构建跨端的应用,ReactNative和Weex就是采用这种模式。
3.2.6 组件化编程
在CS架构流行时,Delphi以强劲的组件开发能力受到追捧。Delphi等工具提供了丰富的组件,极大地简化了开发工作,推动CS模式的开发达到了顶峰。
组件化可以将一个庞大且复杂的UI场景分解成几个小的部分,这些小的部分彼此之间互不干扰,单独开发,单独维护,任意组合。组件化将复杂的逻辑封装在内部,大幅度地提高了代码的复用性,开发者们不需要再面对复杂且难懂的代码,只需要关注组件的组合和使用,同时也降低了维护成本。
Web前端的组件化最开始还是从服务端做起。为了适应服务端的MVC框架,在JSP上使用了Struts标签等服务端模板,模板经框架解析后在浏览器上展示出HTML。AJAX流行后,各种DOM操作框架百花齐放,很多框架将HTML、CSS和DOM操作封装,出现了真正的前端框架,典型如easyUI、DOJO等框架。移动互联网时代,无论是App原生应用还是Web开发都将组件化编程作为重要手段。Web开发中,React、AngularJS、Vue等主流框架都以组件化作为前端开发的核心,大量第三方机构也封装了灵活、美观的第三方组件,如:AntDesign、ElementUI等。新一代的UI组件以MVVM、Virtual DOM、预编译的CSS、扩展HTML元素、DOM操作为基础。
组件(Component)是Vue.js最强大的功能之一。图3-5是Vue官网的组件化相关说明图。
图3-5 Vue中的页面组件
在Vue中,可以通过嵌套的组件树构造应用,如定制页头、侧边栏、内容区等父组件,父组件中可以包含导航链接、博文内容之类的子组件。通过前端的组件技术,可以使用独立可复用的小组件来构建各种大型应用。
图3-6是一个基于组件的前端架构。组件架构的主要技术这里不再一一介绍,在主流前端框架中对这些技术进行了封装,提供了组件编程的基础技术体系。在基础组件这一层,有非常多的第三方开源产品,如ElementUI就是一套第三方开发的基于Vue的UI组件库,形成了布局、表单、导航、对话框等常用的基础UI组件。通过对基础UI组件的封装,可以形成业务组件,如登录、支付、分享、点赞、评论、搜索等。通过这些业务组件再组装成应用界面,几乎任意类型的应用的界面都可以抽象为这样一个组件树。
图3-6 基于组件的前端架构