## 写在最开始 这是一篇面向移动端开发者的科普性文章,从前端开发的最初流程开始,结合示范代码,讨论开发流程的演变过程,希望能覆盖一部分前端开发技术栈,从而对前端开发的相关概念形成初步的认识。 本文会提供一些示范代码,然而他们无法运行,也不需要完全看懂,更多的是方便读者对相关概念和方案有更加具体形象的感受和更清晰的理解。 在写作过程中,我阅读学习了淘宝在前后端分离和前端开发技术演变方面的博客,受益匪浅,相关文章都罗列在文末的参考资料中。同时由于自身能力有限,对很多概念的理解比较浅显,甚至有误,欢迎交流指正。 ## 移动端与前端的区别 在开发 App 的过程中,我们不会刻意思考开发流程,因为一切看上去都非常自然。可以本地确定的内容就直接写死,否则异步发起网络请求并动态的修改,最后把源代码编译成可执行的二进制文件供客户安装。 前端开发和移动端开发就有本质的不同了。一个网页的最终展现样式受到 HTML + CSS 的影响,而 JavaScript 脚本负责用户交互。一个页面不会被编译成可执行文件,它仅仅由几个文本文件组成,由服务端明文下发给浏览器并绘制到屏幕上。 下文中可能会反复提到“渲染”的概念,除非特别说明,它不表示解析 HTML 文档(DOM)并绘制到屏幕上这个过程,因为这一步由浏览器内核实现,普通情况下不需要做过多了解和干预。 网页可以分为静态、动态两种,前者就是一个 HTML 文件,后者可能只是一份模板,在请求时动态的计算出数据,最后拼接成 HTML 格式的字符串,这个过程就被称为渲染。 前端与移动端开发另一个显著差异是: 虽然可以在本地调试 HTML,但实际上这些 HTML 的内容需要部署在服务端,这样才能在用户发起 HTTP 请求时返回 HTML 格式的文本。 ## 前端开发的混沌时代 一开始,我们没有任何工具,只能靠蛮力。我们知道 Servlet 是由 Java 编写的服务端程序,可以方便的处理 HTTP 请求和返回,因此可以直接把 HTML 文本当做字符串返回,也就是上文所说的渲染: ```java public class HelloWorldServlet extends HttpServlet { @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.println("Codestin Search App"); out.println("

Hello World Title

" +new Date().toLocaleString() + "

"); out.flush(); } } ``` 理论上来说,我们已经可以开始所有前端开发了,但在这混沌初开的年代,想写出一份可维护的代码恐怕得用上“洪荒之力”。把 UI 和业务逻辑写在一起是一种非常强的耦合,不利于项目日后的拓展。也无法要求每个人同时会写 Java 和前端。 ## 后端 MVC 上述方案的问题之一在于逻辑混乱不清,移动开发者在入门阶段大多也经历过,解决方案比较简单:使用 MVC,把业务逻辑抽离到 Controller 中,让 View 层专注于显示 UI。 ### MVC 方案实现 在前端开发领域,也有类似的技术,比如 JSP,它经过编译后变成 Servlet。在写 JSP 的时候,我们更加关心页面样式,因此代码看起来就像是 HTML,不过在 `<% %>` 代码块中可以调用 Java 函数: ```jsp Codestin Search App <% out.println("

Hello World!

