软件架构之前后端分离与前端模块化发展史

在现行的软件架构中,前端和后端是分离的,即前端只专注于页面渲染,而后台专注于业务逻辑,前端和后端是两个不同的工种,而前后端交互最常见的方式就是通过接口。

前后端分离架构

在正式说明前后台架构分离之前,我们来看一下多年之前,传统软件开发的架构模式。

为什么要前后端分离

还记得零几年我上大学的时候,在初学 Java Web 开发时,课本上介绍的还是 JSP + Servlet 这种很传统的架构模式,这时候前端和后端业务逻辑代码都在一个工程里面,还没有分离开来,这种开发模式属于 Model1 模式,虽然实现了逻辑功能和显示功能的分离,但是由于视图层和控制层都是由 JSP 页面实现的,即视图层和控制层并没有实现分离。

随着学习的深入以及渐渐流行的企业应用开发,我们渐渐的摈弃这种技术选型,并开始在项目中使用了若干开源框架,常用的框架组合有 Spring +Struts/Spring MVC + Hibernate/Mybatis 等等,由于框架的优越性以及良好的封装性使得这套开发框架组合迅速成为各个企业开发中的不二之选,这些框架的出现也减少了开发者的重复编码工作,简化开发,加快开发进度,降低维护难度,随之而火热的是这套技术框架背后的开发模式,即 MVC 开发模式,它是为了克服 Model1 存在的不足而设计的。

MVC 的具体含义是:Model + View + Controller,即模型+视图+控制器,

  • Model 模型层: 它常常使用 JavaBean 来编写,它接受视图层请求的数据,然后进行相应的业务处理并返回最终的处理结果,它负担的责任最为核心,并利用 JavaBean 具有的特性实现了代码的重用和扩展以及给维护带来了方便。
  • View 视图层: 代表和用户交互的界面,负责数据的采集和展示,通常由 JSP 实现。
  • Controller 控制层: 控制层是从用户端接收请求,然后将请求传递给模型层并告诉模型层应该调用什么功能模块来处理该请求,它将协调视图层和模型层之间的工作,起到中间枢纽的作用,它一般交由 Servlet 来实现。

MVC的工作流程如下图所示。

浏览器端的 Web 应用、iOS 原生 App、安卓端原生 App、微信小程序等等,可能只是开发其中的一部分产品,但是除了 web 应用能够使用传统的 MVC 模式开发外,其他的都无法使用该模式进行开发,像原生 App 或者微信小程序都是通过调用 RESTful api 的方式与后端进行数据交互。

随着互联 技术的发展,更多的技术框架被提了出来,其中最革命性的就是前后端分离概念的提出。

什么是前后端分离

何为前后端分离,我认为应该从以下几个方面来理解。

前后端分离是一种项目开发模式

当业务变得越来越复杂或者产品线越来越多,原有的开发模式已经无法满足业务需求,当端上的产品越来越多,展现层的变化越来越快、越来越多,此时就应该进行前后端分离分层抽象,简化数据获取过程,比如目前比较常用的就是前端人员自行实现跳转逻辑和页面交互,后端只负责提供接口数据,二者之间通过调用 RESTful api 的方式来进行数据交互,如下图所示:

前端模块化发展历程

自 2009 年 5 月 Node.js 发布以来,前端能干的事情越来越多。短短 10 来年的时间,前端便从刀耕火种的年代走向了模块化、工程化的时代。各种前端框架百家争鸣,前端赢来了真正属于自己的时代。

原始时代

时间回到 2009年,记得那时候还没有流行前后端分离,很多项目还是混在一起,而那时候的前端开发人员大多数也都是“切图仔”。前端完成静态页面,由服务端同事完成数据的嵌入,也就是所谓的套页面操作,每当有类似的功能,都会回到之前的页面去复制粘贴,由于处于不同的页面,类名需要更换,但是换汤不换药。

久而久之,重复代码越来越多,但凡改动一个小的地方,都需要改动很多代码,显得极不方便,也不利于大规模的进行工程化开发。虽然市面上也慢慢出现了 Angular、 Avalon 等优秀的前端框架,但是考虑到 SEO 和维护人员并不好招,很多公司还是选择求稳,用套页面的形式制作 页,这对前端的工程化、模块化是一个不小的阻碍。

构建工具的出现

不过,随着 Node 被大力推崇,市面上涌现出大量的构建工具,如 Npm Scripts、Grunt、Gulp、FIS、Webpack、Rollup、Parcel等等。构建工具解放了我们的双手,帮我们处理一些重复的机械劳动。

举个简单的例子:我们用 ES6 写了一段代码,需要在浏览器执行。但是由于浏览器厂商对浏览器的更新非常保守,使得很多 ES6 的代码并不能直接在浏览器上运行。这个时候我们总不能手动将 ES6 代码改成 ES5 的代码。于是乎就有了下面的转换。

就是做了上述的操作,才能使得我们在写前端代码的时候,使用最新的 ECMAScript 语法,并且尽可能的压缩代码的体积,使得浏览器加载静态脚本时能更加快速。

传统的模块化

