全栈式JavaScript

  现在,在建立一个Web应用的过程当中,你须要作出许多架构方面的决策。固然,你会但愿作的每个决定都是正确的:你想要使用可以快速开发的技术,支持持续的迭代,最高的工做效率,迅速,健壮性强。你想要精益求精而且足够敏捷。你但愿你选择的技术可以在短时间和长期上都让你的项目取得成功。但这些技术都不是垂手可得就能选出来的。javascript

  个人经验告诉我,全栈式JavaScript符合了这全部的要求。可能你已经发现了些许端倪,又或许你已经在考虑它的实用性,而且在和朋友讨论争论它的话题。可是你是否亲自尝试过呢?在这篇文章中,我会对于全栈式JavaScript给出一个比较全面的介绍,为何它会是正确的选择,它又是如何施展它的魔法的。php

  先给出一个归纳预览:css

toptal-blog-500-opt

  接下来我会一项一项地介绍这些组件。可是在这以前,咱们简短地回顾一下,咱们是如何发展到如今的这个阶段的。html

  我为何选择用JavaScript

  从1998年开始,我就是一个Web开发者。当时,咱们使用Perl进行大多数的服务器端的开发;可是从那时候开始,咱们就在客户端使用JavaScript。Web服务器端的技术已经发生了翻天覆地的变化:咱们被一波又一波的技术潮流推着往前走,PHP,ASP,JSP,.NET,Ruby,Python,这里只列出了几个例子。开发人员们开始意识到,在服务器端和客户端使用不一样的语言使得事情变得复杂化。前端

  在早期的PHP和ASP的时代,那个时候模板引擎还仅仅是个设想,开发人员们在HTML中嵌入他们的应用代码。咱们常常能够看到下面这种脚本嵌入的写法:html5

1
2
3
4
5
6
7
8
9
<script>
     <?php
         if ($login == true ){
     ?>
     alert( "Welcome" );
     <?php
         }
     ?>
</script>

  或者更糟糕:java

1
2
3
4
5
6
7
8
9
10
11
<script>
     var users_deleted = [];
     <?php
         $arr_ids = array( 1 , 2 , 3 , 4 );
         foreach($arr_ids as $value){
     ?>
     users_deleted.push( "<php>" );
     <?php
         }
     ?>
</script>

  对于新手来讲,很容易被不一样语言之间的用法而混淆,犯下一些很典型的错误,好比for和foreach。更为不爽的是,以这样的方式来写代码,使得服务器端和客户端很难以很是和谐的方式处理相同的数据结构,即便是今天也是如此(固然除非你的开发团队有专职的前端和后端工程师 — 但即便他们之间可以共享信息,但仍然不能仅仅基于对方的代码进行合做)。node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
     $arr = array( "apples" , "bananas" , "oranges" , "strawberries" ),
     $obj = array();
     $i = 10 ;
     foreach($arr as $fruit){
         $obj[$fruit] = $i;
         $i += 10 ;
     }
     echo json_encode(obj);
?>
<script>
     $.ajax({
         url: "/json.php" ,
         success: function (data){
             var x;
             for (x in data){
                 alert( "fruit:" + x + " points:" + data[x]);
             }
         }
     });
</script>

  最初,对于统一使用一种编程语言的尝试是使用后台的语言编写客户端的组件,而后编译成JavaScript。但这种方式并无如指望的同样很好地工做,许多相关的项目都失败了(好比被ASP MVC取代了的ASP.NET Web forms, 又好比正在逐步被Polymer取代的GWT)。固然这些想法都是伟大的,从本质上讲,都是想在服务器端和客户端使用同一种语言,让咱们能够重用一些组件和资源(注意这里的关键词:资源)。nginx

  最终得出的答案很简单:将JavaScript放到服务端git

  其实JavaScript诞生之初是在网景公司的企业及服务器的服务端,只是当时它尚未彻底准备好。通过数年的磨炼和错失,最终Node.js出现了,它不只将JavaScript放到了服务器端,同时也推广了非阻塞式编程(non-blocking programming)的思想,这种思想来自于nginx的世界。感谢Node的创始者们nginx的技术背景,而且继续(聪明地)保持了它的简单性,也感谢JavaScript天生的事件轮询机制。

  (一句话归纳,非阻塞式编程目的在于将消耗时间的任务放到一边,经过指定在这些任务结束时须要作的操做,这样能够在同一时刻让处理器去处理其余的请求。)

  Node.js永久性地改变了咱们处理I/O访问的方式。做为Web开发者,咱们过去一直使用以下的方式访问数据库(I/O):