"); %> ``` JSP 相当于 View 层,它从 Model 中获取数据,说的再具体一点,是使用后端的语言(比如 Java)去访问 Model 层提供的接口。 Controller 作为直接和客户端接触的模块,负责解析请求,数据校验,路由分发,结果返回等等逻辑。路由分发是指根据请求路径的不同,调用不同的 Model 和 View 提供服务。 ### MVC 的缺点与改进 使用了 MVC 架构(比如大名鼎鼎的 Struts)后,似乎职责变清晰了,前端开发者负责写 JSP,后端开发者负责写 Controller 和 Model,然而在实际开发时还是有诸多问题。 首先业务逻辑依然没有严格区分,如果没有良好的编码规范,JSP 中就会混入大量业务逻辑。而一个框架存在的作用应该是让没有接受很多培训的新人也能写出合格的代码。此外,前端开发者还需要对后端逻辑有大致的了解,熟悉后端编程语言,因此也存在很多沟通、学习成本。 ### 前端只写 Demo 一种解决方案是前端开发者只写 Demo,也就是提供静态的 HTML 效果给后端开发者,由后端开发者去完成视图层(比如 JSP)的开发。这样前端开发者就完全不需要了解后端知识了。 可惜这种想法很好,但是一旦付诸实现就会遇到不少问题。首先后端开发者依赖于前端的 Demo,只有看到 HTML 文件他们才可以开始实现 View 层。而前端又依赖于后端开发者完成整体的开发,才能通过网络访问来检查最终的效果,否则他们无法获取真实的数据。 更糟糕的是,一旦需求发生变动,上述流程还需要重新走一遍,前后端的交流依旧无法避免。概况来说就是前后端对接成本太高。 举个例子,在开发 App 的时候,你的同事给你发来一段代码,其中是一个本地写死的视图,然后告诉你:“这个按钮的文字要从数据库,那个图片的内容要通过网络请求获取,你把代码改造一下吧。”。于是你花了半天时间改好了代码,PM 跑来告诉你按钮文字写死就好,但是背景颜色要从数据库读取,另外,再加一个按钮吧。WTF? 显然这种开发流程效率极低,难以接受。 ### HTML 模板 其实一定程度上来说,JSP 可以看做 HTML 模板的雏形。HTML 大体上肩负了两个任务: 页面框架和内容描述。所谓的 HTML 模板是指利用一种结构化的语法,表示出 HTML 的框架,同时把具体数据抽离出来。 比如 `

111

` 表示一个段落,其中内容为 “111”。如果用模板来表示,可以写作 `

Content

` 或者 `p Content` 等等。总之,不要纠结于具体语法,我们只要知道: > 数据 + 模板 = HTML 源码 比如在 Controller 层,可以这样调用: ```js // 这里没有指定模板的名称,是因为使用了依赖倒置的思想,在配置文件中绑定了 Controller 对应的 View return res.view({title: "111"}); ``` 模板中的代码如下: ```js

<%= Content%>

