这个来自之前做的培训,删减了一些业务相关的,参考了很多资料(参考资料列表),谢谢前辈们,么么哒 😘
随着互联网架构的不断演进,前端技术框架从后台输出页面到后台MVC,再到前端MVC、MVP、MVVM,以及到Virtual DOM和MNV*的实现,已经发生了巨大的变化。整体上来看,前端也正在朝着模块化、组件化和高性能Web开发模式化的方向快速发展。除了传统桌面浏览器端Web上的应用,前端技术栈在服务端或移动端上的尝试和发展也从来没有停止过,而且形成了一系列成熟的解决方案。前端的技术栈能解决的不只是页面上的问题,前端工程师的追求也绝不只是页面上的技术。
跨后端技术
这几年全栈工程师已成为一个很热门的关键词,从最早的MEAN技术栈到后端直出,再到现在的前后端同构,前端通过与Node结合的开发模式越来越被开发者认同并在越来越多的项目中得到实践。前端开发者都热衷于在Node上开发有以下几个原因:
- Node是一个基于事件驱动和无阻塞的服务器,非常适合处理并发请求,因此构建在Node上的应用服务相比其他技术实现的服务性能表现要好。
- Node端运行的是JavaScript,对于前端开发者来说学习成本较低,要关注的问题相对来说比前端更纯粹些。
- 作为一名前端工程师确实需要掌握一门后台语言来辅助自己的技术学习。
- Node端处理数据渲染的方式能够解决前端无法解决的问题,这在大型Web应用场景下的优势就体现出来了,这也是目前Node后端直出或同构的实现方式被开发者广泛使用的一个重要原因。
Node后端开发
Node.js 是一个基于 Chrome V8 引擎 的 JavaScript 运行时。
有个叫Ryan Dahl的歪果仁,他的工作是用C/C写高性能Web服务。对于高性能,异步IO、事件驱动是基本原则,但是用C/C写就太痛苦了。于是这位仁兄开始设想用高级语言开发Web服务。他评估了很多种高级语言,发现很多语言虽然同时提供了同步IO和异步IO,但是开发人员一旦用了同步IO,他们就再也懒得写异步IO了,所以,最终,Ryan瞄向了JavaScript。在2009年,Ryan正式推出了基于JavaScript语言和V8引擎的开源Web服务器项目,命名为Node.js。
Node第一次把JavaScript带入到后端服务器开发,加上世界上已经有无数的JavaScript开发人员,所以Node一下子就火了起来。Node最大的优势是借助JavaScript天生的事件驱动机制加V8高性能引擎,使编写高性能Web服务轻而易举。
阻塞和非阻塞
阻塞 是说 Node.js 中其它的 JavaScript 命令必须等到一个非 JavaScript 操作完成之后才可以执行。这是因为当 阻塞 发生时,事件机制无法继续运行JavaScript。
在 Node.js 中,JavaScript由于 CPU 密集操作而表现不佳。而不是等待非 JavaScript操作 (例如I/O)。这被称为 阻塞。
阻塞 方法执行起来是 同步地 ,但是 非阻塞 方法执行起来是 异步地 。 使用文件系统模块读取一个文件,同步方法看上去如下:
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // 这里会阻塞
与之功能等同的 异步 版本:
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
});
在第二个例子中, fs.readFile()
因为是 非阻塞 的,所以 JavaScript 会继续执行,不会发生阻塞, 这对于高效吞吐来说是绝佳的设计。
在 Node.js 中 JavaScript 的执行是单线程的,所以并行与事件轮询能力(即在完成其它任务之后处理 JavaScript 回调函数的能力)有关。任何一个企图以并行的方式运行的代码必须让事件轮询机制以非 JavaScript 操作来运行,像 I/O 操作。
比如 每个对服务器的请求消耗 50 毫秒完成,其中的 45 毫秒又是可以通过异步操作而完成的数据库操作。选择 非阻塞 操作可以释放那 45 毫秒用以处理其它的请求操作。这是在选择 阻塞 和 非阻塞 方法上的重大区别。
Node.js 中的事件轮询机制和其它语言相比而言有区别,其它语言一般需要创建线程来处理并行任务。
MEAN
Node出现的早期还不像现在一样拥有很复杂的概念,相关技术和语言的标准还不成熟,Node开发一般用的比较多的方案就是使用Express作为Web框架进行小型的Web站点建设,与之结合的主流技术则以M(Mysql)、E(Express)、 A(Angular)、 N(Node)最为典型,甚至到了今天MEAN技术组合的方式仍在沿用。
前端一般使用Angular来管理实现页面应用,服务端Web框架以Express为主,同时使用免费开源的MongoDB数据库,这样就可以很快地构建一个Web应用了。
今天可能不一定再去选择使用它,因为可以代替实现的成熟方案已经很多了,各类其他前后端框架都可以用来灵活组合作为MEAN的替代选型方案,比如 Vue、React可以替代 Angular,Koa 可以替代 Express,数据库的选择也有很多。
Node后端数据渲染
对于前端开发者来说,在大型Web应用开发中,很多时候并不需要完全重新设计整个应用后台的架构,更多的情况下需要结合Node的能力帮助我们解决前后端分离开发模式下无法解决的问题。我们先来看下通常前后端分离的开发模式下有哪些问题,利用Node 端的服务又是如何帮助我们解决这些问题的:
SPA场景下SEO的问题
通常情况下,SPA应用或前后端分离的开发模式下页面加载的基本流程是:
- 浏览器端先加载一个空页面和JavaScript 脚本。
- 然后异步请求接口获取数据。
- 渲染页面数据内容后展示给用户。
那么问题来了,搜索引擎抓取页面解析该页面HTML中关键字、描述或其他内容时,JavaScript尚未调用执行,搜索引擎获取到的仅仅是一个空页面,所以无法获取页面上<body>中的具体内容,这就比较影响搜索引擎收录页面的内容排行了。尽管我们会在空页面的<meta>里面添加keyword和description的内容,但这肯定是不够的,因为页面关键性的正文内容描述并没有被搜索引擎获取到。
如果使用Node后端数据渲染(有人称之为直出或服务端渲染 SSR),在页面请求时将内容渲染到页面上输出,那么搜索引擎获取到的HTML就已经包含页面完整的内容,页面也就更容易被检索到了。
前端页面渲染展示缓慢的问题
除了SEO问题,在前后端分离的开发模式下页面在JavaScript执行渲染之前是空白的(或提示用户加载中)。用户在看到数据时已经花费的网络等待时间包括:
DOM下载时间 + DOM解析时间 + JavaScript 文件请求时间 + JavaScript部分执行时间 + 接口请求时间 + DOM渲染时间。
这时用户看到页面数据时已经是三次串行网络资源请求之后的事情了。如果使用后端直出来进行数据渲染,首先SEO的问题不复存在,用户浏览器加载完DOM的内容解析后即可立即展示,网络加载的问题也得到解决。其他的逻辑操作(如事件绑定和滚动加载的内容)则可按需、按异步加载,从而大幅度减少展示页面内容花费的时间。
一般后台页面数据直出的通用架构设计如下:
直出层接受前端的路由请求,并在Node端的Controller层异步请求服务接入层接口,获得Model数据并进行组装拼接,然后提取相对应的Node端View模板渲染出HTML输出给用户浏览器,而不用通过前端JavaScript请求动态数据后渲染。
不仅如此,直出层根据不同的浏览器userAgent,也可以提取不同的模板渲染页面返回给不同的用户浏览器,所以这种实现方式不仅非常适合大型应用服务的实现场景,而且可以方便地实现网站的响应式内容直出。
前后端同构
在前后端分离的开发模式上加入直出层,解决了SEO和数据加载显示缓慢的问题。可是有两个新的问题:
- 前端的开发实现向直出层偏移,不得不在原来的开发模式上做出修改来 适应直出层内容的开发,例如修改后端模板来适应现有的开发模式,结果我们不得不维护两套不同的前后台模板或技术实现——前端渲染实现逻辑和后端直出实现逻辑,尽管可能都是用JavaScript写的。
- 如果是在移动端Hybrid应用上,离线包机制实现可能就会出现问题。因为每次都是从后端直出HTML结构给前端,这样就难做到将HTML文件进行离线缓存,而只能进行其他静态文件的缓存。在Hybrid App的应用场景下,其实我们更希望做到的是移动端首次打开页面时使用后端直出内容来解决加载慢和SEO问题,而在有离线缓存的情况下则使用客户端本地缓存的静态文件拉取数据返回渲染的方式来实现,或者未来在高版本的浏览器支持HTTP2的条件下使用前端渲染,低端浏览器不支持HTTP2的情况下则使用直出的方式实现。
所以需要一套完善的开发方式,和原有开发方式保持一致,且能够同时用于前后端分离的开发模式和后端数据渲染模板开发方式中。这种开发模式就是我们所说的前后端同构。
实现同构的核心
前后端同构的宗旨是,只开发一套项目代码,既可以用来实现前端的JavaScript 加载渲染,也可以用于后台的直出渲染。
为什么可以这样做呢?和前端渲染数据内容的方式相同,页面直出层内容也是通过数据加上模板编译的方式生成的,前端渲染和后台直出的模式生成DOM结构的区别只在于 数据和模板的渲染发生在什么时候。如果使用一套能在前端和后端都编译数据的模板系统,就可以做到使用同一套开发代码在前后端分别进行数据渲染解析。因此前后端同构的核心问题是实现前后台数据渲染的统一性。
同构的优势
除了解决前后端开发方式的问题,前后端同构的网站具有一些明显的优势:
- 可以根据用户的需求方便地选择使用前端渲染数据还是后台直出页面数据;
- 开发者只需维护一套前端代码,而且可以沿用前端原有的项目组件化管理、打包构建方式,根据不同的构建指令生成类似的前后端数据模板或组件在前后端执行解析,所以这对于DOM结构层上的开发方式应该是一致的。
前后端同构的实现原理
基于数据模板的前后端同构方案
早在前端MVC开发的时代,前端模板的使用就非常广泛,例如Mustache、Handlebar 等,基本原理是将模板描述语法与数据进行拼接生成HTML代码字符串插入到页面特定的元素中来完成数据的渲染。同理,后端直出层也可以通过该方法来实现数据的渲染产生HTML字符串输出到页面上。
如果前后端使用同一个模板解析引擎,那么我们只需要编写同一段模板描述语法结构就可以在前端和后端分开进行渲染了。比如同样的模板:
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{body}}
</div>
</div>
前端,后端拿到数据后解析保持一致:
{
"title": "Hello",
"body": "World"
}
<div class="entry">
<h1>Hello</h1>
<div class="body">
World
</div>
</div>
对于前端开发的同一段模板语法结构,我们既可以选择在浏览器端渲染生成HTML字符串输出,也可以选择在后端渲染生成HTML字符串输出。如果选择在前端渲染,则可以将模板进行打包编译,在数据请求成功后进行DOM渲染;如果选择后端渲染,就可以将模板数据直接发送到直出层的View视图进行渲染,实现同一个模板语法结构在前后端渲染出相同的内容。这里的前提是要保证前后端使用的模板渲染引擎或者模板解析的语法是一致的。
基于MVVM的前后端同构
MVVM框架页面上的JavaScript逻辑主要是通过Directive(不只是Directive,还有filter、 表达式等,以Directive为主)来实现的,一般前端页面加载完成后会开始扫描DOM结构中的Directive指令并进行DOM操作渲染或事件绑定,所以数据的显示仍然需要页面执行Directive后才能完成。那么如果将Directive的操作在直出层实现,浏览器直接输出的页面就是渲染后的内容数据了。
<div class="entry">
<h1 x-html="title"></h1>
<div class="body" x-html="body"></div>
</div>
前端编写的同一段MVVM的语法结构,通过前端MVVM框架解析或后端Directive 运行解析最终都可以生成相同的HTML结构,不同的是前端执行解析后生成的是ViewModel对象并通过浏览器体现,后端渲染则生成HTML标签的文本字符串输出给浏览器。这里同样需要做一件事,即在后台实现一个与前端解析Directive相同的模块,甚至还包括filter、语法表达式等的实现。这样就可以在前后端完成同一段语法结构的解析了。
基于VirtualDOM的前后端同构
之前说过,VirtualDOM作为一种新的编程概念被广泛应用在实际项目开发中,其核心是使用JavaScript 对象来描述DOM结构。那么既然Virtual DOM是一个JavaScript对象,就表示其可以同时存在于前后端,通过不同的处理方式来实现同构。
在前端开发的组件中声明某段VirtualDOM描述语法,然后通过VirtualDOM框架解析生成VirtualDOM,这里的VirtualDOM既可以用于在浏览器端生成前端的DOM结构,也可以在直出层直接转换成HTML标记的文本字符串输出,后面这种情况就可以在服务端上实现Virtual DOM到HTML文本字符串的转换。这样,通过对Virtual DOM的不同操作处理,就可以统一前后端渲染机制,实现组件的前后端对同一段描述语法进行渲染。
这里VirtualDOM上的逻辑实现仍然需要在浏览器端进行事件绑定来完成,最好能让同构框架帮助我们自动完成,根据HTML的结构进行特定的事件绑定处理,保证最后展示给用户的页面是完整且带有交互逻辑的。
无论哪一种方式,核心都体现在HTML的结构形式变化上,页面内容的描述方式有很多,而且可以通过特定的处理过程实现转化,这样就提供了更多的可能性。
Egg.js
Node虽然生态比较火热,但是至今还没有一款公认的成熟的企业级框架,主要是因为使用Node来开发大型后端应用的企业还很少。现在主要有两款:Sails 和 Egg.js。
设计原则
一个插件只做一件事:Egg 没有内置很多额外的功能,而是通过插件的方式来实现,Egg 通过框架聚合这些插件,并根据自己的业务场景定制配置,这样应用的开发成本就变得很低。
约定优于配置:按照一套统一的约定进行应用开发,团队内部采用这种方式可以减少开发人员的学习成本,这也是很多框架的思路。
特点
- 提供基于 Egg 定制上层框架的能力:可以基于 Egg 去封装适合团队的上层框架。
- 高度可扩展的插件机制:可以促进业务逻辑的复用,生态圈的形成。
- 内置多进程管理。
- 基于 Koa 开发,性能优异:支持所有的Koa中间件。
- 框架稳定,测试覆盖率高。
- 渐进式开发:可以流畅的实现编码 --> 编码抽象成功能 --> 功能抽象成插件 --> 插件封装到框架 的渐进过程。
现在我们部门的Node项目基本上是 Koa,Egg流。其他部门也有 Express 流。
跨终端技术
移动端
移动互联网兴起后,智能移动设备出现,大量应用市场的Native应用也开始涌现。随着第一波移动端互联网开发浪潮渐渐平静,各类Native应用开始进入有序更新迭代的阶段。人们对移动互联网需求急剧增长,Native 应用快速迭代开发的需求也越来越多,但是现有Native应用的开发迭代速度依然无法满足市场快速变化的需要。随之而来的是HTML5的出现,它允许开发者在移动设备上快速开发网页端应用,并让移动互联网应用开发很快进入了Native应用、Web应用、Hybrid 应用并存的时代。
在发展过程中,最大限度的利用原生能力成为了一大趋势。出现了 React Native、Weex 等框架,可以直接使用 Javascript 来编写原生应用。比如React Native,产出的并不是“网页应用”, 或者说“HTML5应用”,又或者“混合应用”。 最终产品是一个真正的移动应用,从使用感受上和用Objective-C或Java编写的应用相比几乎是无法区分的。
import React, { Component } from 'react';
import { Text, View } from 'react-native';
class WhyReactNativeIsSoGreat extends Component {
render() {
return (
<View>
<Text>
如果你喜欢在Web上使用React,那你也肯定会喜欢React Native.
</Text>
<Text>
基本上就是用原生组件比如'View'和'Text'
来代替web组件'div'和'span'。
</Text>
</View>
);
}
}
也就是说即使不懂原生应用的开发,也可以用 Javascript 来编写原生应用了。
桌面端
现在,也可以使用 JavaScript, HTML 和 CSS 构建跨平台的桌面应用。通过 Electron 之类的应用,可以直接把 Web 项目打包成桌面应用,运行在各个操作系统中。
著名的 Atom IDE 就是通过 Electron 构建的,其他的包括 VS Code、Skype、Github Desktop 之类的 App 也都是通过 Electron 构建的。