1
2
var resultset = db.query( "SELECT * FROM 'table'" );
drawTable(resultset);

  这里的第一行代码本质上已经阻塞了你的代码,由于你的代码中止下来等待数据库驱动返回一个结果集(resultset)。而与此同时,你的平台架构其实给你提供了并发的方法,一般是经过线程(threads)和派生(forks)。

  在Node.js和非阻塞式编程的帮助下,咱们能够更多的控制咱们程序的执行流。如今(尽管在数据库I/O驱动器的背后可能已经有并行执行),你能够定义你的程序在I/O操做期间并行作的事情,以及在接收到结果集以后作的操做。

1
2
3
4
db.query( "SELECT * FROM 'table'" , function (resultset){
    drawTable(resultset);
});
doSomeThingElse();

  上面的代码片断中,咱们定义了两个程序流:第一个在咱们发出数据库查询以后执行的操做,第二个是以回调的方式在咱们接收到结果集以后作的操做。这是一个很是优雅而且强大的处理并发的方式。正如他们所说的,“一切都在并行执行——除了你的代码。(Evetything runs in parallel — except your code.)”这样,你的代码会更易写,有更高的可读性,容易理解,也便于维护,这些都基于你找回了对程序流的控制。

  这些观点早就不是很新的观点,那为何他们随着Node.js变得如此流行起来。很简单:非阻塞式编程能够有多重实现的方式。但可能最简单的就是使用回调和事件轮询。在大多数于语言里,作到这点并非一个简单的事情。回调机制在其余的一些于语言里是一个比较常见的功能,可是事件轮询却不是。你会常常发现本身还须要在一些扩展库上作挣扎(好比,Python中使用Tornado)。

  可是在JavaScript中,回调机制已经被内建在语言中, 事件轮询也是如此。而对JavaScript稍有了解的程序员对它们也很是熟悉(或者至少使用过它们,即便他们有可能并不彻底理解什么是事件轮询)。忽然之间,地球上全部的创业公司均可以在客户端和服务器端重用开发人员(或者资源),解决了“须要Python大师(Python Guru Needed)”的招聘发布问题

  所以,如今咱们有了一个发展迅速的平台(感谢于非阻塞式编程),和一个很是易于使用的语言(感谢JavaScript)。可是这就足够了吗?它是可持续的吗?我确信,JavaScript在未来会有一个很是重要的地位。下面我来告诉你为何。

  函数式编程

  JavaScript是第一个将函数式范式带给民众的语言(固然,Lisp第一个出现,可是大多数的程序员都没有使用它开发过一个能够做为产品的应用)。Lisp和Self,这两个深深影响了JavaScript的语言,充满了创新的理念,它们解放了咱们的思想,去挖掘新的技术,模式和规范。这些都延续到了JavaScript上。看一下mondasChurch number, 或者甚至(做为更有实践性的例子)UnderscoreCollections functions,这些能够节约你一行又一行的代码。

  动态对象以及原型继承

  没有类(Classes),也没有无穷无尽的类层次结构的面向对象(Object-oriented)编程是提供了更快速的编程体验——只要建立对象,添加方法而后使用他们。更重要的是,它大大减小了维护时重构的成本,由于它容许程序员直接修改对象的实例,而不须要修改类。这种速度和灵活的方式为快速开发铺平了道路。

  JavaScript就是互联网

  JavaScript是因互联网而生的。它从一开始就出现了,而且伴随到如今。任何想要摧毁它的尝试都以失败而了结,好比Java Applets的衰落,VBScript被微软的TypeScript(它最终会被编译成JavaScript)所取代,以及Flash在手机市场以及HTML5上的一败涂地。若是想不破坏成千上万个Web页面而取代JavaScript是不可能的,因此咱们接下来的目标应该是提升和完善它。这个工做,没有谁比ECMA的Technical Committee 39更适合了。

  固然,JavaScript的替代者们天天都在诞生,好比CoffeeScriptTypeScript,以及成千上万能被编译成JavaScript的语言。这些替代者们在开发过程当中也许是有用的(经过source maps),可是他们最终都不可能成功地代替JavaScript,两个主要缘由:他们的社区永远不会比JavaScript更大,他们中的优秀特性会被ECMAScript(也就是JavaScript)所吸取。JavaScript不是汇编语言,它是一个你能理解代码的高级编程语言——因此你应该理解它。

  端到端(End-to-End)JavaScript:Node.js和MongoDB

  咱们已经介绍了为何要使用JavaScript。接着,咱们来看看使用Node.js和MongoDB的理由。

  NODE.JS

  Node.js是一个搭建快速和可扩展的网络应用的平台——正如Node.js网站上所说。可是Node.js远不止这些:它是现在最火的JavaScript运行环境,被大量的应用和程序库所使用——甚至是浏览器的库代码也运行在Node.js上。更重要的是,这种服务器端的快速执行让程序员能够专一于更复杂的问题,好比作天然语言处理Natural。即便你并无计划用Node.js来写你的服务器端应用,你也有可能使用基于Node.js的工具来改进你的开发流程。举例来讲:用Bower来作前端包依赖管理,Mocha作单元测试,Grunt作自动化打包,甚至用Brachets作全文代码编辑。

  所以,若是你正准备开发服务器端活客户端的JavaScript应用,你就须要对Node.js更加熟悉,由于你在平常工做中会须要他。有一些颇有趣的代替的选择,可是它们中的任何一个的社区都不及Node.js的10%。

  MONGODB

  MongoDB是一个基于文档(Document-based)NoSQL数据库,它使用JavaScript做为它的查询语言(可是它不是用JavaScript写的),它完善了咱们端到端的JavaScript平台。可是这个并非咱们选择MonoDB的主要缘由。

  MongoDB 是无模式的(schema-less),容许你以很是灵活的方式把对象持久化,所以可以迅速的应对需求变动。此外,它具备高度可扩展性,而且基于map-reduce,让它很是适合于大数据的应用。MongoDB如此灵活,以致于它既能够用做无模式的文档数据库,也能够用做关系数据存储(尽管它缺乏事务,只能经过模拟来实现),甚至是用来缓存结果的键值对存储,就像MemcachedRedis

  基于Express的服务器端组件化

  服务器端的组件化开发一直不是一件容易的是。可是 Express(和Connect)带来了“中间件(middleware)的思想”。在我看来,中间件是服务器端定义组件最好的方式。若是你想找个熟悉的模式来对比一下的话,那它很是接近于管道和过滤器(pipes and filters)。

  基本思想就是将你的组件做为管道的一部分。管道处理一个请求(也叫输入),生成一个结果(也叫输出),可是你的组件并不负责整个响应结果。相反,它只作它须要作的修改,而后将委派给下管道的下一节点。当管道的最后的节点处理完以后,这个结果再返回给客户端。

  咱们称这些管道的节点为中间件。很明显,咱们能够建立两种类型的中间件:

  • 中间型(Intermediates)
    一个中间型节中间件理请求和响应,可是它不负全权责整个响应,而是继续将它们分派给下一个中间件。
  • 终结型(Finals)
    一个结束型中间件负责最终的响应结果。它对请求和响应进行处理,以后不会分派给下一个中间件。但实践中,继续分派给一个中间件能够给架构带来更高的灵活性(好比,以后须要增长其余的中间件),即便下一个中间件并不存在(这种状况下,结果会直接被传递到客户端)。