``` 熟悉前端开发的读者可能会发现,这其实采用了 [Sails.js](http://sailsjs.org/) + [EJS](http://www.embeddedjs.com/) 开发。前者是基于 JavaScript 的服务端应用程序,后者是基于 HTML 语法的模板,另一种风格的模板是 [Jade](http://jade-lang.com/),不过本文的目的并不是重点介绍这些工具如何使用,就不再赘述了。 回到模板的概念上来,它相对于 JSP 的优势在于,**利用工具强行禁止前端开发者在视图层写业务逻辑**。前端开发者只需要关心 UI 实现并确定 HTML 中的变量。而后端开发者只要传入参数即可获取 HTML 格式的字符串。 模板开发的另一个好处是前后端可以同步开发。双方约定一个数据格式,前端就可以模拟出假数据并用来自测,后端也可以用生成的数据与假数据对比进行测试。同时,这个约定的数据格式扮演了契约和文档的作用,规范了双方的数据交流形式,从而节省交流的时间成本。关于更多 Mock Server 的话题,请参考 [这个连接](https://www.zhihu.com/question/35436669)。 ### 后端 MVC 架构总结 使用后端 MVC 架构加上模板开发是当前比较主流的一种开发模型,但它也不是完美的。由于模板由前端开发者完成,所以要求前端开发者对后端环境(注意这里不是实现细节)有所了解。 举个简单例子,大型应用的后端要分很多文件夹,这就要求前端对代码组织结构有所了解,上传文件时需要掌握 ssh、vim 并且依赖于服务端环境。 总的来说,采用服务端 MVC 方案时,HTML 在后端渲染,整体开发流程也全部基于后端环境。因此前端工程师不可避免的需要依赖于后端(虽然使用模板后情况已经大大改善)。 ## AJAX 与前端 MVC AJAX 的全称是 Asynchronous Javascript And XML,即 “异步 JavaScript 和 XML”。它并非一个框架,而是一种编程思想。它利用 JavaScript 异步发起请求,结果以 XML 格式返回,随后 JavaScript 可以根据返回结果局部操作 DOM。 AJAX 最大的优点是不用重新加载全部数据,而是只要获取改动的内容即可。这在移动端编程中看上去是天经地义的,而前端开发则需要专门使用 AJAX 来实现,默认情况下网页的任何一处微小改动都需要重新加载整个网页。 类比移动应用就会发现,AJAX 适合做单页面更新,但是不擅长页面跳转,就像你的 app 页面跳转都是新建一个 UIViewController/Activity 而不是直接把当前页面的内容全部换掉。 得益于 AJAX 的提出,HTML 在前端渲染变成了可能。我们可以下载一个空壳 HTML 文件和一个 JavaScript 脚本,然后在 JavaScript 脚本中获取数据,为 DOM 添加节点。 这时候就出现了很多前端的 MVC 框架,比如 Backbone.js,AngularJS(姑且认为MVVM 是 MVC 的变种) 等一堆名词,你可以从 [这里](http://todomvc.com/) 找到他们各自的 Demo。以我相对熟悉的 React 为例: ```html
``` 这段代码不用完全读懂,你只要意识到,引入 React.js 这个框架后,我们就可以脱离 HTML 编程了。所有的逻辑都写在 ` ``` Controllers 负责页面转发与模板渲染,具体的服务转交给 Services 去完成: ```js module.exports = { app : function(req, res) { // 如果有必要,在这里调用 Services 获取数据 return res.view({}); }, }; ``` 这个 Demo 中没有实现 Services,通常它用于和真正的后端进行交互,可以视情况选择 HTTP 或 SOAP,并对返回结果做处理。此外还有 policies/responses 模块分别对 HTTP 请求和返回内容做处理。 前端的相关代码都封装在模板层,能够与 Node.js 无缝接合。 ### 风险控制 虽然我们用增加 Node.js 处理层的方式解决了前后端分离中的一些痛点,但在实际应用中还是需要考虑得更加周全。 新增一层后,势必会导致性能损耗。然而分层本就是一个在衡量得失后做出的权衡,可以通过各种优化把性能损耗降到最低。况且,在 Node.js 这一层还可以使用 BigPipe 来处理多个异步请求。 传统网页在加载页面时,首先获取 HTML,然后获取其中引用的 CSS 和 JavaScript。在服务端准备数据和网络传输过程中,浏览器需要一直等待,而 BigPipe 将页面分成若干小块,虽然每个块的加载逻辑不变,但块与块之间可以形成流水线作业,避免浏览器无意义的等待。 使用 BigPipe 技术在一定场景下可以代替 Ajax 的多个异步请求。具体介绍可以参考 [BigPipe学习研究](http://www.searchtb.com/2011/04/an-introduction-to-bigpipe.html)。 使用 Node.js 后,对前端开发者的技术要求提高了,编码工作量也会相应的增加。然而这都是工程化的必经之路,编码量增加的背后其实是沟通、维护效率的提高。 ## 总结 为了处理前后端复杂的逻辑,我们尝试了使用了后端 MVC 框架来分离业务,用 HTML 模板来分离数据和 UI 样式。使用了 Ajax 技术的网页更适合单页应用,虽然做到了物理层的分离,但在处理多页面时还是有不少问题。 实际上页面渲染本就是前后端共同关心的话题,我们更应该根据业务逻辑进行前后端分离。最终选择了 Node.js,借助它使用 JavaScript 的特性,由前端工程师负责获取数据后的处理和模板的使用,分担了一部分原本逻辑上属于前端,但技术上偏向后端的任务。这种开发模式看上去像是一种倒退,其实是螺旋式的上升与返璞归真。 Node.js 基于事件和异步 I/O 的特性,以及优秀的处理高并发的能力非常适合前后端分离的场景,使用 BigPipe 等技术也足以将分层带来的损耗降到最低。选择 Node.js 做前后端分离并不一定最佳实践,但在目前看来有不错的应用,同时也需要一边探索一边前进。 ## 参考资料 1. [淘宝前后端分离实践](http://2014.jsconf.cn/slides/herman-taobaoweb/#/) 2. [Web 研发模式的演变](https://github.com/lifesinger/blog/issues/184) 3. [前后端分离的思考与实践(一)](http://blog.jobbole.com/65513/)