精通 MEAN: MEAN 堆栈

端到端地开发现代的、全堆栈的二十一世纪 Web 项目

Web 开发专家 Scott Davis 将在一个包含 6 篇文章的文章系列中,使用 MongoDB、Express、AngularJS 和 Node.js 构建一个现代的 Web 应用程序。本文是该文章系列的第一篇文章,其中包含一些演示和示例代码,并对创建一个基础的 MEAN 应用程序进行了完整介绍。您还将了解 Yeoman 生成器,您可以使用它快速地轻松创建一个新的 MEAN 应用程序。

Scott Davis, 创始人, IBM

2014 年 10 月 30 日

  • +内容

在 IBM Bluemix 云平台上开发并部署您的下一个应用。

在 2002 年的一本著作中,David Weinberger 将发展迅速的 Web 内容描述成一个 小块松散组合(Small Pieces Loosely Joined)。这个比喻让我印象深刻,因为大家一般很容易认为 Web 是一个巨大的技术堆栈。实际上,您访问的每个网站都是库、语言与 Web 框架的一种独特组合。 

LAMP 堆栈 是早期表现突出的开源 Web 技术集合之一:它使用 Linux 作为操作系统,使用 Apache 作为 Web 服务器,使用 MySQL 作为数据库,并使用 Perl(或者 Python 和 PHP)作为生成基于 HTML Web 页面的编程语言。这些技术的出现并非为了一起联合工作。它们是独立的项目,由多位雄心勃勃的软件工程师前赴后继地整合在一起。自那以后,我们就见证了 Web 堆栈的大爆发。每一种现代编程语言似乎都有一个(或两个)对应的 Web 框架,可将各种混杂的技术预先组装在一起,快速而又轻松地创建一个新的网站。

MEAN 堆栈是 Web 社区中赢得大量关注和令人兴奋的一种新兴堆栈:MongoDB、Express、AngularJS 和 Node.js。MEAN 堆栈代表着一种完全现代的 Web 开发方法:一种语言运行在应用程序的所有层次上,从客户端到服务器,再到持久层。本系列文章演示了一个 MEAN Web 开发项目的端到端开发情况,但这种开发并不仅限于简单的语法。本文将通过作者的亲身实践向您深入浅出地介绍了该堆栈的组件技术,包括安装与设置。参见 下载 部分,以便获取示例代码。 

关于本系列

在使用开源软件构建专业网站领域时,MEAN(MongoDB、Express、AngularJS 和 Node.js)堆栈是对流行已久的 LAMP 堆栈的一个新兴挑战者。MEAN 代表着架构与心理模型(mental model)方面的一次重大变迁:从关系数据库到 NoSQL,以及从服务器端的模型-视图-控制器到客户端的单页面应用程序。本系列文章将介绍 MEAN 堆栈技术如何互补,以及如何使用堆栈创建二十一世纪的、现代的全堆栈 JavaScript Web 应用程序。 

实际上,您访问的每个网站都是库、语言与 Web 框架的独特组合。


从 LAMP 到 MEAN

MEAN 不仅仅是一次首字母缩写的简单重新安排与技术升级。将基础平台从操作系统 (Linux) 转变为 JavaScript 运行时 (Node.js) 让操作系统变得独立:Node.js 在 Windows 与 OS X 上的运行情况和在 Linux 上一样优秀。 

Node.js 同样取代了 LAMP 堆栈中的 Apache。但 Node.js 远远不止是一种简单的 Web 服务器。事实上,用户不会将完成后的应用程序部署到单机的 Web 服务器上;相反,Web 服务器已经包含在应用程序中,并已在 MEAN 堆栈中自动安装。结果,部署过程得到了极大简化,因为所需的 Web 服务器版本已经与余下的运行时依赖关系一起得到了明确定义。 

不仅是 MEAN

