浅谈全栈式JavaScript[原文来自smashing magazine]

来源:互联网 发布:http 了解情况js 编辑:程序博客网 时间:2024/05/01 08:17

***基于Learning By Translating的原则,开始陆续翻译一些英语文章。

 

英文原版:An Introduction To Full-Stack JavaScript

出处:http://coding.smashingmagazine.com/2013/11/21/introduction-to-full-stack-javascript/ 


时至今日,任何你所创建的Web应用程序,可以有很多框架供选择。然而,你希望选择正确的一个:你希望使用可以快速开发的技术,持续迭代,最高效,快速,健壮性等等。你希望是精益的、敏捷的。你希望使用可以让你在短期和长期都能成功的技术。但是,这样的技术往往不容易被甄选出来。

 

依我之见,全栈式JavaScript切中所有要点。你可能隐约地看到过,可能曾认为它很有用,甚至与朋友讨论过它。但是,你亲自尝试过吗?在这个帖子里,我将给出概述:为什么全栈式JavaScript可能会适合你,以及它是如何魔幻般地运行。

 

以下给出快速预览:

 

 

我将逐步地介绍这些组件。但是首先,简单阐明我们是怎样发展到今天我们所处的(a short not on how we got to where we are today ???)。

 

为什么我使用JavaScript

自1998年起,我就是一名Web开发人员。当时,我们使用Perl进行多数服务端开发;但即使从那时起,我们已经在客户端使用JavaScript。Web服务端技术自当时起已经发生巨大变化:我们经历了一波又一波的语言和技术,比如PHP,ASP,JSP,.NET,Ruby,Python,仅举几例。开发者开始意识到在客户端和服务端环境使用两种不同语言使问题变得复杂。

 

在PHP和ASP的早期时代,当模板引擎还是个不错的主意,开发者将程序代码嵌入HTML。看到这样的嵌入式脚本再正常不过了:

 

<script>    <?php        if ($login == true){    ?>    alert("Welcome");    <?php        }    ?></script>

 

或者,甚至更糟糕:

 

<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。此外,编写这样的代码,在服务器端和客户端处理相同的数据结构,即使在今天也是也不舒服的(当然了,除非你有一个这样的团队,有专注于前端和后端的工程师,但即使他们可能共享信息,他们依然无法在各自的代码协作):

 

<?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。它未能如能得偿所愿(This didn’t work as expected ),并且多数项目都失败了(比如,ASPMVC取代了ASP.NET Web forms,以及可以预见的,不久的将来GWT将被Polymer取代)。但是这个主意非常不错,实质上:在客户端和服务端的使用单一语言,使得我们可以重用控件和资源(并且是关键词:资源)。

 

答案是简单的:将JavaScript放置到服务端。

 

JavaScript实际在是诞生于Netscape企业版服务端,但是坦白地说,这个语言在当时没有准备好(but thelanguage simply wasn’t ready at the time ?)。经常多年的尝试和失败,Node.js最终浮现了,它不仅仅将JavaScript植入服务端,还提升了从nginx领域引入的非阻塞式编程(non-blockingprogramming)概念,感谢Node创建者的nginx背景,并且(明智地)保持它的简单,感谢JavaScipt的事件循环(event-loop)特性。

 

(一言以概之,非阻塞式编程目标在于将耗时的任务搁置一旁,一般是通过指定当这些任务完成时应该做什么,并且允许处理器同时处理其它请求。)

 

Node.js永远地改变我们处理I/O访问的方式。作为Web开发者,访问数据库(I/O)时我们经常使用如下几行代码:

 

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

 

这几行实质上会阻塞你的代码,因为你的程序将停止运行直至你的数据库驱动返回一个resultSet。与此同时,你的平台基础架构提供了并发的方式,通常是使用线程或分叉(forks???).

 

采用Node.js和非阻塞式编程,我们被提供了更多在程序流上的控制。现在(即使你还有一段被数据库(I/O)驱动隐藏的程序在并行地运行),你可以定义此时程序应该做什么以及当接收到resultset时应该做什么:

 

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

 

通过这个代码片断,我们定义了两个程序流:第一个处理发出数据库查询后的动作,而第二个使用简单的回调处理我们接收resultSet后的动作。这是一个优雅的、强大的管理并发的方式。正如人们所说的:一切都是并行的——除了你的代码。正是如此,你的代码将容易编写、阅读、理解和维护,这一切并不会丧失你对程序流的控制(all without your losing control over the program flow)。

 

在当时,这些概念并不新鲜——那么,它们为什么因Node.js变得如此流行?很简单:非阻塞式编程可以以多种方式实现。或许最简单的方式是使用回调和事件循环。在大部分语言里,这却不是件简单的事:虽然回调在很多其它语言是一个基本功能,但是事件循环却不是,你会发现自己时常努力克服外部引用库(比如,Pathon的Tornado)。

 

