前言
The only constant in the world is change.世界上唯一不变的是变化。--《谁动了我的奶酪》作者 斯宾塞·约翰逊
IT 行业变化太快了,尤其是前端开发(Frontend Development)。如果能穿越回 10 年前,碰上一位 Web 开发软件工程师,他一定会告诉你玩转前端就是精通 jQuery 和搞定 IE 浏览器兼容性。不过随着前端的不断发展,jQuery 遭遇 “官方逼死同人” 逐渐退出历史舞台(元素选择和操作被标准的 DOM API 所统一);而饱为诟病的 IE 浏览器兼容性问题,因为 IE 市场的逐渐萎缩以及一些兼容性工具(Polyfill)的出现,让其从之前的核心优化问题降级为如今的瘙痒问题,不再成为前端工程师的标配。
如今的前端开发,有着玲琅满目的专业术语和纷繁复杂的技术生态,可能会让初入前端开发的工程师感到惴惴不安:要学的东西实在是太多了。现在的前端工程师如果不了解 Webpack、Babel、Node.js、NPM/Yarn、ES6/7、React/Vue、Sass/Less、TypeScript、ESLint、Canvas/SVG 等现代化前端知识,就难以让人信服自己的专业背景。2021 年的前端工程师可能是真正意义上的工程师(Engineer),他们通常需要运用大量的专业知识来解决工程化问题,包括如何将项目进行模块化,如何设计组件间的交互,如何提高可复用性,如何提升打包效率,优化浏览器渲染性能,等等。他们不再像以前,只需要 HTML/CSS/JS 一个套路来开发静态页面。
本文将着重就现代前端开发的主题,来详细介绍前端工程化的各个重要技术,帮助读者了解现代前端页面的复杂和多样性是如何构造的。本文是一篇关于前端工程的科普文,即使你不了解前端技术,也可以从本文受益。
打包部署
要说现代前端开发跟 10 年前最大的不同点是什么,我肯定会说是打包部署(Bundling & Deployment)。生活在前端 “石器时代”(jQuery 主流时期)的开发者肯定很难想象如今的前端项目需要编译(Compile)。以前的大部分 Web UI 是通过 MVC 的方式来提供给用户的,前端页面大部分是通过后端模版渲染生成静态资源(HTML/CSS/JS)不经任何加工提供给浏览器的。也就是说,当时前端的静态资源相当于后端服务表现层(Presentation Layer)的一部分,复杂的交互通常都由后端来完成。因此前端静态资源通常非常简单,不需要经过复杂的编译打包,最多就是压缩(Minify)成更小的文件。
而现在的情况完全不同了,用户对于交互性的需求逐渐增大,通常不愿意花过多的时间来等待页面响应。传统的 MVC 模式受限于网络传输,通常需要几百毫秒甚至更多来完成一次页面导航,而这是非常影响用户体验的。因此,前端项目逐渐分离出来,单独作为一个应用运行在浏览器中,交互在浏览器端进行,速度非常快。但是,复杂交互都集中在前端里面,让前端项目变得更复杂,更臃肿,也更难以管理。因此,庞大的项目源码需要被压缩成很小的静态文件,尽可能少的占用网络资源传输给浏览器。现在稍微大一点的前端项目的依赖文件就会超过 500MB。相信你不会傻到将这么多文件一股脑塞给用户吧!
因此,你需要一个非常强大的打包和编译工具。Webpack 是现在比较主流的打包工具,能够配合编译工具(例如 Babel)将各种类型的项目源代码文件(例如 .vue、.ts、.tsx、.sass)打包成原生的 .js、.css、.html 等静态文件,直接提供给浏览器运行。下图是 Webpack 官方网站的流程示意图。Webpack 通过检查源代码文件的各种依赖关系,分析编译后生成对应的静态资源文件。这就跟编译 Java、C# 代码编译是一个道理。
Webpack 的配置由一个 JavaScript 文件来定义,文件名通常为 webpack.config.js
。下面是一个最基础的 Webpack 配置。
const path = require('path');
module.exports = {
entry: {
main: './src/main.js' // 入口文件
}
output: {
path: path.resolve(__dirname, 'dist'), // 打包后的输出路径
filename: 'bundle.js' // 打包后的输出文件名
},
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' }, // .css 文件转换模块
{ test: /\.ts$/, use: 'ts-loader' } // .ts 文件转换模块
]
}
};
这段配置代码定义了打包的入口(Entry)、输出(Output)以及编译所用到的加载规则(Rules),这些都可以通过一个配置文件来完成。定义好配置文件 webpack.config.js
,在项目根目录下执行 webpack
命令,就会默认加载该配置文件,然后读取源代码并打包为编译后的静态文件。您可能会注意到,有 module.rules
这个数组结构的配置项,这里可以定义各种文件类型的加载器(Loaders),也可以看作编译器。在这个例子中,css-loader
是处理 .css 文件的加载器,它会将 @import
、url()
、import
、require
等关键词转换为 原生 CSS/JS 中兼容的代码。而为了让 css-loader
生效,您需要在项目根目录下运行 npm install css-loader --save
来安装该加载器。同理,处理 .ts 文件的 ts-loader
也需要通过 npm install ts-loader --save
来使其生效。其实,为了让 Webpack 能处理各种文件类型(例如 .vue、.tsx、.sass),通常都需要安装对应的加载器,这其实给了 Webpack 非常强的可扩展性。通过安装加载器的方式,让不同类型的文件统一成原生前端代码,这也使我们能够非常有效的管理复杂而庞大的前端项目。
由于本文不是一个参考指南,因此关于 Webpack 的具体配置本文不打算详细介绍。如果需要了解 Webpack 的更多内容,可以去 Webpack 官方文档(https://webpack.js.org/concepts)深入研究。当然,前端打包工具并不止 Webpack 一种,还有其他一些比较受欢迎的工具,例如 Rollup.js、Gulp.js、Parcel.js、ESBuild 等等。感兴趣的读者可以深入研究一下。
Webpack 以及其他打包工具的驱动引擎是 Node.js,也就是说它们是作为 Node.js 的一个依赖来运行的。其实,任何一个现代化的前端项目,都离不开 Node.js,还有其包管理系统 NPM。我们将在下一个小节介绍前端项目的模块化,包括 NPM。
模块化
模块化(Moduarization)是任何技术工程项目中非常重要的概念。一个复杂的稳定运行的大型系统通常是由多个子模块一起构成的,它们之间相互通信、共同协作来完成一些非常复杂的逻辑或任务。复杂性为维护人员带来的麻烦通常是非线性的,也就是说,在系统成长到一定规模的时候,要增加单位规模所需要花费的成本会成指数级增加。如果我们不采取一定措施限制维护成本,那这样规模的项目几乎是不可维护的。为了让开发者能够有效管理大型项目,我们的做法通常是减少复杂性和可理解性,尽可能的减少系统复杂性增加的速度。而一种有效降低复杂性的方式,就是分而治之,对于软件项目来说就是模块化。模块化让各个负责相同或类似功能的子系统独立运行(高内聚),并适当开放一些接口提供给其他子模块(低耦合)。这样整个大型系统由多个子模块描述,而这些子模块都有各自的特性和功能,让维护人员能够准确的定位到各功能对应的模块,从而大大减少了维护成本。“高内聚,低耦合” 是模块化的核心理念,其最终的目的,是降低系统复杂性和提高可维护性。模块化对于日渐复杂而且动辄几百兆代码的前端项目来说,是非常非常重要的。
NPM
对于前端项目来说,NPM(Node Package Manager)不可或缺的前端项目构建工具。NPM 顾名思义是 Node.js 的包管理工具,类似于 Python 的 pip,Java 的 Maven,C# 的 Nuget,等等。对于一些已经存在的模块或功能,我们不需要从头开始编写,因为软件工程界所倡导的 DRY 原则(Don't Repeat Yourself),否则我们会花大量的时间在重复造轮子上。NPM 就是前端项目中的依赖管理工具,它能够帮助你安装构建前端项目所需要的包和库,然后再通过前文的 Wepback 等打包工具将源代码编译成原生前端静态文件。
描述前端项目依赖的配置文件叫做 package.json
,类似于 Python 中的 setup.py
,Java 里的 pom.xml
,可以定义项目的依赖包、运行入口、环境要求等等。package.json
可以手动生成,也可以用 npm init
的方式创建。下面是一个样例 package.json
文件。
{
"name": "crawlab-frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test": "jest"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-brands-svg-icons": "^5.15.1",
"@fortawesome/free-regular-svg-icons": "^5.15.1",
"@fortawesome/free-solid-svg-icons": "^5.15.1",
"@fortawesome/vue-fontawesome": "^3.0.0-2",
"@popperjs/core": "^2.6.0",
"@types/codemirror": "^0.0.103",
"@types/md5": "^2.2.1",
"atom-material-icons": "^3.0.0",
"codemirror": "^5.59.1",
"core-js": "^3.6.5",
"element-plus": "^1.0.1-beta.7",
"font-awesome": "^4.7.0",
"md5": "^2.3.0",
"node-sass": "^5.0.0",
"normalize.css": "^8.0.1",
"vue": "^3.0.0",
"vue-i18n": "^9.0.0-beta.11",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
},
"devDependencies": {
"@babel/preset-typescript": "^7.12.7",
"@types/jest": "^26.0.19",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-typescript": "^5.0.2",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0-0",
"sass-loader": "^10.1.0",
"scss-loader": "^0.0.1",
"typescript": "~3.9.3"
}
}
其中,比较重要的是 dependencies
和 devDependencies
这两个配置项,它们定义了该项目所依赖的包。它们是 Key-Value 的形式,Key 为依赖的包名,Value 是对应的依赖包的版本号或者版本号匹配表达式。当项目创建好后,执行 npm install
就可以自动的将依赖包从包托管网站 https://npmjs.com 上拉取下来。所有安装好的模块会出现在 node_modules
目录下,每个包对应的目录名称以其包名命名。当我们打包编译源代码时,项目构建工具会自动分析源代码的依赖,并从 node_modules
找到对应的模块来进行编译和构建。
scripts
配置项是项目开发、调试、构建、测试、部署的命令行别名(Alias),帮助您快速启动项目。它也是 Key-Value 的形式,Key 为别名,Value 为具体对应的执行命令。例如,根据上面的配置,如果执行 npm run build
,相当于执行了 vue-cli-service build
,即通过 vue-cli-service
这个官方命令行工具来打包编译前端项目(vue-cli-service
其实是针对 Vue 项目封装的 Webpack 构建工具)。
除了前面提到的要点,NPM 还有其他很多配置功能。详细信息可以参考官方文档(https://docs.npmjs.com)。如果你想更深入了解前端包管理,推荐学习一下 Yarn,这是 Facebook 开源的包管理工具,跟 NPM 非常像,但有一些实用功能,例如依赖缓存。
项目结构
如果说上面说的 NPM 以及包管理系统是宏观层面项目间的模块化,那么这个小节介绍的项目结构则是微观层面项目内的模块化。为了保证前端项目代码的可维护性,前端开发工程师通常需要掌握清晰而合理的代码组织方式,包括如何对不同类型文件或组件进行分类和管理,以及如何组织项目目录的层次和结构。下面是一个典型的 Vue3 的前端项目代码结构。
.
├── LICENSE
├── README.md
├── babel.config.js // Babel 编译器配置文件
├── jest.config.ts // Jest 测试工具配置文件
├── package.json // 项目定义文件
├── public // 公有静态目录
│ ├── favicon.ico
│ └── index.html
├── src // 源代码根目录
│ ├── assets // 静态文件目录,包括 .scss、.svg、.png 等
│ ├── components // 组件
│ ├── i18n // 国际化
│ ├── interfaces // 类型定义
│ ├── layouts // 布局
│ ├── main.ts // 主入口文件
│ ├── router // 路由
│ ├── shims-vue.d.ts // Vue 的 TS 适配文件
│ ├── store // 状态管理
│ ├── styles // 样式
│ ├── test // 测试
│ ├── utils // 公共方法
│ └── views // 页面
├── tsconfig.json // TS 配置
└── yarn.lock // yarn 依赖锁定
可以看到,一些基础配置文件都在根目录下,src
目录是源代码的主要目录,可以理解为核心代码都在 src
目录下。而在 src
目录下,有一些子目录,这里是前端项目的核心模块,包括页面(Views)、路由(Router)、组件(Components)、布局(Layouts)、状态管理(Store)等。当然,不是每个项目都是一样的组织形式,可能或多或少会有一些不同,但页面、组件、路由这些通常都是重要的模块需要独立出来。组件一般是可复用的,是页面的组成部分;路由是实现前端导航匹配页面的功能;页面就是各种网页的实际内容。其实,这种代码的组织形式跟传统的后端项目类似,也是将负责不同功能的高内聚的模块独立出来,各自形成同一类型的结构。
这样做的好处在于,开发者可以通过对所要解决的问题进行分类,从而能够精确的定位到相关的模块,快速完成开发任务或解决疑难问题。这就跟整理家里杂物或者文件归档一样,如果经常收拾整理,会让生活或者工作环境变得更井井有条。
当然,也不是说传统的前端项目就没有模块化的概念,只是在 jQuery 时代,如何进行模块化甚至需不需要模块化都没有一个统一标准。如果你在 10 年前做过前端项目,你应该对那些臭不可闻的 “屎山” 代码深有体会。如今,随着前端技术的不断发展,前端行业已经衍生出很多帮助前端工程师进行工程化的工具和方法论,除了本小节的模块化以外,还有接下来将要介绍的前端框架,也就是现在熟知的 React、Vue、Angular 三大框架以及其他一些小众的框架。
前端框架
jQuery(石器时代)
很多时候,一个新事物的诞生和走红绝对不是偶然的。就像 2006 年横空出世的 jQuery 一样,它解决了当时用原生方法操作 DOM 的痛点。jQuery 通过简洁的 API 和高效的性能,成为当时前端界的标配。要说 jQuery 的成功,是建立在当时原生 JavaScript 的缺陷上:啰嗦而臃肿的语法,复杂而难以理解的设计模式,浏览器标准的不统一。这给当时的 Web 开发人员带来了巨大的痛苦。而 jQuery 作为 JavaScript 的一个轻量级的工具库,很大程度上弥补了 JS 的不足,因此得到了大量开发者的欢迎,并且随着互联网产业的发展成为了后来很多年的前端霸主。很多其他的知名第三方库,例如 Bootstrap,也是建立在 jQuery 上的。
在越来越多的开发者开始拥抱 jQuery 时,不少人也开始抱怨它的不足。在用户体验变得越来越重要的趋势下,部分开发者开始将原本一些后端的交互逻辑迁移到前端,以追求更快的响应速度和更流畅的用户体验。而为了达到这个目的,不少开发者开始大量利用 jQuery 通过 Ajax 异步请求来实时渲染前端页面,也就是用 JS 代码来操作 DOM。而这一变化则带来了大量 jQuery 用户的抱怨:他们认为用 jQuery 实现带有复杂交互逻辑的 UI 是一件非常头疼的事情。首先,jQuery 严重依赖于 DOM 元素的 CSS 或 XPath 选择器,这为项目模块化带来了非常大的麻烦:所有的元素对于一个前端应用来说几乎是全局,这意味着 class
、id
等属性对于项目稳定性来说变得异常敏感,因为稍不注意你可能就误改了不该修改的元素,污染了整个应用。其次,jQuery 实现前端交互的原理是直接操作 DOM,这对于简单应用来说还好,但如果前端界面有大量需要渲染的数据或内容,或者要求频繁的改变界面的样式,这将产生非常大的性能问题,将导致浏览器卡顿,严重影响用户体验。第三点,jQuery 没有一个编写前端项目的统一模式,程序员们可以根据自己的喜好随意发挥,这个也是造成前端应用 Bug 横飞的原因之一。总的来说,大量使用 jQuery 来实现前端交互使前端项目变得非常脆弱(Fragile)。
AngularJS(铁器时代)
2009 年,一位 Google 工程师开源了他的业余项目 AngularJS,这个全新的框架给当时被 jQuery 统治的前端行业带来了革命性的进步。当时这位工程师只用 1,500 行 AngularJS 代码在 2 周内实现了 3 人 花了 6 个月开发的 17,000 行代码的内部项目。由此可见这个框架的强大。AngularJS 的主要特点是 HTML 视图(HTML View)与数据模型(Data Model)的双向绑定(Two-way Binding),意味着前端界面会响应式的随着数据而自动发生改变。这个特性对于习惯写 jQuery 的前端工程师来说是既陌生又期待的,因为这种编写模式不再要求主动更新 DOM,从而将节省大量主动操作 DOM 的代码和逻辑,这些 AngularJS 全部帮你自动完成了。下面是 AnguarJS 双向绑定的示意图。
AngularJS(1.0 版本的名称,后来更名为 Angular)能做到一部分组件化(Componentization),但支持得并不完整。利用 Directive 来组件化一些公共模块会显得力不从心。这给后来的两大框架 React(由 Facebook 开发)以及 Vue(由前 Google 工程师尤雨溪开发)带来了灵感和机遇。
React、Vue & Angular(三足鼎立)
2013 年,Facebook 在 JS ConfUS 上公开发布了 React 这个全新前端框架。React 独树一帜,创造了虚拟 DOM(Virtual DOM)的概念,巧妙的解决了前端渲染的性能问题;同时,React 对组件化支持得非常到位。另一方面,React 的数据-视图单向绑定(One-way Binding)很好的解决了一致性问题,也非常好的支持 TypeScript,这能够为开发大型前端项目带来稳定性和健壮性。不过也因此形成了不小的门槛。React 初学者通常需要理解大量的基础概念,例如 JSX,才能顺利的编写 React 应用。
而 1 年以后,后起之秀 Vue 由前 Google 工程师尤雨溪公开发布。它另辟蹊径,在吸收 React 与 AngularJS 优点的同时,也做了一些优秀的创新。Vue 把视图(HTML)、数据模型(JS)和样式(CSS)整合到一个 .vue 文件中,并沿用 AngularJS 的双向绑定,这降低了前端开发的门槛。如果你写过 AngularJS,你会非常容易上手 Vue。另外,Vue 在组件化的支持上也做得非常棒。最近发布的 Vue3 也加入了 TypeScript 的支持,让其能够充分支持大型前端项目。想进一步了解 Vue3 的读者可以关注 "码之道" 公众号(ID: codao),查看历史文章《TS 加持的 Vue 3,如何帮你轻松构建企业级前端应用》,里面有关于 Vue3 新特性以及如何利用它构建大型项目的非常详细的介绍。
自 2.0 版本开始,AngularJS 更名为 Angular,优化了之前的部分不足,而且全面拥抱了 TypeScript。Angular 与 AngularJS 从语法上来看几乎是两个完全不同的框架。Angular 强制要求使用 TS 开发,这使其变得非常适合构建大型前端项目。不过,这也让初学者需要同时学习 TS,以及 Angular 中本身非常复杂的概念,使得其学习曲线非常陡峭。
以下是对目前这三大前端框架的总结。
属性 | React | Vue | Angular |
---|---|---|---|
创始者 | 尤雨溪(前 Google 工程师) | Misko Hevery(Google) | |
首次发布时间 | 2013(距今 8 年) | 2014(距今 7 年) | 2009(距今 12 年) |
国内使用量 | 高 | 高 | 低 |
国外使用量 | 高 | 低 | 中 |
上手难度 | 高 | 低 | 非常高 |
适用项目规模 | 大中型 | 大中小型 | 大型 |
生态丰富度 | 高 | 中 | 低 |
数据绑定 | 单向 | 双向 | 双向 |
TypeScript | 支持 | 支持 | 强制要求 |
文件大小 | 43KB | 23KB | 143KB |
Github Stars | 145k | 158k | 58k |
优点 | 快速,稳定,生态好 | 简单,灵活性高,易学习,文档详细 | 复用性强,强制 TS,适合企业级项目 |
缺点 | 学习成本高,文档更新慢 | 稳定性略差,社区不大 | 复杂,非常高的学习曲线,文档不详细 |
未来展望
React、Vue、Angular 如今在前端领域里已成为前端工程师必须了解、掌握甚至精通的前端框架。不过,随着移动端的崛起,以及新技术的出现,可以预见到这种 “三足鼎立” 的趋势将在未来几年发生巨大的变化。WebAssembly 的出现给前端传统的 HTML/CSS/JS 技术带来了挑战;混合开发(Hybrid Development)、小程序(Mini-Program)、PWA 等技术让前端工程师能够涉足移动端开发;新兴的前端框架,例如 Svelte、Elm.js、ReasonML 等,随着知名度的不断增加,都可能会对现有的技术产生影响。总之,正如本文开头所说的,“The only constant in the world is change”。
组件化
对于现代化前端工程来说,组件化是一个核心概念。组件化实际上是前端 UI 界面的模块化。对于复杂的前端应用来说,很多组件,例如按钮、导航、输入框等,都是可以复用的 UI 模块,可以用在各种各样的页面中。另外,组件之内还可以套用子组件,子组件中还可以套用子子组件,这使得前端界面的组织变得非常灵活。如果你设计妥当,当面对老板或产品经理的需求变更时,例如布局调整、样式变更、功能扩展等,你都可以从容不迫的应对。组件化因为太重要了,每个前端工程师都需要掌握其中的设计原理,不管你使用哪种前端框架。
如果目标是实现一个大型的扩展性强的前端项目,前端工程师除了需要提前组织好代码结构(布局、组件、路由)等,还需要考虑设计一些基础组件,简化后续的开发与维护工作。其中,最基础的组件应该算是布局组件(Layout Components)了,你可能需要设计顶部(Header)、侧边栏(Sidebar)、主容器(Main Container)以及底部(Footer)等等。当然,一些常用的功能组件也需要考虑,例如输入框(Input)、表单(Form)、弹出框(Modal)等等。建议不要重复造轮子,你可以根据自己的框架选择合适的 UI 框架,例如 React 的 Ant Design,Vue 的 ElementUI,只有比较特殊的组件需要自己来实现。
另外,前端工程师也需要非常熟悉如何设计组件间的通信。一般的父子组件间通信可以通过属性传递(Property Propagation)以及事件发射(Event Emission)来实现。对于多层间的通信,可以考虑用 Context 或 Event Bus 来处理。兄弟组件间的通信通常是用状态管理(Store)来实现。
当然,组件化的设计模式并不是千篇一律,前端工程师们一般都从实践和借鉴中积累经验,形成自己的一套知识体系。对于初学者来说,可以看看前人们发布的开源框架,例如 Ant Design 或 ElementUI,里面的源码以及实现过程是公开的,研究里面的源代码可以帮助你有效掌握已经经过项目实践考验的设计理念。
类型约束
JavaScript 在前端开发中扮演着非常重要的角色,是交互逻辑的核心。然而,JavaScript 是一个弱类型动态语言,意味着各种基础类型之间可以互相转化,因此用 JS 编写的程序的稳定性和健壮性比较堪忧。这对于构建大型前端应用来说是个致命的缺点。幸运的是,微软于 2012 年发布了 TypeScript,它可以利用静态代码检测机制将 JavaScript 用强类型约束起来。TypeScript 是 JavaScript 的超集,也就是说,TS 包含 JS 的全部语法和特性,并且在此基础上加入了类(Class)、接口(Interface)、装饰器(Decorators)、命名空间(Namespace)等面向对象编程(OOP)的概念。
对 TypeScript 不熟悉的读者可以参考码之道公众号之前的文章《为什么说 TypeScript 是开发大型前端项目的必备语言》,可以关注 "码之道" 公众号(ID: codao)查看历史文章。
正如 TypeScript 官网上所定义的,TypeScript 是 "Typed JavaScript at Any Scale"(任何规模的类型约束 JavaScript)。TypeScript 的主要特点就是类型系统,它通过加入类型来扩展 JavaScript。如果你了解静态类型(Static Typing)和动态类型(Dynamic Typing)的区别,你应该会清楚静态类型的优势:可预测性,健壮性,稳定性。通过编译,TS 代码可以转化为原生的 JS 代码,因此在浏览器兼容性上,TS 代码没有任何问题。
下面两段段代码可以清楚的理解分别用 纯 JS 和 TS 编写前端应用的区别。首先看纯 JS(没有 TS)的样例代码。
// JS Example
// 没有任何类型定义
// 数据类型约束全靠文档或记忆
// 合法用户实例
const validUser = {
name: 'Zhang San',
sex: 'male',
age: 40,
};
// 非法用户实例,编译时不会报错
const invalidUser: User = {
sex: 'unknown',
age: '10',
};
console.log(invalidUser.name); // undefined
console.log(invalidUser.name.substr(0, 10)); // Uncaught TypeError: Cannot read property 'substr' of undefined
可以看到,整段代码不会报语法错误(Syntax Error),因此可以顺利在 JS 引擎中运行。运行到 console.log(invalidUser.name)
时会打印 undefined
,同样不会抛错。不过这给后面的代码带来了隐患。当执行接下来的一句 console.log(invalidUser.name.substr(0, 10))
时,会报 Uncaught TypeError: Cannot read property 'substr' of undefined
这个在 JavaScript 中非常常见的错误。对于大型项目来说,一旦开发者遇到这样的错误,通常是无从下手的,因为你不知道这个 undefined
是如何产生的:来自后台 API、或来自前面的代码逻辑错误、还是数据问题?
再看看下一段 TS 的代码。
// TS Example
// 性别
type Sex = 'female' | 'male';
// 用户类
interface User {
name: string;
sex: Sex;
age: number;
}
// 合法用户实例
const validUser: User = {
name: 'Zhang San',
sex: 'male',
age: 40,
};
// 非法用户实例. 编译报错
const invalidUser: User = {
sex: 'unknown',
age: '10',
};
console.log(invalidUser.name); // 编译报错
console.log(invalidUser.name.substr(0, 10)); // 编译报错
由于用 type
和 interface
定义了类型,TS 编译器可以在预编译的时候扫描代码提前发现错误,例如缺少字段或者字段非法等等。编译这段代码时,你会发现错误会在编译时提前抛出来。因此开发者能够在运行代码之前提前预警可能的 Bug,并对此采取措施。
TypeScript 几乎是大型前端项目的标配,几乎是每个前端工程师的必备技能。
其他
前端工程化相关的知识还有很多,前文只介绍了一些核心知识。其他的一些前端工程相关的知识还包括但不限于以下内容:
- 代码风格约束。帮助统一代码风格,例如 ESLint。
- 单元测试。有效的避免 Bug,工具包括 Jest、Mocha 等。
- 多项目管理。拆分大型项目为不同的子项目或模块,帮助管理多模块项目,代表工具有 Lerna。
- 项目部署。CI/CD、Nginx 等。
总结
本文从前端行业的发展开始,介绍了关于前端技术发展的历史背景,解释了前端工程化的重要性和必要性,并从打包部署开始,讲解了现代化前端工程的打包部署过程以及实现工具。接下来,本文还介绍了前端工程化的另一个重要概念,模块化,解释了随着前端应用变复杂的趋势,产生的模块化的需求,以及如何实现模块化。之后,还介绍了前端框架的发展,包括最初的 jQuery,和后来的 AngularJS,以及现在的 React、Vue、Angular 三大前端框架,还有未来的前端框架发展展望。除此之外,本文还介绍了组件化和类型约束,解释了为什么要在大型项目中使用 TypeScript。
总之,前端行业是一个高速发展的行业,特别是最近几年,互联网的高速发展催生了前端工程的发展,让前端工程师成为名副其实的工程师岗位。前端工程师之所以能称作工程师,绝不仅仅是因为他们能写前端代码,而是他们能利用架构思维和工程化思维来构建和管理大型复杂的前端应用,让用户能体验到后台功能复杂、但前台使用简单的各种 Web 或移动应用。前端技术以及前端工程化还在继续发展,随着新技术的出现和不断成熟,相信我们还会学习到更多有趣而实用的前端知识。今天入坑前端的你,还学得动么?
社区
如果您对笔者的文章感兴趣,可以加笔者微信 tikazyq1 并注明 "码之道",笔者会将你拉入 "码之道" 交流群。