规整化
前面已经说明了规整化前端的必要性。
规整化的目的,不是让代码像精细雕刻的工艺品一样,而是通过限制开发过程,让混乱限制在一定范围之内,从而保证软件质量和后续维护升级的成本。
而前端规整化的手段,主要包括编码规范、JavaScript库、组件工具箱和框架这4个方面。
本节将介绍这4个方面的细节。在项目前期,一般也是通过这4个方面来初始化前端工程的。
注意:规整化需要把握一个度,标准太高会拖慢项目进度,标准太低又达不到规整的目的,所以前端架构需要根据实际的团队水平和项目周期制定规整化的标准。
需要特别强调的是,很多人一提到前端架构,总会陷入“原生开发还是使用脚手架”“Vue、React、Angular应该选择哪一个”的苦恼之中。其实,没有任何的基础技术能保证最终的软件质量。而架构设计的目的,是想要保证最终的软件质量。相对于选择什么基础技术,制定规范化的标准才是架构设计更应该考虑的事情。
编码规范
制定编码规范是最简单且最有效的整顿办法,只需要开发团队遵守一些规则就能很大程度上避免混乱。这里先不考虑使用框架的情况,只是单纯地从网页开发方面介绍一下编码规范化的几个参考点:
- 规范化目录结构;
- 规范化前端资源文件使用,限制和分离HTML、CSS和JavaScript文件;
- 抽离通用部分,建立共用的JavaScript脚本和CSS主题文件;
- 规范化第三方插件的使用;
- 其他方面。
注意:虽然这里以原生网页开发作为基础,但是规整化需要注意的点是相似的。另外,以下的具体规则不是唯一,读者需要根据实际情况斟酌参考。
1.规范化目录结构
一般的前端目录结构都是按JavaScript文件、CSS文件、HTML文件及资源文件分离的,如图3.24所示。
图3.24 一般的前端目录
但是这样的前端目录结构不适合大型网站,因为大型网站有很多网页,也有很多网页资源,如果还是按这种目录结构组织网页资源的话,那么前端的目录结构其实还是混乱的。试想一下,在图3.24中,HTML文件有几十甚至上百个,伴随的网页资源也有几百个,这种“几十甚至上百个文件的罗列”无论在开发上还是网页发布上,都极有可能造成不必要的人为失误。
因此,在图3.24所示的结构上层,最好再增加一层结构。增加一层结构后,能大大降低每个子目录的文件数,可以把每个目录的文件数控制在十几二十个左右,从而规整前端结构。如图3.25所示,根据用户角色,增加了user(普通用户)和admin(管理员)的网页划分。需要注意的是,网页的划分更多是以业务架构的子模块为基础的,需要根据实际情况而定。
增加结构后,在浏览器中输入网址或IP地址后无法显示默认网页,这时需要设置一下默认网页。设置默认网页的方法有两种,第一种是新增一个index.html文件,做强制跳转;第二种是更改Web服务器的配置。下面以3.1.2小节中的网页为例,介绍设置默认网页的方法。
第一种方法是重定向,强制跳转。由于Web服务器的默认配置是打开根目录下的index.html文件,所以我们在根目录下新建index.html文件,通过index.html文件自动跳转到指定的新网页。index.html文件的内容如代码3.12所示,其中/sample/abc.html为要设定的默认网页。
代码3.12 重定向使用的index.html文件代码
<head>
<meta http-equiv="refresh" content="0;url=/sample/abc.html">
</head>
图3.25 增加一层目录结构后的前端目录
第二种方法是更改Web服务器的配置。以Nginx为例,修改conf/nginx.conf中的配置即可,修改后的配置如代码3.13所示,其中/sample/abc.html为要设定的默认网页。
代码3.13 Nginx默认网页的配置
…
location / {
…
index /sample/abc.html; #网站默认网页。
…
}
…
对于默认网页的设置,一般倾向于使用第二种设置方法,这是因为第一种配置方式会增加一次不必要的HTTP请求,而且由于发生了跳转,默认网页的地址显得不够简洁,如图3.26所示。
图3.26 第一种方法和第二种方法的默认网页地址栏对比
不过,虽然第二种方法的配置能让默认网页的地址保持简洁,但是HTML文件引用其他网页资源时,需要把相对地址改成绝对地址,如代码3.14所示。这是因为浏览器都是以完整的URL请求网页资源的,所以在修改之前,浏览器会请求 http://localhost/css_1.css,而css_2.css的正确URL是http://localhost/sample/css_1.css。
代码3.14 把HTML文件中的资源引用地址改成绝对地址
…
<link rel="stylesheet" href="css_1.css">
<link rel="stylesheet" href="css_2.css">
…
改成
…
<link rel="stylesheet" href="/sample/css_1.css">
<link rel="stylesheet" href="/sample/css_2.css">
…
说明:一般的默认网页为/index.html(网站根目录下的index.html),在只输入网址或IP地址的情况下会自动打开index.html网页,这是Web服务器的配置决定的,详见3.1.1小节中的介绍。
2.规范化前端资源文件使用
为了方便上传,网上许多例子通常会把代码只写在一个HTML文件中(即网页、JavaScript和CSS内容写在一个文件),而开发人员可能会为了方便,直接复制、粘贴代码;有时候开发人员也会为了方便,直接在HTML标签上使用style属性来配置样式;有时候开发人员为了使用别人写好的样式和函数,会引用别人写好的CSS和JavaScript文件。以上这些做法都会使工程代码变得混乱。
为了规整这样的混乱,我们需要对HTML、CSS、JavaScript等网页资源文件作限制和分离。对于一般的网页而言,除去一些共用的CSS文件和JavaScript文件外,限定每个网页只有一个HTML文件、一个CSS文件和一个JavaScript文件,不能引用其他网页的CSS和JavaScript文件,如图3.27所示。HTML文件内只能写网页结构;全部样式设置都写在CSS文件中,禁止在HTML标签中使用style属性;所有JavaScript代码都写在JavaScript文件中。
这种限制的好处是保证了每个网页的独立性,可以避免一些命名冲突等小问题,也明确了HTML文件、CSS文件和JavaScript文件的分工。
注意:这种限定是对于一般的网页而言的。如果网页过于复杂,那么也可以建立多个CSS文件和JavaScript文件。
3.抽离通用部分
不可避免的,有一些JavaScript代码是多个网页都需要用到的,一个网站的主题也是统一的(如字体大小、网页配色等CSS样式)。因此,我们需要进一步抽离出这些共用的JavaScript代码和CSS文件,在工程结构中添加独立的common和theme文件夹,如图3.28所示。
图3.27 限制每个网页的网页资源
图3.28 添加共用的JavaScript和CSS文件
common文件夹中保存网页共用的JavaScript文件,在文件中添加一些共用的函数,每个网页都可以调用。函数名尽量以统一前缀命名(如Common_),这样可以在翻看代码时一眼就能认出是共用的函数。
theme文件夹中保存一些网页共同的CSS主题文件,在文件中添加一些如配色、字体、字号等配置,其中主题样式的命名尽量以统一前缀命名(如Theme_),这样可以在各个网页代码看出是主题的样式。
注意:最好每个前端子模块都有一个theme文件夹和common文件夹,虽然这么做一定程度上会造成冗余,但是这样可以保证每个前端子模块是完整且独立的。除了theme文件夹和common文件夹,一些网站还有字体和多语言的要求,那么可以另外再建立font文件夹和language文件夹来存放相关文件。
4.规范化第三方插件的使用
虽然过度依赖第三方插件不是一个好习惯,但是合理地使用一些比较稳定的第三方插件能大大降低开发的工作量。这些第三方插件一般包含CSS文件和JavaScript文件,我们不能把这些文件直接放到js和css文件夹中,需要把这些第三方插件放到另外的lib文件夹中。这么做是为了独立出第三方插件,从而可以统一对这些第三方插件进行管理。
图3.29 第三方插件引用
注意:一些比较流行的第三方插件可能会提供一个URL方便开发者引用,如<scriptsrc="
https://cdn.staticfile.org/angular.js/1.4.6/angular.min.js"></script>。这样确实很方便,但是为了网站自身的稳定,最好还是把文件下载下来,放到服务器上。
5.其他
除了上面提到的几个点,还有很多细节需要考虑,如命名规则、JavaScript编码规范、禁止以同步方式使用Ajax、cookie使用规范等。具体细节的编码规范需要根据团队水平和团队偏好来制定。
以上这些编码规范虽然都是琐碎的事情,但是对于大型网站而言,这些琐碎的规则确实能避免很多问题,也能以最低的代价规整前端工程结构,使网站后续的升级和维护也不至于举步维艰。
JavaScript库
库,一般来说是一个开发语言经过浓缩和优化后的超集或者工具包。
JavaScript库的作用就是简化JavaScript代码,一个JavaScript库无疑会规整化整个前端工程。JavaScript库使用前后如代码3.15所示。
代码3.15 JavaScript库使用前后
//使用JavaScript原生函数修改HTML内容
document.getElementById("id_text").innerText = "xxxxx";
//使用jQuery(JavaScript库)的函数修改HTML内容
$("#id_text").html("xxxxx");
流行的JavaScript库有很多种,在我们开始介绍这些JavaScript库之前,首先要明确一点,除去某些项目的特殊要求外,选择JavaScript库的关键不是这些库的优劣,而是需要尊重开发团队中大多数人的习惯,选择更偏向于大多数人都使用过的JavaScript库。这样可以降低团队的学习成本,让项目开发过程更顺畅一些。
注意:库和框架是不一样的,框架是解决一类问题的基本软件框架,而库直白地讲只是一堆函数、工具或者一些语法改进。
下面介绍一些比较流行的JavaScript库。
1.jQuery
相信jQuery的大名很多人应该都知道,jQuery凭借其优秀的设计思想和实用的语法,让它在2006年一经推出,就得到了各界的好评和世界各大项目的深度应用。
jQuery解决了早期浏览器的兼容性问题(早期的浏览器是一个厂商一个样)。jQuery拥有强大的DOM元素选择器,简化了JavaScript语法。jQuery也拥有Ajax这个利器,大大简化了前后端的通信代码。除此之外,jQuery还拥有丰富的动画支持和强大的插件群。
以上都是它的好处,但目前,jQuery的作用越来越小了。例如,原生的JavaScript也新增了querySelector和querySelectorAll函数,可以直接通过CSS选择器获取元素;CSS3也提供了丰富的动画效果;浏览器的兼容性问题随着互联网的发展变得越来越小;Ajax也有其他替代方案。因此,目前使用jQuery的必要性越来越低,甚至网上都有了jQuery已经过时的说法。
不过,jQuery远远没有到被淘汰的地步。因为浏览器还没完全到大一统的地步;经过了这么多年的发展,jQuery已经相当稳定;jQuery的任何使用问题都能在网上找到;大多数前端工程师还习惯于使用jQuery。也就是说,浏览器兼容性、稳定性、社区完备性及使用习惯,这4点便使得jQuery还是JavaScript库的首选。
2.Zepto
Zepto是jQuery的精简版,是一个针对现代高级浏览器的轻量级JavaScript库,它与jQuery有类似的API。
由于移动端的浏览器绝大部分都是现代浏览器,而且Zepto比jQuery轻量,加上jQuery的底层是通过DOM来实现的,Zepto更多的是操作CSS3,所以如果是只针对移动端的项目,Zepto确实是很好的选择。
3.自建JavaScript库
对于JavaScript库,目前没有太多的选择余地,毕竟jQuery还处于统治的地位。因此,这里说的自建JavaScript库并不是说要做一个替代jQuery的库。
自建JavaScript库的意义是,针对某一项目的业务特性增加的通用函数库。自建的JavaScript库与第三方的JavaScript库,应该是相互补充和并列使用的关系。这里的自建JavaScript库其实是3.3.1小节中提到的共用的JavaScript文件。
例如,一个音视频相关的网站,前端工程将会有很多音视频相关的处理函数(如视频属性判断、文件大小单位转换等);又或者前端和后端做了某些约定,需要前端处理完数据后才递交给后端处理(如加密处理)。还有很多情况,项目中有很多共用的函数,这时候就需要自建一个JavaScript库。自建JavaScript库能避免代码重复编写、代码更新不完全等不良情况。
这里选择jQuery作为JavaScript库。jQuery其实就是一个JavaScript文件,HTML网页简单地引用即可,jQuery文件可以放到前端目录结构中的第三方插件目录(lib)下。但是,由于jQuery是我们选定的基础库,原则上每个网页都需要使用,所以更好的方法应该是另外新建一个目录存放这些基础依赖。
图3.30 增加基础依赖目录
如图3.30所示,其中basicdepend便是存放基础依赖的文件夹。
说明:JavaScript本身也在不断地优化,时至今日,仅仅使用JavaScript(不引用其他JavaScript库)开发的体验也是不错的,所以引用JavaScript库并不是必要的。目前,越来越多的项目开始抛弃各式各样的JavaScript库,有点返璞归真的感觉。
组件工具箱
前端组件工具箱是对HTML默认组件(如输入框、按钮)的美化和扩充,如图3.31所示。
图3.31 默认标签样式与使用组件工具箱美化后的样式对比
组件工具箱其实就是预先写好一些CSS样式配置和JavaScript脚本,在我们需要用到它们时,引用就可以了,如代码3.16所示,其中form-control、btn和btn-primary样式是组件工具箱提供的。
代码3.16 HTML元素使用组件工具箱
<html>
…
<input class="form-control" >
<button class="btn btn-primary">按钮</button>
…
</html>
选择一个前端组件工具箱,能大大降低组件美化带来的工作量。不过需要注意一点,每个第三方前端组件工具箱都有自己固有的设计风格,这种设计风格基本上是不可调控的。如果组件工具箱的设计风格和UI设计产生冲突的话,需要考虑是换一个组件工具箱还是UI设计稍做妥协。组件工具箱的学习成本相对较低,但是也不等于没有学习成本,所以还需要一定程度上考虑开发人员的习惯。
注意:组件工具箱选择的关键是,其是否包含项目需要的绝大多数组件,以及其设计风格是否符合UI设计的审美。
下面介绍几个比较流行的前端组件工具箱。
1.Bootstrap
Bootstrap的受欢迎程度是有目共睹的,也是目前使用最广泛的组件工具箱。Bootstrap本身组件量足够多,还有很多基于Bootstrap风格制作的第三方插件,所以组件数量上能应对绝大多数的网站需求。Bootstrap的设计风格也适中,符合大众审美。Bootstrap还提供了栅格结构的布局系统,大大降低了屏幕适配性的工作量,不过也有很多人不喜欢Bootstrap的栅格结构,这个就见仁见智了。另外,Bootstrap的组件是适配PC端和手机端的,如果项目需要适配PC端和手机端,一个Bootstrap组件工具箱就足够了。
笔者也很喜欢Bootstrap组件工具箱,如果前端风格没有太过于严格的要求,选用Bootstrap将会是一个不错的选择。
2.jQuery UI
jQuery UI从严格来讲不算组件工具箱,大多数开发者都是通过jQuery制作一些动画效果,这些效果在老旧浏览器上的表现是优秀的。不过,现在很多使用jQuery UI制作的效果使用几行CSS3属性便可以替代,而且jQuery UI的审美也有些过时了。
既然jQuery UI有了过时的感觉,这里为什么还要提及jQuery UI呢?这是因为网上还是有很多使用jQuery UI做的例子,还有一些使用jQuery UI的老项目。另外最关键的一点,如果网站需要适配老旧浏览器,那么很多时候就不得不使用jQuery UI了。
3.Three.js
Three.js是使用JavaScript编写的WebGL(Web Graphics Library,是一种3D绘图协议)第三方库。Three.js不算是组件工具箱,顶多算是一个工具箱。Three.js是一款运行在浏览器中的3D引擎,其包括摄影机、光影、材质等各种对象,用户可以用它创建各种三维场景。一些门户网站或者一些宣传性质的网站如果想要追求酷炫效果的话,那么选择Three.js是不错的决定。虽然Three.js目前还处于比较不成熟的阶段,不够丰富的API和匮乏的文档都增加了学习的难度,但是一般的网站需要用到Three.js特效的地方只会有一两个,而且网上用Three.js做的特效例子还是比较多的。
4.其他组件工具箱
随着人们对前端审美的重视,越来越多的组件工具箱随之出现,如MuseUI、Element UI等。这些组件设计风格各异,看上去都挺不错的。不过,在选择这些组件工具箱前需要好好斟酌,除了判断设计风格、组件量是否足够以外,还需要判断其是否需要依赖其他框架、稳定性、文档的完备性及网上能否轻易搜索到资料等因素。因为在使用这些相对不那么流行的工具箱或者技术时,先不说学习成本怎么样,更需要考虑的是风险。如果一旦出现资料查不到、工具箱本身存在某些不可知Bug等情况,那么项目进度和成本就会开始偏离。毕竟做项目是一种经济行为,前端架构在选择工具箱或其他技术的时候,都应该充分考虑风险。如果不是对这些工具箱特别了解或者项目上有限制,那么还是选择流行的工具箱比较好。
注意:做项目是一种经济行为,架构设计在选择一些工具和框架时,风险把控更为重要。盲目地跟随大公司的设计或者不权衡风险就选用新技术,都是极其不负责任的行为。
5.自建组件工具箱
自建组件工具箱更多的是项目需要。例如一些特大型的网站,或者一些艺术行业相关的网站,都想看上去特别一些,那么就很可能需要自建组件工具箱了。
说明:虽然这种情况不经常发生,但也说明了一点,一切技术都是问题驱动的,有需要才会选择某个技术,而不是因为掌握了某个技术,任何情况下都选择这种技术。
这里我们选择比较流行的Bootstrap作为基本的组件工具箱,Boostrap的相关文件在https://www.bootcss.com/官网下载即可。由于组件工具箱属于基础依赖,每个网页都需要用到,所以我们将其放到basicdepend文件夹中。需要注意的是,Boostrap 4依赖jQuery.js和popper.js(可以在Bootstrap官网找到下载地址),不引用这两个JavaScript文件会出现问题。引用Boostrap后的工程结构如图3.32所示。
图3.32 增加组件工具箱的工程目录
框架
框架是在一定的设计原则上,规划和安排各个组件的关系,以按照一定规则解决一类问题的软件。简单地说,软件框架是解决一类问题的基本软件工具。在顺从框架基本思路的前提下,熟练地使用框架,可以大大提升开发效率。
注意,是在顺从框架基本思路和熟练使用框架工具的情况下,才能大大提升开发效率。因为要深入了解框架的基本思路和熟悉框架的使用,所以每个框架都有很高的学习成本。框架想解决的问题越多,学习成本也会越大。前端框架的学习成本也是很高的,所以前端框架的选择除了要对比各大流行框架的优劣以外,更重要的是要评估前端团队的学习能力和使用习惯。
当然,前端框架也不是必须选择的。一些项目也偏向不选用前端框架,因为使用前端框架会让原本简单的前端开发增加学习难度,更何况前端框架用得不好的话,反而会增加前端工程的混乱度和工作量,弄巧成拙。
注意:不选择前端框架不等于前端开发人员可以随意选择自己顺手的框架,而是不使用任何前端框架。
1.前端框架要解决的问题
在介绍具体的前端框架之前,先来介绍一下前端框架想解决的一些问题,主要包括数据双向绑定、指令、虚拟DOM和模块化。值得一提的是,要解决这些问题,并非一定要用到某个框架,自己编写JavaScript代码也是可以解决的,框架只是帮我们写好了这部分JavaScript代码。
(1)数据双向绑定。
一般情况下,JavaScript想要改变或获取HTML元素的值时,需要通过如代码3.17所示的方式才能实现。
代码3.17 使用JavaScript获取或改变HTML元素的值//获取HTML元素显示的值
var text = document.getElementById("id_text").innerText;
//设置HTML元素显示的值
document.getElementById("id_text").innerText = "xxx";
在以上代码中,每次操作都需要获取一遍HTML元素,那么是否有一种方法,可以直接把JavaScript变量和HTML元素绑定起来呢?这样的话,我们仅需要操作这个JavaScript变量就可以影响到HTML元素了,不需要写这么多代码。
例如一个JavaScript变量和输入框绑定后,当变量改变时,输入框的显示值也会改变,反之亦然。
这就是数据双向绑定,通过一次性绑定JavaScript变量和HTML元素后,操作这个JavaScript变量即可影响到HTML元素。数据双向绑定其实用到了JavaScript提供的Object.defineProperty()函数,该函数可以让数据对象变得“可观测”。目前比较流行的前端框架基本上都支持双向绑定,以Vue.js框架为例,HTML代码如代码3.18所示,双向绑定的JavaScript代码如代码3.19所示。
代码3.18 HTML代码
<div id="id_inputBody">
…
<input v-model=" Vue_Input">
…
</div>
代码3.19 JavaScript代码
//初始化,绑定JavaScript变量和代码3.18中的HTML元素
var VueObject = new Vue({
el:"#id_inputBody",
data:{
Vue_Input: "123"
}
});
//当input元素中的值被修改时,此变量也会变更,当此变量被修改时,input元素显示的值也
会变化
VueObject.Vue_Input;
说明:数据双向绑定确实能减少一定的代码量,但“是不是必须要这样做”就见仁见智了,毕竟数据双向绑定减少的代码量有限,而传统的方法在代码理解上可能更直观一些。
(2)指令。
我们看到的网页其实就是HTML文件的一堆字符串,一般认为,HTML本身是静态的,HTML的变化只能通过JavaScript对其进行修改。但是在一些场景中,单纯地使用JavaScript对HTML进行操作是比较烦琐的。以把一个列表数据显示到网页上为例,HTML代码如代码3.20所示,JavaScript代码如代码3.21所示,效果如图3.33所示。
代码3.20 HTML代码
<html>
…
<div id="id_listBody"></div>
…
</html>
代码3.21 JavaScript代码
//列表数据
var data = [
{"id":1, "name":"张三"},
{"id":2, "name":"李四"},
{"id":3, "name":"王五"}
]
var content = "";
for(var index in data){ //拼接HTML元素
content += "<p>"+
"id:"+data[index]["id"]+", "+
"姓名:"+data[index]["name"]+
"</p>";
}
//修改HTML内容
document.getElementById("id_listBody").innerHTML = content;
图3.33 页面效果
根据数据来改变页面内容,最常用的做法就是拼接HTML字符串并将其替换到页面上。这样的做法有几个不好的地方,第一,比较烦琐,每次都需要写一遍循环体;第二,不够直观,往往需要把一些作为模板的HTML字符串混在拼接的逻辑里。
为了改变上面这种不好的体验,指令被提出。指令的作用是,开发者可以直接在HTML里添加一些框架定义的指令(如for、if等),即可在HTML代码上添加逻辑,省略掉一大堆JavaScript代码的同时,能更直观地让HTML变化起来。以使用Vue.js框架为例,HTML代码如代码3.22所示,JavaScript代码如代码3.23所示,页面效果与之前的效果一样(如图3.33所示)。
代码3.22 HTML代码
<html>
…
<div id="id_listBody">
<p v-for="list in Vue_List">id:{{list.id}}, 姓名:{{list.name}}</p>
</div>
…
</html>
代码3.23 JavaScript代码
//初始化,绑定JavaScript变量和代码3.22中的HTML元素
var VueObject_List = new Vue({
el:"#id_listBody",
data:{
Vue_List:[]}
});
//列表数据
var data = [
{"id":1, "name":"张三"},
{"id":2, "name":"李四"},
{"id":3, "name":"王五"}
]
//操作vue对象的变量后,HTML的内容会自动被修改
VueObject_List.Vue_List = data;
在上面的做法中,在HTML代码里添加了指令(v-for),让静态的HTML看起来有了逻辑,JavaScript代码中也不需要再写拼接HTML字符串的代码。加入指令后,整个代码结构清晰了很多。
指令的基本原理,是利用了HTML元素可以自定义属性的这个特点,把一部分看起来像是代码的字符串(如list in Vue_List)记录在这些标签上。然后,框架的JavaScript脚本在提取这些自定义的属性后,会根据这些字符串转换成逻辑代码,最后拼接HTML字符串并替换页面上的内容。
注意:指令是一个非常有意思的东西,它让静态的HTML看起来有了逻辑。它也从一个新奇的角度打破了我们把“让HTML动起来”这件事只交给JavaScript完成的习惯,打破了HTML是完全静态的这个认知。正如飞机让人类“学会飞行”一样,这确实是想象力带给技术的一些惊喜。但我们回顾这些让人惊讶的想象力时,其实不过是发现问题后用间接的手段解决问题而已。因此,保持思考十分重要,很多问题都是可以解决的。
(3)虚拟DOM。
前端页面是依赖DOM树来构建的,这在3.1.3小节前端网页的工作原理中提到过。JavaScript脚本修改页面元素时,实际上是修改DOM树的节点,当DOM树的某个节点发生变化时,浏览器会立刻重新渲染。在极端情况下,如果在一段JavaScript代码中修改多个页面元素,浏览器可能会频繁地重新渲染,这无疑增加了浏览器的性能损耗。
虚拟DOM就是为了解决浏览器性能问题而设计出来的。虚拟DOM实际上是一个JavaScript对象,页面元素的更新先全部反映在虚拟DOM上(更新JavaScript对象),等修改页面元素操作全部完成后,再一次性地更新到DOM树上,交由浏览器去渲染。
这么做的好处是,如果一次JavaScript操作中有10次更新页面元素的操作,虚拟DOM只会让浏览器渲染一次,如果没有虚拟DOM的话,浏览器很可能会渲染10次。由于浏览器多次渲染页面是损耗性能的,而虚拟DOM的作用就是为了节省这些性能。
最后需要说清楚的是,第一,虚拟DOM是一些框架的内部行为,只对框架自身有用,如果直接通过JavaScript修改页面元素,虚拟DOM是不起作用的。
第二,对于现在的浏览器来说,虚拟DOM的作用其实没有想象中作用大。因为浏览器内部对网页渲染是有一定优化的,当一段JavaScript代码连续进行了10次修改页面元素操作时,浏览器也可能只会渲染一次网页。
(4)模块化。
前端模块化可以剥离出通用的前端模块,代码复用性会大大提高,模块化的必要性在3.2.3小节中有详细的说明。不过,个人认为目前的前端框架对模块化的实现方式都不太好。模块化的一些具体方式会在后续3.5节中详细说明。
2.流行的前端框架
以上是前端框架想要解决的一些共同问题,同时也是前端网页技术提升的一个方向。下面介绍几个比较流行的前端框架,包括Vue.js、React.js、AngularJS和Angular2及后续版本。
(1)Vue.js。
Vue.js是一套轻量级的前端框架,它其实是一个JavaScript文件,只需要简单引用即可使用。Vue.js具有简单的语法和完备的文档,学习起来也比较容易。Vue.js支持双向数据绑定、指令和支持虚拟DOM,虽然也支持模块化,但实现的形式不太好,需要把模块代码都塞进JavaScript文件里。
(2)React.js。
React.js起源于Facebook的内部项目,是一个拥有高性能的前端框架,它也是一个JavaScript文件,只需要简单引用即可使用。React.js的语法和功能相对于Vue.js要复杂一些,所以学习起来会比Vue.js要麻烦一些。
React.js支持双向数据绑定和虚拟DOM(它也是第一个提出虚拟DOM概念的),不支持指令(有别的方式替代指令)。React.js的模块化和Vue.js类似,都需要把模块代码塞进JavaScript文件里。
(3)AngularJS。
AngularJS是一款来自谷歌的开源Web前端框架。它也是一个JavaScript文件,只需要简单引用即可使用。AngularJS的流行度还是不错的,学习起来也不算困难。不过AngularJS发布了Angular 2等断崖式的更新,因此AngularJS的后续支持度怎么样值得斟酌。
AngularJS支持双向数据绑定和指令,不支持虚拟DOM。AngularJS的模块化和Vue.js类似,都需要把模块代码塞进JavaScript文件里。
(4)Angular 2及后续版本。
Angular 2及后续版本,一般统称为Angular,不过都跟AngularJS没有关系,是全新的框架。Angular 2及后续版本都依赖TypeScript和node.js,它不再是引用一个JavaScript文件即可使用的框架,所以Angular 2及后续版本的学习曲线是非常陡峭的,学习成本是很高的。
Angular 2及后续版本支持双向数据绑定、指令和虚拟DOM。Angular 2及后续版本是真正模块化的框架,可以把模块代码完全独立出来。
与Angular 2及后续版本相似的框架,还有Vue-CLI和React-CLI。这类框架改变了前端工程结构,让前端工程更趋向于“软件应该有的样子”(前端工程变成一个整体,而非以一个页面为一个独立单元)。此类框架改变了前端开发的过程,打破了原生网页开发“一个页面只有一个HTML文件”的限制,使得前端代码复用性提升(模块代码可以完成独立)。再者,得益于node.js的npm,此类框架引用插件是十分容易的。
由于以上好处,加上各种培训课程的营销推广,此类框架越来越流行。甚至很多前端工程师只会使用此类框架进行开发。但是,此类框架在运行或打包时,终归是要编译成JavaScript脚本的,而这个过程基本上是一个黑盒。这样就会产生两个问题,一是工作原理不可预见,性能调优很难进行;二是学习成本很高,熟悉周期很长。
综上,此类框架比较好地解决了前端模块化的问题,但并非非用不可。当然,选取与否,更多的是取决于开发团队的习惯。如果大部分开发人员都熟悉此类框架,这确实是不错的选择。但如果大部分开发人员只熟悉原生网页开发的话,选择此类框架往往会弄巧成拙。
说明:判断是否选取一个技术,首先应该判断其是否能解决关键问题;其次取决于团队的使用成本及使用风险;最后,才是考虑其流行性。很多时候,流行性是一种“一犬吠影,百犬吠声”的错觉。
(5)自研框架。
很多时候,在不认同现成框架思想的时候,可以选择自研框架。不过,自研框架是一个漫长且成本很高的行为,也是风险最高的一条路。这看似和“做项目是一种经济行为”的观点相互矛盾,但是自研技术是一种长远的经济收益,也是一个技术团队的核心竞争力。
注意:现有的技术不一定能解决我们新发现的问题,现有的技术在解决某个问题时也不一定是最优的。如果有新的想法,可以大胆地尝试。对于这样的尝试,笔者不太同意“重复造轮子”的说法,毕竟,汽车的轮子用在火车上是没有用的。
这里我们选用比较轻量且流行的Vue.js框架。加上基本架构的选择后,便完成了整个前端目录结构的构造,如图3.34所示。
图3.34 完整的前端目录结构