但是JavaScript中,回调是内置在语言里的,同样地事件循环也是(as isthe event loop),多数曾经涉猎过JavaScript的程序应该对此熟悉(或者至少使用过它们,即便不怎么理解事件循环是什么)。忽然之间,地球上的每个创业公司都可以在客户端和服务器重复使用开发者(也即资源),解决了“Phthon大师需求”的工作岗位问题(every startup on Earth could reuse developers(i.e. resources) onboth and client and server side,soling the “Python Guru Needed” jobposting problem)。

 

所以,现在我们有一个难以置信高速平台(感谢非阻塞式编程),它有着极易使用的编程语言(感谢JavaScript)。但这就够了吗?它会持续下去吗?我相信JavaScript在未来会有一个非常重要的地位。让我来告诉你为什么

 

函数式编程(FUNCTIONAL PROGRAMMING)

在大多数语言中,JavaScript是第一种引入函数式范式的语言(当然,Lisp更早引入,但是多数程序员从未使用其创建准产品级的应用程序)。对JavaScript有着主要影响的Lisp和Self,充满了革新性的创意,能够解放我们探索新技术、模式和范式的思想(Lisp andSelf, JavaScript’s main influence, are full of innovative ideas that can freeour minds to explore new techniques,patterns and paradigms)。并且所有这些继续存在于JavaScript。看一看monads,Churchnumbers或者甚至(更多的实用性案例)Underscore的函数集合,能使你少写一行行的代码。

 

动态对象和原型继承(DYNAMIC OBJECTS AND PROTOTYPAL INHERITANCE)

摒弃了“类”的面向对象编程(并且没有无尽的类继承)使得能够快速开发——只管创建对象,添加方法然后使用它们。更重要的是,通过允许程序员修改对象的实例,而非类,它减少了在维护时重构时间。这个速度及灵活性为快速开发铺平了道路。

 

JavaScript属于互联网(JAVASCRIPT IS THE INTERNET)

JavaScript被设计用于互联网。它一开始便属于这里,也不曾离开。任何摧毁它的尝试都以失败告终。回顾一下,比如,Java Applets的衰退,VBScript被微软的TypeScript(编译成JavaScript)替代,以及Flash死在移动市场和HTML5手中。取代JavaScript而不破坏成千上成网页是不可能的,所以我们进一步的目标是去提升它。而且,没有任何个人或团体比Technical Committee 39 of ECMA更适合这项工作。

 

当然了,JavaScript的替代者每天都在产生,像CoffeeScript,TypeScript以及数以万计编译成JavaScript的语言。这些替代者或许在开发阶段(通过源码映射 [viasource maps])颇有作用,但它们终究还是无法替代JavaScript,原因有二:它们的社区将未能壮大,并且它们最好的功能将会被ECMAScript(如JavaScript)采纳。JavaScript不是一种汇编语言:它是一门源代码可以被理解的高级编程语言——所以,你应该理解它。

 

端到端的JavaScript:Node.js和MongoDB

我们已经罗列了使用JavaScript的原因。接下来,我们将视JavaScript为使用Node.js和MongoDB原因之一。

 

NODE.JS

Node.js是一个用来构建快速、大型网络应用程序的平台——这大致是Node.js网站所描述的。但是Node.js更甚于此——它是当前最火热的JavaScript运行环境,被用于成千上万的应用程序和库——甚至浏览器的库都运行在Node.js。更重要地,快速的服务端运行允许开发者关注更复杂的问题,比如 Natural for natural language processing。即便你不打算用Node.js编写你的主服务端程序,你仍可以使用基于Node.js创建的工具来提升你的开发进程;比如,用于前端包管理的Bower ,用于单元测试的Mocha,自动构建任务的Grunt以及用于纯文本编辑的Brackets

 

所以,如果你打算为服务器和客户端编写Javascript程序,你应该对Node.js变得很熟悉,因为你将每天都需要它。一些有趣的替代者也存在,但是甚至没有一个能拥有Node.js社区的10%大的社区 。

 

MONGODB

MongoDB是一种基于文档的NoSQL数据库,它使用JavaScript作为其查询语言(但它本身不是用JavaScript编写的),如此一来(thus?)完成了我们的端到端JavaScript平台。然而这甚至不是我们选择这个数据库的主要原因。

 

MongoDB是无模式的,使我们能够以灵活方式持久化对象,正是如此,能快速适应需求变化。另外,它是高度可伸缩的且基于map-reduce(映射-化简),使得其适合于大数据应用程序。MongoDB是如此灵活以至于它可以被用作无模式的文档型数据库,相关性数据存储(尽管其缺少事务,只能模拟实现),以及甚至作为缓存响应的键-值存储,像Memcached和Redis。

 