尽管本系列文章重点讲述的是 MEAN 太阳系中的四大行星,但也会介绍 MEAN 堆栈中的一些较小的(但并非不重要的)卫星类技术: 

  • Yeoman:一种功能强大的命令行开发工具组合,用于基架 (Yo)、编译脚本 (Grunt) 与客户端依赖关性管理 (Bower)。

  • Bootstrap:一个 CSS 库,支持开箱即用的移动端 响应式 Web 设计。

  • 测试库:除了 Mocha、Jasmine 和 Karma 以外,还有大量测试库可用于模拟 Ajax 调用 (Chai),显示测试范围 (Istanbul),以及自动完成功能测试,以便在实际的浏览器中 运行它们(Protractor)。

从传统数据库(如 MySQL)到 NoSQL,再到无架构的、以文档为导向的持久存储(如 MongoDB),这些代表着持久化策略发生了根本性的转变。用户花费在编写 SQL 上的时间将会减少,将会有更多的时间编写 JavaScript 中的映射/化简功能。用户还能省掉大量的转换逻辑,因为 MongoDB 可以在本地运行 JavaScript Object Notation (JSON)。因此,编写 RESTful Web 服务变得前所未有的容易。 

但从 LAMP 到 MEAN 的最大转变在于从传统的服务器端页面生成变为客户端 单页面应用程序 (SPA)。借助 Express 仍然可以处理服务器端的路由与页面生成,但目前的重点在客户端视图上,而 AngularJS 可以实现这一点。这种变化并不仅仅是将 模型-视图-控制器 (MVC) 工件从服务器转移到客户端。用户还要尝试从习惯的同步方式转而使用基本由事件驱动的、实质上为异步的方式。或许最重要的一点是,您将从以页面为中心的应用程序视图转到面向组件的视图。 

MEAN 堆栈并非以移动为中心,AngularJS 在桌面电脑、笔记本电脑、智能手机、平板电脑和甚至是智能电视上的运行效果都一样,但它不会把移动设备当作二等公民对待。而且测试事后不再是问题:借助世界级的测试框架,比如MochaJS、JasmineJS 和 KarmaJS,您可以为自己的 MEAN 应用程序编写深入而又全面的测试套件。 

准备好获得 MEAN 了吗?

安装 Node.js

您需要安装 Node.js,以便在本系列中的示例应用程序上工作,如果尚未安装它,那就立刻开始安装吧。 

如果使用 UNIX 风格的操作系统(Linux、Mac OS X 等),我推荐使用 Node Version Manager (NVM)。(否则,在 Node.js 主页上单击Install,下载适合您操作系统的安装程序,然后接受默认选项即可。)借助 NVM,您可以轻松下载 Node.js,并从命令行切换各种版本。这可以帮助您从一个版本的 Node.js 无缝转移到下一版本,就像我从一个客户项目转到下一个客户项目一样。 

NVM 安装完毕后,请输入命令 nvm ls-remote 查看哪些 Node.js 版本可用于安装,如清单 1 中所示。 

清单 1. 使用 NVM 列出可用的 Node.js 版本
$ nvm ls-remote

v0.10.20

v0.10.21
v0.10.22
v0.10.23
v0.10.24
v0.10.25
v0.10.26
v0.10.27
v0.10.28

输入 nvm ls 命令可以显示本地已经安装的 Node.js 版本,以及目前正在使用中的版本。

在撰写本文之际,Node 网站推荐 v0.10.28 是最新的稳定版本。输入 nvm install v0.10.28 命令在本地安装它。 

安装 Node.js 后(通过 NVM 或平台特定的安装程序均可),可以输入 node --version 命令来确认当前使用的版本:

$ node --version

v0.10.28

什么是 Node.js?

Node.js 是一种 headless JavaScript 运行时。它与运行在 Google Chrome 内的 JavaScript 引擎(名叫 V8)是一样的,但使用 Node.js 可以从命令行(而非浏览器)运行 JavaScript。 

访问浏览器的开发人员工具

