01 前端工程质量相关前驱知识
1.1 前端工程化简史
1.1.1 “原始社会”
1990年,Tim Berners-Lee(以下简称Tim)以超文本标记语言HTML为基础,在NeXT计算机上发明了最原始的Web浏览器。
1991年,Tim作为布道者在Internet上广泛推广Web的理念,与此同时,美国国家超级计算应用中心(National Center for Supercomputing Applications)对此表现出了浓厚的兴趣,开发了一款名为NCSA Mosaic的浏览器,并于1993年4月发布。NCSA Mosaic的设计初心是方便科学家看文档、传论文,这也是前端中高频词汇document的起源。NCSA Mosaic的浏览器界面如图1-1所示。
1994年,万维网联盟(World Wide Web Consortium)成立,简称W3C。
此时前端的文本语言只有HTML,它存在很多弊病,包括但不限于以下几点。
• 页面内容不支持动态修改。如果需要修改页面任意文本,如标点符号、文字等,那么需要浏览器重新下载页面。
• 缺少数据异步提交和校验的能力。当提交用户注册信息时,如果两次输入的密码不一致,则该校验不能在浏览器完成,浏览器会白屏等待许久,由服务端进行校验,校验失败后返回对应的错误信息。
• 缺乏差异化渲染能力。如果当前网站是一个电商购物网站,网站中数以千万计的商品都需要有对应的商品详情页面,那么只能创建千万个HTML页面,后续如果要做一些修改,则需要同时修改所有页面。
图1-1 NCSA Mosaic 的浏览器界面
这时的互联网并没有划分为前端和后端,因为前后端开发是一体的,开发人员被统称为程序员。从代码工程的角度来看,前端代码是后端代码的一部分,浏览器渲染页面的过程为:后端收到浏览器的请求→发送静态页面→发送到浏览器。此时的网页以HTML为主,是纯静态的网页,网页是“只读”的,信息流只能从服务器向客户端单向流通。
1.1.2 “石器时代”
1994年,PHP诞生了,赋予了开发人员将数据嵌入HTML的能力,从这个时期开始,前端页面逐步从后端模块中脱离出来,有了一定的独立编码能力,但是仅限于视图模块。此时的互联网开始流行通过模板渲染数据的模式,经典的MVC模式如图1-2所示。
• Model(模型层):代表一个存取数据的对象。它可以带有逻辑,在数据变化时更新控制器。
• View(视图层):代表模型数据的可视化,即提供用户交互的界面。
• Controller(控制层):代表数据处理部分,作用于模型和视图上,控制数据流向模型对象,并在数据变化时更新视图,可以分离视图与模型。
图1-2 经典的MVC模式
此时依然没有“前端工程”这一概念,更别提前端工程师了,这时的前端开发人员自称“切图仔”。通常,后端偏重于对数据进行处理,没有太多精力关注页面,因此才会在前端编写页面模板后,让后端代码读取模板、替换变量、渲染出页面。例如,Java的JSP、ASP的ASPX等,以ASPX为例,它是以<%--%>为服务端代码描述的分隔符,示例如下。
<%--%>标签内的代码是在服务器上执行的;Response.Write是向HTML输出流中写东西的ASP代码;now()是返回服务器当前日期和时间的函数。
在这个时期,视图渲染从后端中独立出来,具备了一定的差异化渲染能力。对于购物网站等场景,只需要将差异化的部分使用后端代码进行描述就可以生成,免去了大量的修改工作。
1.1.3 “铁器时代”
1994年,Hakon Wium Lie提出了CSS规范,他联合当时正在设计Argo浏览器的Bert Bos,一起打造了CSS规范的最初版本。紧接着,他们在芝加哥举办的Mosaic and the Web大会上第一次正式提出了制定CSS规范的建议。1995年,他们再次提出这个建议。当时W3C刚刚成立,W3C对CSS规范很感兴趣,为此专门组织了一次讨论会。
1995年,网景工程师Brendan Eich花了10天时间设计了JavaScript语言。起初,这种脚本语言叫作Mocha,后改名为LiveScript,再后来为了借Java语言的名气创造良好的营销效果,最终改名为JavaScript。网景公司把这种脚本语言嵌入Navigator 2.0中,使其能在浏览器中运行。
1996年,微软发布了VBScript和JScript。JScript是JavaScript的逆向工程的实现,内置于Internet Explorer 3中。由于JavaScript与JScript的实现存在差别,导致程序员开发的网页不能同时兼容Netscape Navigator和IE(Internet Explorer)浏览器。IE开始抢夺Netscape Navigator的市场份额,这导致了“第一次浏览器战争”。
1996年11月,为了确保JavaScript的市场领先地位,网景将JavaScript提交到欧洲计算机制造商协会(European Computer Manufacturers Association,ECMA),以便将其国际标准化。1996年12月,W3C推出了CSS规范的第1个版本,即CSS 1.0。至此,HTML描述页面结构、CSS描述页面样式、JavaScript响应用户交互的三大基石地位确立,前端的雏形逐步形成。但是此时的前端还存在一个致命的问题——没有独立获取数据的能力。
随着“第一次浏览器战争”的打响,前端被推动着快速发展,各种规范和技术内容呈井喷式发展。
1997年1月,HTML3.2作为W3C推荐标准发布。
1997年6月,ECMA以JavaScript语言为基础制定了ECMAScript 1.0标准规范。ECMA以JavaScript语言为基础制定了ECMAScript标准规范ECMA-262。JavaScript是ECMAScript规范最著名的实现语言之一,除此之外,ActionScript和JScript也都是ECMAScript规范的实现语言。自此,各浏览器厂商开始逐步落实ECMAScript规范。
1997年12月,HTML 4.0规范发布。
1998年5月,W3C推出了CSS 2.0。CSS 2.0是一套全新的样式表结构,是由W3C推行的,同以往的CSS 1.0或CSS 1.2完全不一样,CSS 2.0推荐的是内容和表现效果分离的方式,HTML元素可以通过CSS 2.0的样式控制显示效果,可完全不使用以往HTML中的table和td来定义表单的外观和样式,只需使用div和li等HTML标签来分割元素,之后即可通过CSS 2.0样式来定义表单界面的外观。
1998年6月,ECMAScript 2规范发布,并通过ISO生成了正式的国际标准ISO/IEC 16262。
1999年12月,ECMAScript 3规范发布,在此后的10年间,ECMAScript规范基本没有变动过。ECMAScript 3成为当今主流浏览器使用和实现范围最广的语言规范基础。1996年,微软推出用于异步数据传输的ActiveX,随即各大浏览器厂商仿造其实现了XMLHttpRequest,也就是AJAX(Asynchronous JavaScript and XML)的雏形,它改变了前端页面要想获取后台信息必须刷新整个页面的局面。这时的网页开始逐步脱离后端的模板渲染,通过AJAX自行拉取数据进行渲染,再结合JavaScript和CSS进行页面逻辑和样式的实现。前端已经由最开始的纯静态页面展示向动态页面发展,页面的交互开始变得更加友好、Navigator多元,样式更加美观,前端也开始具有数据处理的能力。“第一次浏览器战争”以IE完胜Netscape Navigator告终,IE开始统领浏览器市场,在2002年达到了96%的市场份额最高峰。随着“第一次浏览器战争”的结束,浏览器的创新也随之减少。
IE在“第一次浏览器战争”中击败Netscape Navigator赢得胜利,垄断了浏览器市场。作为“独裁者”,IE并不遵循W3C的标准,IE成了事实标准。
2004年11月,Firefox首次发布,其9个月的下载量超过6000万,IE的主导地位首次受到了挑战,“第二次浏览器战争”爆发。2008年年底,Firefox的市场份额达到了25%以上,IE则跌至65%以下。
时间继续推进,Google分别在2004年和2005年发布了两款重量级的Web产品:Gmail和Google Map。这两款Web产品都大量使用了AJAX技术,不需要刷新页面就可以让前端与服务器进行网络通信,这种技术在当今看来是理所应当的,在十几年前却是革命性的,彻底颠覆了用户体验。随着越来越多的网站使用AJAX获取数据,动态页面火速席卷整个互联网界。
2005年2月,Adaptive Path公司的Jesse James Garrett在网上发表了一篇名为“Ajax:A New Approach to Web Applications”的文章。这篇文章大大提高了人们对该项技术的认识,同时AJAX对Mozilla/Gecko的支持使得该技术走向成熟,变得更为简单易用。
2006年,AJAX被W3C正式认定为前端开发的标准。从此,前端不再仅仅是后端的模板,它可以通过AJAX独立得到各种数据。
在“第二次浏览器战争”中,随着以Firefox和Opera为首的W3C阵营与IE对抗的加剧,浏览器碎片化问题越来越严重,不同的浏览器执行不同的标准,这对于开发人员来说是一个噩梦。为了解决浏览器兼容性问题,Dojo、jQuery、YUI、ExtJS、MooTools等前端框架相继诞生。前端开发人员用这些框架频繁发送AJAX请求到后台,在得到数据后,再用这些框架更新DOM树。
jQuery凭借其简单的使用方式和强大的兼容能力受到了诸多开发人员的追捧,快速风靡全球。大量基于jQuery的插件构成了一个庞大的生态系统,Dojo、YUI、ExtJS等提供了很多组件,进一步确立了jQuery的王者地位,使其成为当时前端开发人员的首选。同时,大量的社区生态使得开发人员能基于这些模块进行复用,进一步开发更加复杂的Web应用。截至2021年,依然有很多Web应用使用jQuery进行开发。
1.1.4 “工业化时代”
1999年,W3C发布了HTML 4.01版本,在之后的几年,没有发布新的Web标准。随着Web的迅猛发展,旧的Web标准已不能满足Web应用的快速发展需求。
2004年6月,Mozilla基金会和Opera软件公司在万维网联盟(W3C)主办的研讨会上提出了一份联合建议书,其中包括Web Forms 2.0的规范草案,建议投票表决W3C是否应该扩展HTML和DOM,从而满足Web应用中的新需求。最后以8票赞成、14票反对否决此建议,这引起一些人的不满。不久,部分浏览器厂商宣布成立网页超文本应用技术工作小组(WHATWG),以继续推动该规范的开发工作,该组织再度提出Web Applications 1.0规范草案,后来这两种规范合并形成HTML 5。2007年,HTML 5被W3C接纳,成立了新的HTML工作团队。2008年1月22日,第一份正式草案发布。
2008年12月,Google发布Chrome浏览器,加入了“第二次浏览器战争”。Chrome使用Safari开源的WebKit作为布局引擎,并研发了高效的JavaScript引擎V8。在“第二次浏览器战争”中,各个浏览器厂商都以提升JavaScript运行效率和支持HTML 5新特性为主要目标,促进了浏览器的良性竞争。在这场“战争”中,Chrome攻城略地,抢夺IE市场份额。2013年,Chrome超过IE,成为市场份额最高的浏览器。2016年,Chrome占据了浏览器市场的半壁江山。自2008年以来,浏览器中不断推出的支持HTML 5的新特性让开发人员激动不已:Web Worker可以让JavaScript运行在多线程中;WebSocket可以实现前端与后台的双工通信;WebGL可以创建Web3D网页游戏……
随着HTML 5的流行,前端逐渐在互联网中占据了举足轻重的地位,很多以前在C/S中实现的桌面软件的功能逐步迁移到了前端,前端的代码逻辑逐渐变得复杂起来。面对需要处理的海量数据和成百上千个需要维护的复杂页面,使用jQuery的开发人员开始显得力不从心,部分原因如下。
• 频繁的DOM操作严重降低性能,会导致页面卡顿甚至不再响应。
• jQuery的操作强依赖标签选择器和DOM树结构,如果修改了页面结构或者标签选择器,那么将牵一发而动全身。
• 插件无法有效隔离,容易导致冲突。插件曾经是jQuery登顶前端的重要生态组成部分,然而当开发人员在同一页面上使用多个插件时,很容易导致冲突,尤其是在这些插件依赖相同事件或标签选择器时最为明显。这不是jQuery自身的问题,但又确实是一个难于调试和解决的问题,因为插件之间的标签选择器没有很好的隔离方式。
• jQuery诞生的初衷是为了抹平浏览器之间的差异,但是面对越来越强大的ECMAScript和不断进化的JavaScript,jQuery的优势越来越小。例如,ECMAScript新定义的函数已经足以覆盖大部分jQuery的选择器模块了。
这个时候开始涌现出各种各样的前端框架,如Knockout、Backbone、Ember等,以及当下依然是前端三大主流框架的Angular、React、Vue,它们凭借强大的功能和丰富的开源生态,成为前端开发人员的首选。随着这些框架的出现,网页逐渐由Web Site模式演变成了Web App模式,最终的体现就是复杂的单页应用(Single Page Application)。
前端历史上的第一次重大突破是AJAX的诞生,它使得前端摆脱了依赖后端模板渲染获取数据的局面;第二次重大突破就是Node.js的诞生。
早在1994年,Netscape就公布了其Netscape Enterprise Server中的一种服务器脚本实现,叫作LiveWire,是最早的服务器端JavaScript,甚至早于浏览器中的JavaScript。对于这门图灵完备的语言,Netscape很早就开始尝试将它用在后端上。
微软在1996年发布的IE 3.0中内嵌了自己的JScript语言,其兼容JavaScript语法。1997年年初,微软在它的服务器IIS 3.0中也内嵌了JScript,这就是我们在ASP中能使用的脚本语言。
1997年,Netscape为了用Java实现JavaScript而创建了Rhino项目,最终Rhino演变成一个基于Java实现的JavaScript引擎,由Mozilla维护并开源。Rhino可以为Java应用程序提供脚本能力。2006年12月,J2SE 6将Rhino作为Java默认的脚本引擎。
SpiderMonkey是Mozilla用C/C++语言实现的一个JavaScript引擎,从Firefox 3.5开始作为JavaScript编译引擎,并被CouchDB等项目作为服务端脚本语言使用。
JavaScript从诞生之初就能同时运行在前、后端,但在前、后端的待遇不尽相同。随着Java、PHP、.Net等服务器端技术的风靡,与前端浏览器中的JavaScript越来越流行不同,服务端的JavaScript日渐式微。
2008年,Chrome发布,其JavaScript引擎V8的高效执行能力引起了Ryan Dahl的注意。2009年,Ryan利用Chrome的V8引擎打造了基于事件循环的异步I/O框架——Node.js。Node.js具有以下特点。
• 基于事件循环的异步I/O框架,能够提高I/O吞吐量。
• 单线程运行,避免了多线程变量同步的问题。
• 使得JavaScript可以编写后台代码,前后端编程语言统一。
Node.js的出现吸引了很多前端开发人员用JavaScript编写服务器代码,其异步编程风格也深受开发人员的喜爱。Node.js的伟大不仅在于它拓展了JavaScript在服务器端的无限可能,更重要的是它构建了一个庞大的生态系统。
2010年1月,NPM作为Node.js的包管理系统首次发布,前端开发人员可以按照CommonJS的规范编写Node.js模块,然后将其发布到NPM上面供其他开发人员使用,大大促进了前端的生态繁荣和快速发展。截至2021年6月,NPM具有165万个左右的模块,每月被下载约124亿次,是世界上最大的包模块管理系统。
在此之前,JavaScript不是一种模块化编程语言,因为它并不支持类(class)和模块(module),这导致前端开发人员在使用JavaScript开发时,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。随着前端的发展,开发人员对模块的需求越来越大,很多语言都具有了这项功能,例如,Ruby的require、Python的import,就连CSS都有@import。JavaScript在这方面的缺失对开发大型、复杂的项目形成了巨大阻碍。针对这一问题,开源社区制定了三种模块加载方案——CommonJS、AMD、CMD,为了兼容这三种模块的加载方案又开发了UMD,它是一种兼容的写法,主要通过判断module、define.amd、define.cmd等关键词执行对应的加载方案。直到ES6诞生,ES6从语言标准的层面上实现了模块功能,完全可以取代CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。
针对巨型的页面应用,开发人员设计了多元化的视图框架和模块管理方案。由于开发人员不断增加,开发和保障协作效率的难度也随之增加,工程的质量和可维护性遇到了挑战。
• JavaScript是一门弱类型的语言,且属于动态类型,它的类型系统非常薄弱,甚至可以说没有类型系统。这一特点使得JavaScript编码灵活多变,却丢失了类型系统的可靠性。诸如'1'+2='12的隐式转换常常在系统中埋下隐患。
• 在实际开发中,对于JavaScript定义的变量,IDE/编辑器无法进行智能提示,大大降低了开发效率。
• 缺少类型检测导致的另一个问题是公共变量的关键字段被修改后,只能依赖开发人员人工扫描,低效且质量没有保障。
诸如此类的问题开始大面积出现,业界迫不及待地需要一种方案去解决这些问题。2021年10月,微软发布TypeScript公开版。TypeScript是由微软开发的自由和开源的编程语言,为开发大型应用而设计。它是JavaScript的一个超集,添加了可选的静态类型和基于类的面向对象编程。TypeScript扩展了JavaScript的语法,它在编译后会生成JavaScript,以确保兼容性,所以任何已有的JavaScript程序都可以直接在TypeScript下工作。
随着前端的各项功能被逐渐补全,前端工程化这一概念出现。前端工程化指使用软件工程的技术和方法将前端的开发流程、技术、工具、经验等规范化、标准化,其主要目的是提高效率和降低成本,即提高开发过程中的效率,减少不必要的工作。
webpack、Rollup、Parcel等各式各样的前端工程化打包工具开始出现,基于这些工具,开源社区衍生了一系列的插件生态,大大提升了前端工程化的能力。各式各样的前端工具也如雨后春笋一般冒了出来。例如,用于JavaScript编译转换的Babel,用于CSS转换、预处理的PostCSS,用于单元测试的Jest,用于网络请求处理的Axios、Request等,这些开源工具的出现大大提高了前端工程的质量和开发效率。
在工程化的质量和开发效率得到进一步提升后,前端不再局限于PC端网页这一领域。随着智能手机被广泛使用,移动浏览器开始逐步发展,对HTML 5的支持程度也越来越高。同时,人们生活习惯的改变导致流量逐渐从PC端迁移到移动平台。相比Native App,移动Web开发成本低、跨平台、发布周期短的优势明显,但是Native App的性能和UI体验远胜于移动Web。移动Web与Native App孰优孰劣的争论愈演愈烈,经过无数次的实践,开发人员发现不应将两者定位为对立关系,而是应该将两者结合起来,取长补短,因此,Hybrid技术逐渐得到广大开发人员的认可。Hybrid技术指利用Web开发技术,调用Native相关API,实现移动与Web的有机结合,既能体现Web开发周期短的优势,又能为用户提供Native的体验,这一技术也是当今App开发的主流。
随着React被大规模应用,Facebook越发感受到React及Web技术的优势,希望Native的开发速度也能像Web一样快。
• 快速的迭代周期(Rapid iteration cycle):Web支持高频发布,产品迭代周期更短。
• 即时的测试反馈(Immediate testing feedback):Web发布立即触达用户,A/B测试等实验结果立等可取,产品演进更快。
• 快速的测试反馈(Rapid development velocity):刷新浏览器即可生效,不必等待重新编译App。
为了实现这个目标,Facebook先后尝试了3种方案。2013年,React Native从Facebook内部的黑客马拉松(hackathon)中诞生,是第三种方案的最终成果。类似的解决方案还有Weex、Flutter等。
除了移动端开发,前端也不满足于浏览器作为容器的局面。浏览器的沙箱机制让很多桌面应用程序具备了传统PC Web页面没有的功能。传统的桌面GUI应用程序由于对操作系统存在依赖,所以难以跨平台。2013年,Atom编辑器问世,它的底层框架Electron也逐渐被熟知。在2014年春季被开源时,Electron还叫作Atom Shell,直到2015年4月,它才被正式更名为Electron。它可以用于构建具有HTML、CSS、JavaScript的跨平台桌面应用程序,并通过将Chromium和Node.js合并到同一个运行环境中来实现这一点,应用程序可以打包到Mac、Windows和Linux系统上,进一步延伸了前端的能力。
伴随着国民级应用微信推出小程序功能,各大厂商也争相推出自己的小程序标准,一时间,支付宝小程序、百度小程序等小程序应用充斥前端圈,前端开发人员疲于应付各种小程序的标准,针对同一功能维护多套代码。为了提高开发效率,mpvue、Taro等框架相继问世,成功解决了这一问题。截至2021年,前端已经形成了非常庞大的生态系统。
• 服务端:Node.js、Deno等。
• 跨平台开发:React Native、Weex、Taro、mpvue、Electron等。
• 打包构建工具:webpack、Gulp、Rollup、ESbuild、Parcel等。
• CSS预编译语言:Less、Sass、Stylus等。
• 前端开发框架:React、Vue、Angular、San等。
• 状态管理:MobX、Redux、Flux等。
• 请求库:Axios、Fetch、Request等。
• 基础组件库:Ant Design、Element UI、Vant等。
• 可视化:Three.js、D3、G2等。
• 测试工具:Jest、Mocha、Karma等。
• IDE:Atom、VS Code、Sublime Text、Web Storm等。
• 代码质量:JSLint、CSS Lint、ESLint等。
• 包管理工具:NPM、yarn、pnpm等。
总的来说,前端的整个生态领域已经形成了工程化的体系,各种工具能有效保障前端的研发效率和质量。在这个阶段,基础能力趋于饱和,前端开发人员对开源社区的贡献大多数是对现有功能的优化和升级,缺少像AJAX和Node.js这种跨时代的产品。
1.1.5 未来的方向——智能化
“前端智能化”是一种新的概念和技术,当前,5G、人工智能、大数据、云计算、物联网、预测性维护、机器视觉、地理信息等技术以各种各样的形式被应用到前端领域。JavaScript作为一门编程语言,之所以有如此蓬勃的生命力,是因为前端开发人员不断突破、创新,不断拓展前端的边界,将新兴的技术不断应用到前端。
从交互层面来看,传统前端依赖键盘、鼠标等设备在用户和页面之间传递信息。例如,登录时用户需要输入用户名、密码、验证码等。而随着人脸识别技术的普及,用户甚至不需要输入任何一个字符,直接使用摄像头即可进行认证登录。
前端研发的成本主要花费在页面实现和业务逻辑上,其中页面实现主要依赖开发人员对UI设计稿进行理解分析,使用HTML+CSS进行还原。这是一项烦琐的工作,但是又不得不做,耗费了开发人员极大的精力,还可能存在一些细节还原不到位的情况。因此,直接将设计稿生成代码,不仅是前端工程师的梦想,也是很多设计师的期望。2017年,一篇名为“pix2code:Generating Code from a Graphical User Interface Screenshot”的论文横空出世,立刻引发了广泛关注。pix2code将设计图与对应的DSL描述通过深度神经网络进行训练,给出一张新图并通过推理得出一个新的DSL描述,再通过代码生成器变成目标平台上的代码。
当计算机软件和互联网行业的流量达到一定程度后,为了控制成本、增加营收,企业就会探索更多的方向,比如No Code和Low Code技术方案。例如,对于2020年的“双11”活动,淘宝内部成立了研发效率专项团队,可以通过设计稿生成代码的D2C(Design to Code)平台imgcook提升了研发效率,承接了90.4%的新模块代码的智能生成任务,代码可用率达到79.26%,相比传统模块的开发模式,通过设计稿生成代码技术将编码效率(模块复杂度和研发耗时比)提升了68%,固定人力单位时间模块需求吞吐量增加约1.5倍。
总的来说,前端会朝着智能化发展,不断结合新兴技术,产生新的提效工具,将开发人员从烦琐的工作中解放出来,做更多有意义的研发和探索工作。前端智能化是把智能化应用到前端,把人工智能技术工程化、业务化,最终落地到实际生活中,为人民生活带来便利。
前端智能化是前端技术生态土壤上长出的新物种。在前端技术内部,前端智能化可以丰富技术视角、技术思想、技术手段。在前端技术外部,前端智能化可以拓展更多应用场景、赋予业务更多价值,缩短在开发效率上和其他软件技术的差距。
前端智能化不会导致前端开发人员失业,只会推进前端的发展,这是因为程序设计的复杂度永远不会消失,只会转移。动态化能力越强,就要求整个架构和程序在设计实现之初越灵活,研发过程越复杂。在前端智能化过程中要摒弃一些重复、低效、无意义的工作,智能化引擎本身的设计、实现及优化还是要依赖前端工程师。作为前端开发工程师,对于新兴的技术应该持续保持新奇、包容的态度,只有这样才能不断地推陈出新,拓展能力边界。