使用Express实现服务端组件化

服务端组件化从来都不容易。但是由于Express(和Connect),出现了“中间件”的概念。依我之见,中间件是定义服务端组件的最好方式。如果你想将之与一已知的模式比较,它可能比较接近管道(pipes)或过滤器(filters)。

 

基本的思想是你的组件是管道的一部分。管道处理一个请求(如输入)和产生响应(如输出),但是你的组件不负责整个响应。相反地,它仅修改它所需要的,然后再委托给管道的下一个环节(and thendelegates to the next piece in the pipeline???)。当管道的最后一个环节完成处理,响应被发送回客户端。

 

我们视这些管道的环节为中间件。很显然,我们可以创建两类中间件:

 

*中级点(Intermediates?)

         一个中级点的(中间件)处理请求和响应但是不全部分负责响应本身,所以委托给下一个中间件。

*终结点(Finals)

         一个终结点的(中间件)负责最终的响应。它处理和修改请求和响应,但无需委托给下一个中间件。实际上,委托给下一个中间件,无论如何都将使得架构具有灵活性(比如,后续增加更多的中间件),即使那个中间件不存在(这种情况下,响应将直接发送到客户端)

 

 

作为一个具体的例子,考虑服务器端的“用户管理”组件。以中间件的形式,我们已经兼具结点(finals)和中级点(intermediates)。对于我们的终结点,我们具有创建用户和列举用户的功能。但是在我们执行这些动作前,需要我们的中级点进行验证(因为我我们不希望未被验证的请求进入并且创建用户)。一旦我们创建了这些验证的中级点,我们可以将其插入到任何我们想将此前无验证功能转变为有验证功能的地方。

 

单页面应用程序(Single-Page Applications)

当使用全栈式JavaScript开发,你将会时常聚焦在创建单页面应用程序(SPAs)。多数Web开发者不止一次亲自尝试SPAs(Most Web developers are tempted morethan once to try their hand at SPAs???)。我已经创建了若干个(多数是个人的),并且我相信它们是未来的网络应用程序。你是否曾经对比过,在移动接入情况下的SPA和常规Web应用程序?他们的响应差别大约是几十秒(Thedifference in responsevieness is in the order of tens of seconds???)。

 

(注意:有些人可能不赞同我的观点。比如,Twitter撤回了他们的SPA方案。而此时,像Zendesk这样的大型网站正向它迁移。我是已经见过足够多的信赖SPA所带来的益处的证据,butexperience vary[ I’ve seen enough evidence of the benefits of SPAs to believe in them, butexperience vary??? ])

 

既然SPA如此优秀,为什么还要用传统方式构建产品?我所听到的一个普遍的议论是:人们担心SEO。但只要你处理得当,这应该不是个问题:你可以采取不同的方法,当检测到网络爬虫使用headless浏览器(如PhantomJS)来渲染HTML(You can take different approaches,from using a headless browser(such as PhantomJS) to render the HTML when a Webcrawler is detected to performing server-side rendering with the help ofexisting frameworks. ???)。

 

Backbone.js,Marionette和TwitterBootstrap整合的客户端MV*

已经有很多关于SPAs的MV*框架的讨论。这是个艰难的选择,但是我不得不说的三个首选是:Backbone.js,Ember和AngularJS。

 

这三者都值得推介。但是哪个更适合你呢?

 

不幸运地是,我必须承认本人对AngularJS了解有限,所以把它排除在我的讨论范围。现在,Ember和Backbone.js代表解决同一问题的两种不同方式。

 

Backbone.js是小型的,提供恰如其分的功能来创建简单的SPA。相反地,Ember则是一个创建SPA的完整、专业的框架。它有着更多的花里胡哨(bells and whistles),当然学习曲线也更为陡峭。(你可以在这里阅读更多关于Ember.js)

 

基于你应用程序的规模,考虑“被使用的功能”与“有效的功能”的比例,它将给你一个很大的提示(hint),你的选择会变得简单。

 

样式也是一大挑战,但是再者,我们可以依靠框架来解放我们(bail us out)。比如CSS,Twitter Bootstrap是个很好的选择,因为它提供了一套完整的立即可用且易于定制的样式集合。

 

Bootstrap使用LESS语言创建,并且是开源的,因此我们可以按需修改。它提供大量编写良好的UX控件。两者,还有一个能够让你创建自己的控件的定制模型。它无疑是完成这此工作的绝佳工具(It is definitely the right tool for the job)。

 

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

最后,我们应该定义一些最佳实践,同时论及如何实现和维护。颇具代表性地,我的解决方案集中在基于Node.js的若干工具。

 

MOCHA和CHAI