熟悉自己所选浏览器中的开发人员工具。我将在整个系列中通篇使用 Google Chrome,但用户可以自行选择使用 Firefox、Safari 或者甚至是 Internet Explorer。 

  • 在 Google Chrome 中,单击 Tools > JavaScript Console

  • 在 Firefox 中,单击 Tools > Web Developer > Browser Console

  • 在 Safari 中,单击 Develop > Show Error Console。(如果看不到 Develop 菜单,可以在 Advanced preferences 页面上单击 Show Develop menu in menu bar。) 

  • 在 Internet Explorer 中,单击 Developer Tools > Script > Console

我曾有些学生嘲笑过从命令行运行 JavaScript 的主意:“如果没有要控制的 HTML,那 JavaScript 还有什么好处呢?" JavaScript 是在浏览器(Netscape Navigator 2.0)中来到这个世界的,因此那些反对者的短视和天真是可以原谅的。 

事实上,JavaScript 编程语言并未针对 文档对象模型 (DOM) 操作或形成 Ajax 请求提供本地功能。该浏览器提供了 DOM API,可以方便用户使用 JavaScript 来完成这类工作,但在浏览器之外的地方,JavaScript 不具备这些功能。 

下面给出了一个例子。在浏览器中打开一个 JavaScript 控制台(参见 访问浏览器的开发人员工具)。输入 navigator.appName。获得响应后,请输入 navigator.appVersion。得到的结果类似于图 1 中所示。 

图 1. 在 Web 浏览器中使用 JavaScript navigator 对象

精通 MEAN: MEAN 堆栈_第1张图片

在图 1 中,Netscape 是对 navigator.appName 的响应,而对 navigator.appVersion 的响应则是经验丰富的 Web 开发人员已经熟知但爱恨不一的、神秘的开发人员代理字符串。在图 1 中(截自 OS X 上的 Chrome 浏览器),该字符串是 5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36

现在,我们要创建一个名为 test.js 的文件。在文件中输入同样的命令,并将每个命令包含在 console.log() 调用中: 

console.log(navigator.appName);
console.log(navigator.appVersion);

保存文件并输入 node test.js 来运行它,如清单 2 中所示。 

清单 2. 查看 Node.js 中的 navigator is not defined 错误
$ node test.js 

/test.js:1
ion (exports, require, module, __filename, __dirname) { console.log(navigator.
                                                                    ^
ReferenceError: navigator is not defined
    at Object. (/test.js:1:75)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:902:3

正如您看到的那样,navigator 在浏览器中可用,但在 Node.js 中不可用。(不好意思,让您的第一个 Node.js 脚本失败了,但我想确保让您相信,在浏览器中运行 JavaScript 与在 Node.js 中运行它是不同的。) 

根据堆栈跟踪的情况,正确的 Module 没有得到加载。(Modules 是在浏览器中运行 JavaScript 与在 Node.js 中运行它之间的另一主要区别。我们将立刻讲述 Modules 的更多相关内容。)为了从 Node.js 获得类似的信息,请将 test.js 的内容修改为: 

console.log(process.versions)
console.log(process.arch)
console.log(process.platform)

再次输入 node test.js,可以看到类似于清单 3 中的输出。 

清单 3. 在 Node.js 中使用过程模块
$ node test.js
 
{ http_parser: '1.0',
  node: '0.10.28',
  v8: '3.14.5.9',
  ares: '1.9.0-DEV',
  uv: '0.10.27',
  zlib: '1.2.3',

  modules: '11',
  openssl: '1.0.1g' }
x64
darwin

在 Node.js 中成功运行第一个脚本之后,我们将接触下一个主要概念:模块。 

什么是模块?

可以在 JavaScript 中创建单一功能的函数,但与在 Java、Ruby 或 Perl 中不同,无法将多个函数打包到一个能够导入导出的内聚模块或 ”包“ 中。当然,使用