user-manager-500-opt
(Large view)

  取一个具体的例子,假设服务器端有一个“用户管理”的组件。根据中间件的方式,咱们最好能有终结型和中间型的中间件。对于终结节点,咱们要有建立用户和列出用户的功能。可是在咱们作这些操做以前,咱们须要使用中间节点来作认证(由于咱们不但愿没有认证过的请求能进来,甚至建立用户)。一旦咱们建立好了这些认证中间件,当咱们想要把一个原先不须要认证的功能改变成认证功能的时候,咱们只须要将这个中间件安插在相应的位置。

  单页面(Single-Page)应用

  当你使用全栈式JavaScript的时候,多数状况下你会专一开发单页面应用。大多数的Web开发者们都禁不住不止一次地尝试着着手于单页面应用。我已经建立了几个(多数为我的的),我相信他们就是Web应用的将来。你是否在移动连接上对比过单页面应用和一般的Web应用?他们在响应速度的差距有数十秒之多。

  (注意:有些人可能不一样意个人观点。好比Twitter,回滚了他们的单页面途径。与此同时,不少大的网站正在步入单页面时代,好比Zendesk。我已经看到足够的证据证实单页面应用带来的好处,而且对此深信不疑。可是具体仍是因状况而异。)

  若是单页面应用如此强大,那为何仍是要选择老土的方式来建立你的应用呢?我常常听到的一种争论就是他们担忧SEO(Search Engine Optimization)。可是若是你对此作了正确的处理,这将不是一个问题:你能够有多种解决方式,从使用无界面的浏览器(headless browser),好比PhantomJS,在检测到网络爬虫的时候渲染HTML,到使用一些现有框架执行服务器端渲染

  基于Backbones.js,Marionette和Twitter Bootstrap的客户端MV*模式

  关于使用MV*框架开发单页面应用已经有太多的讨论了。尽管很难选择,可是我想说排名前三的是Backbone.jsEmberAngularJS

  这三个都是很是被推崇的,但哪一个是最适合你的

  不幸的是,我必须得认可我在AngularJS上的经验有限,因此我就把它放在讨论范围以外。那么,Ember和Backbone.js表明了解决同一问题的两种不一样方式。

  Backbone.js很小,可是恰到好处的提供了建立一个简单的单页面应用所须要的功能。另外一方面,Ember是一个建立单页面应用的完整且专业的框架。它有更多的辅助工具,可是也有更加陡峭的学习曲线。(你能够阅读更多关于Ember.js的内容。)

  基于你的应用的大小,能够简单地经过比较“须要的功能”占“可用的功能”的比例来作出决定,它会给你很大的提示。

  样式设计也一样是一个挑战,可是再次,咱们也能够列举出一些能够助咱们一臂之力的框架。对于CSS,Twitter Bootstrap是一个很是好的选择,它提供了一套完整的样式,它们能够当即使用,也很是便于自定义

  Bootstrap是使用LESS语言建立的,它是开源的,咱们能够根据咱们的须要来修改它。伴随它的还有一大堆用户友好的组件,它们也有很是完善的文档。此外,一个定制化模式让你很方便地建立你本身的。毫无疑问,它正是这个工做所须要的正确的工具。

  最佳实践:Grunt,Mocha,Chai,RequireJS 和 CoverJS

  最后,咱们将定义一些最佳实践,同时谈谈该如何实现和维护它们。具备表明性的,个人解决方案,最终聚焦到几个工具上,他们自己都是基于Node.js。

  MOCHA 和 CHAI

  这些工具能帮助你使用测试驱动开发模式(test-driven development)或者行为驱动开发模式(behavior-driven development)来改进你的开发流程,建立一些基础架构来管理你的单元测试,而且自动运行这些测试。

  如今有大量的JavaScript单元测试框架,为何要用Mocha?简短的回答就是它即灵活又完善。我来解释一下:

  • 用户界面(Interfaces)
    也许你习惯于测试驱动的程序组和单元测试的概念,又或许倾向于行为驱动测试的使用describle和should来定义行为定义的理念。Mocha让你能够同时使用这两种方式。
  • 报表生成器(reporter)
    运行你的测试代码会生成测试结果的报表,你可使用各式各样的reporter来格式化这些结果。举例来讲,若是你须要提供一个持续集成服务器信息,你能够找到一个report来作这些。
  • 没有指定断言库(Lack of an assertion library)
    这几乎不是一个问题,Mocha决定让你选择本身要使用的断言库,从而给你更多的灵活性。你有不少的选择,这正是Chai施展身手的地方。

  Chai 是一个很是灵活的断言库,它可让你使用以下三中主要断言方式的任何一种:

  • assert
    这是来自老派测试驱动开发的经典的assert方式。好比:

     

    1
    assert.equal( var iable, "value" );
  • expect
    这种链式的断言风格在行为驱动开发中最为常见。好比:

     

    1
    expect( var iable).to.equal( "value" );
  • should
    这也是用在测试驱动开发中,可是我更推荐expect,由于should常常听起来比较反复(好比,定义一个行为规范,”it (should do something…)”)。举例:

     

    1
    var iable.should.equal( "value" );

  Chai和Mocha能够无缝集成。使用这两个程序库,你可使用测试驱动,行为驱动活任何想获得的方式来写你的测试代码。

  GRUNT

  Grunt是你可以自动化你的build任务,包含简单的复制粘贴和文件拼接,模板预编译,style语言(SASS和LESS)编译,单元测试(使用Mocha),代码检查,以及代码最小化(好比,使用UglifyJS或者Closure Compiler)。你能够添加你本身的自动化任务到Grunt中或者搜索registry,那里数百个插件可供使用(再次提醒,选择使用有良好的社区支持的工具)。Grunt也能够监控你的文件,当发生更改时触发一些操做。

  REQUIREJS

  RequireJS 听起来是基于AMD API的另外一种加载模块的方式,可是我敢保证地告诉你,它远远不止这个功能。使用RequireJS,你能够定义你的模块之间的依赖和层次结构,让RequireJS库帮你来加载他们。它还提供了一种很是简便的方式来避免全局变量污染,经过在函数体中定义你的模块。这让模块能够重用,不像命名空间模块(namespaced modules)。试想一下:若是定义了一个相似于Demoapp.helloWorlModule的模块,你想把他改为Firstapp.helloWorldModule,那么你须要把全部引用到Demoapp命名空间的地方都作修改,才能让它变得可移植。

  RequireJS还能让你拥抱依赖注入模式。假设你有一个模块须要用到主应用对象(单例)的一个实例。经过使用RequireJS,你意识到你不须要使用全局变量来存储它,你也不能使用一个实例做为RequireJS的依赖。因此,你须要在你的模块构造器中加载这个依赖。让咱们看一个例子:

  在main.js:

1
2
3
4
5
6
7
8
9
10
11
12
define(
       [ "App" , "module" ],
       function (App, Module){
           var app = new App();
 
           var module = new Module({
               app: app
           })
 
           return app;
       }
   );

  在module.js

1
2
3
4
5
6
7
8
9
10
11
define([],
       function (){
           var module = function (options){
               this .app = options.app;
           };
           module.prototype.useApp = function (){
               this .app.performAction();
           };
           return module
       }
   );

  注意,咱们不能在module的定义中加入对main.js的依赖,不然咱们会建立出一个循环引用。

  COVERJS

  代码覆盖率(Code coverage)是你测试的一个度量标准。正如它的名字所示,它能告诉你当前的测试集覆盖了你代码的多少部分。CoverJS经过检测你代码中的语句(而不是像JSCoverage那样看代码行)并生成一个检测过的版本的代码来测量你的测试代码的覆盖率。它也能够支持对持续集成服务器提供持续报表生成。

  总结

  全栈式JavaScript并不能解决全部的问题。可是它的社区和技术会带领你走很长一段路。使用JavaScript,你能够建立基于统一的语言的可扩展的,可维护的应用。毫无疑问,这是绝对值得咱们关注的。

  原文连接: smashingmagazine   翻译: 伯乐在线 - Owen Chen

相关文章
相关标签/搜索