这些工具,通过应用测试驱动开发(test-drivendevelopment,TDD)或者行为驱动开发(behavior-drivendevelopment,BDD),创建组织单元测试的基础平台来和自动执行这些测试的运行器,使你提升开发进度。

 

JavaScript的单元测试框架多不胜数。为何选用Mocha?直白的答案是:它是灵活的和完备的。

 

确切的答案是它有两个重要的功能(接口和报告者)以及一个重要的删减(断言)。请允许我解释:

 

*接口

 

或许你习惯于套件和单元测试的TDD概念,也或许你选择具有describe和should的行为限定BDD概念(or perhapsyou prefer BDD ideas of behavior specifications with describe and should.)。Mocha让你二者兼有。

 

*报告者

 

         运行测试将会产生结果的报告,你可以使用不同的报告者来格式化这些结果。例如,如果你需要订阅一个持续集成服务器(if youneed to feed a continuous integration server),你会找出一个恰好用来完成此事的报告者

 

*缺少断言库

 

         为避免成为一个问题,Mocha被设计为让你可以选择地使用断言库,给你更多的灵活性。你有很多选择,此时Chai开始发挥作用。

 

Chai是一个灵活的断言库,让你可以使用三种主要断言体的任何一个:

 

*** assert

 

         这是老式TDD中的经典断言体。比如:

 

assert.equal(variable, "value");

 

*** expect

 

这是普遍用于BDD的链式断言体。比如:

 

expect(variable).to.equal("value");

 

*** should

 

这也使用于BDD,但是倾向expect,因为shold时常显得有些重复(如,“它(应该做什么…)的行为限定”)。比如:

 

variable.should.equal("value");

 

Chai与Mocha完美结合。使用以上两个库,你能够以TDD,BDD或者任何你可以想象的来编写测试。

 

GRUNT

Grunt使你能够自动构建任务,任何任务包括简单的粘贴和复制和文件合并,模板预编译,样式语言(如SASS和LESS)编译,单元测试(配合MOCHA),程序预检查(linting)和代码精简(比如,通过UglifyJS或ClosureCompiler)。你可以添加自己要自动化的任务到Grunt或者搜索(Grunt)注册处,这里有数以百计的插件(再次,使用一个其成功背后有着大型社区的工具)。Grunt亦能够监控你的文件并在被修改时触发动作。

 

REQUIREJS

RequireJS或许应该像是其它以AMD API加载模块的方式,但是向你保证它不仅如此。通过RequireJS,你可以定义模块间的依赖和层级,然后让RequireJS库加载它们。它也提供一个避免变局变量空间污染的简单方式,通过定义你所有的模块到函数中。这使得模块可复用,而不像命名空间化的模块(namespaced module)。考虑一下:如果你定义了这样一个模块Demoapp.helloWorldModule,然后你想把它导入到Firstapp.helloworldModule,如此一来你需要改变所有指向Demoapp命名空间的引用以使得其可导入。

 

RequireJS也帮你拥抱依赖注入模式。假设你有一个需要主程序实例(一个单例)的组件。通过使用RequireJS,你会意识到你不应该使用全局变量来加载它,也不能有一个作为RequireJS依赖的实例(and youcan’t have an instance as a RequireJS dependency ???)。所以,相反地,你需要在模块构造器中包括这个依赖。让我们看这个一个样子:

 

在main.js中:

 

define(
      ["App","module"],
      function(App, Module){
          var app = new App();
 
          var module = new Module({
              app: app
          })
 
          return app;
      }
  );

 

在module.js中:

 

define([],
      function(){
          var module = function(options){
              this.app = options.app;
          };
          module.prototype.useApp = function(){
              this.app.performAction();
          };
          return module
      }
  );

 

注意:我们无法定义一个对main.js有依赖的模块而不产生循环引用(Note that we cannot define the module with a dependency to main.jswithout creating a circular reference.)。

 

COVERJS

代码覆盖(Code coverage)是评估测试的一个度量标准。诚如其名,它告诉你有多少代码被你当前的测试套件覆盖。CoverJS度量你的测试代码覆盖,通过放置检测声明到你的代码(取代一行行的代码,如JSCoverage),并生成检测声明版 的代码(CoverJS measures your tests’ codecoverage by instrumenting statements(instead of lines of code,like JSCoverage)in your code and generating an instrumented version of the code.)。它也可以生成报告来供给你的持续集成服务器(It can also generate reports to feed your continuous integration server)。

 

总结(Conclusion)

全栈式JavaScript并非一切问题的答案。但是其社区和技术会让走得很远(will carry you a long way)。通过JavaScript,你可以在同一种语言下创建可伸缩的、可维护的应用程序。勿庸置疑,这是一股不可忽视的力量。(it’s a force to be reckoned with)。

 


0 0
原创粉丝点击