随着 Ajax 的流行,前端工程师能做的事情就不只是“切图” 这么简单,现在前端工程师能做的越来越多,开始出现了明确的分工,并且能够与服务端工程师进行数据联调。这里说的传统模块化还不是后现代的模块化,早期的模块化是不借助任何工具的,纯属由 JavaScript 完成代码的结构化。在传统的模块化中我们主要是将一些能够复用的代码抽成公共方法,以便统一维护和管理,比如下面代码。

但是,这种做法会衍生出两个很大的问题,一个是全局变量的污染,另一个是人工维护模块之间的依赖关系会造成代码的混乱。

例如,当我们的项目有十几个甚至几十个人维护的时候,难免会有人在公用组件中添加新的方法,比如 show 这个方法一旦被覆盖了,使用它的人会得到和预期不同的结果,这样就造成的全局变量的污染。另一个问题,因为真实项目中的公用脚本之间的依赖关系是比较复杂的,比如 c 脚本依赖 b 脚本,a 脚本依赖 b 脚本,那么我们在引入的时候就要注意必须要这样引入。

要这样引入才能保证 a 脚本的正常运行,否则就会 错。对于这类问题,我们该如何解决这样的问题呢p>

全局变量的污染

解决这个问题有两种,先说说治标不治本的方法,我们通过团队规范开发文档,比如说我有个方法,是在购物车模块中使用的,可以如下书写。

这样就能比较有效的避开全局变量的污染,把方法写到对象里,再通过对象去调用。专业术语上这叫命名空间的规范,但是这样模块多了变量名会比较累赘,一写就是一长串,所以我叫它治标不治本。

还有一种比较专业的方法技术通过立即执行函数完成闭包封装,为了解决封装内变量的问题,立即执行函数是个很好的办法,这也是早期很多开发正在使用的方式,如下所示。

上述代码,通过一个立即执行函数,给予了模块的独立作用域,同时通过全局变量配置了我们的模块,达到了模块化的目的。

当前的模块化方案

先来说说 CommonJS 规范,在 Node.JS 发布之后,CommonJS 模块化规范就被用在了项目开发中,它有几个概念给大家解释一下。

  • 每个文件都是一个模块,它都有属于自己的作用域,内部定义的变量、函数都是私有的,对外是不可见的;
  • 每个模块内部的 module 变量代表当前模块,这个变量是一个对象;
  • module 的 exports 属性是对外的接口,加载某个模块其实就是在加载模块的 module.exports 属性;
  • 使用 require 关键字加载对应的模块,require 的基本功能就是读入并执行一个 JavaScript 文件,然后返回改模块的 exports 对象,如果没有的话会 错的;

下面来看一下示例,我们就将上面提到过的代码通过 CommonJS 模块化。

除了 CommonJS 规范外,还有几个现在只能在老项目里才能看到的模块化模式,比如以 require.js 为代表的 AMD(Asynchronous Module Definition) 规范 和 玉伯团队写的 sea.js 为代表的 CMD(Common Module Definition) 规范。
AMD 的特点:是一步加载模块,但是前提是一开始就要将所有的依赖项加载完全。CMD 的特点是:依赖延迟,在需要的时候才去加载。

AMD

首先,我们来看一下如何通过 AMD 规范的 require.js 书写上述模块化代码。

require.js 定义了一个函数 define,它是全局变量,用来定义模块,它的语法规范如下:

define(id, dependencies, factory)

  • id:它是可选参数,用于标识模块;
  • dependencies:当前模块所依赖的模块名称数组,如上述模块依赖 home 模块,这就解决了之前说的模块之间依赖关系换乱的问题,通过这个参数可以将前置依赖模块加载进来;
  • factory:模块初始化要执行的函数或对象。

require([dependencies], function(){})

然后,在其他文件中使用 require 进行引入,第一个参数为需要依赖的模块数组,第二个参数为一个回调函数,当前面的依赖模块被加载成功之后,回调函数会被执行,加载进来的模块将会以参数的形式传入函数内,以便进行其他操作。

CMD

sea.js 和 require.js 解决的问题其实是一样的,只是运行的机制不同,遵循的是就近依赖,来看下使用CMD方式实现的模块化代码。

首先是引入 sea.js 库,定义和导出模块分别是 define() 和 exports,可以在定义模块的时候通过 require 参数手动引入需要依赖的模块,使用模块通过 seajs.use。

ES6

ES6 提出了最新的模块化方案,并且引入了类的机制,让 JavaScript 从早期的表单验证脚本语言摇身一变成了一个面向对象的语言了。ES6 的模块化使用的是 import/export 关键字来实现导入和导出,并且自动采用的是严格模式(use strict),考虑到都是运行在模块之中,所以 ES6 实际上把整个语言都升到了严格模式。

在 ES6 中每一个模块即是一个文件,在文件中定义变量、函数、对象在外部是无法获取的。如果想要获取模块内的内容,就必须使用 export 关键字来对其进行暴露。我们把之前的公用脚本用 ES6 的形式再重构一遍。

可以发现,ES6 的写法更加清晰。具备了面向对象和面向函数的特征,可读性更强。

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年7月19日
下一篇 2020年7月19日

相关推荐