joqk

 

目录:

  •  编写第一个Node.js程序;
  •  异步式I/O和事件循环;
  •  模块和包;
  •  调试。

1. 编写第一个Node.js程序;

  Node.js 具有深厚的开源血统,它诞生于托管了许多优秀开源项目的网站—— github。和大多数开源软件一样,它由一个黑客发起,然后吸引了一小拨爱好者参与贡献代码。一开始它默默无闻,靠口口相传扩散,直到某一天被一个黑客媒体曝光,进入业界视野,随后便有一些有远见的公司提供商业支持,使其逐步发展壮大。

  用 Node.js 编程是一件令人愉快的事情,因为你将开始用黑客的思维和风格编写代码。你会发现像这样的语言是很容易入门的,可以快速了解到它的细节,然后掌握它。

1.1helloworld

好了,让我们开始实现第一个 Node.js 程序吧。打开你常用的文本编辑器,在其中输入:

console.log(\'Hello World\');

将文件保存为 helloworld.js,打开终端,进入 helloworld.js 所在的目录,执行以下命令:

node helloworld.js

 如果一切正常,你将会在终端中看到输出 Hello World。

console 是 Node.js 提供的控制台对象,其中包含了向标准输出写入的操作,如 console.log、console.error 等。console.log 是我们最常用的输出指令,它和 C 语言中的 printf 的功能类似,也可以接受任意多个参数,支持 %d、%s 变量引用,

//consolelog.js
console.log(\'%s: %d\', \'Hello\', 25);

 1.2 nodejs 的命令行工具

前面的 Hello World 示例中,我们用到了命令行中的 node 命令,输入 node --help可以看到详细的帮助信息:

其中显示了 node 的用法,运行 Node.js 程序的基本方法就是执行 node script.js,其中 script.js是脚本的文件名。

除了直接运行脚本文件外,node --help 显示的使用方法中说明了另一种输出 HelloWorld 的方式:

$ node -e "console.log(\'Hello World\');"
Hello World

 我们可以把要执行的语句作为 node -e 的参数直接执行。

使用 node 的 REPL 模式:

  REPL (Read-eval-print loop),即输入—求值—输出循环。如果你用过 Python,就会知道在终端下运行无参数的 python 命令或者使用 Python IDLE 打开的 shell,可以进入一个即时求值的运行环境。Node.js 也有这样的功能,运行无参数的 node 将会启动一个 JavaScript的交互式 shell:

  

1.3 建立http服务器

  Node.js 是为网络而诞生的平台,但又与 ASP、PHP 有很大的不同,究竟不同在哪里呢?如果你有 PHP 开发经验,会知道在成功运行 PHP 之前先要配置一个功能强大而复杂的 HTTP服务器,譬如 Apache、IIS 或 Nginx,还需要将 PHP 配置为 HTTP 服务器的模块,或者使用FastCGI 协议调用 PHP 解释器。这种架构是“浏览器  HTTP 服务器  PHP 解释器”的组织方式,而Node.js采用了一种不同的组织方式,

我们看到,Node.js 将“HTTP服务器”这一层抽离,直接面向浏览器用户。这种架构从某种意义上来说是颠覆性的。

好了,回归正题,让我们创建一个 HTTP 服务器吧。建立一个名为 app.js 的文件,内容为:

//app.js
var http = require(\'http\');
http.createServer(function(req, res) {
res.writeHead(200, {\'Content-Type\': \'text/html\'});
res.write(\'<h1>Node.js</h1>\');
res.end(\'<p>Hello World</p>\');
}).listen(3000);
console.log("HTTP server is listening at port 3000.");

 接下来运行 node app.js命令,打开浏览器访问 http://localhost:3000,即可看到下图所示的内容。

用 Node.js 实现的最简单的 HTTP 服务器就这样诞生了。这个程序调用了 Node.js 提供的http 模块,对所有 HTTP 请求答复同样的内容并监听 3000 端口。在终端中运行这个脚本时,我们会发现它并不像 Hello World 一样结束后立即退出,而是一直等待,直到按下 Ctrl +C 才会结束。这是因为 listen 函数中创建了事件监听器,使得 Node.js 进程不会退出事件循环。我们会在后面的章节中详细介绍这其中的奥秘。

小技巧——使用 supervisor

  如果你有 PHP 开发经验,会习惯在修改 PHP 脚本后直接刷新浏览器以观察结果,而你在开发 Node.js 实现的 HTTP 应用时会发现,无论你修改了代码的哪一部份,都必须终止Node.js 再重新运行才会奏效。这是因为 Node.js 只有在第一次引用到某部份时才会去解析脚本文件,以后都会直接访问内存,避免重复载入,而 PHP 则总是重新读取并解析脚本(如果没有专门的优化配置)。Node.js的这种设计虽然有利于提高性能,却不利于开发调试,因为我们在开发过程中总是希望修改后立即看到效果,而不是每次都要终止进程并重启。

  supervisor 可以帮助你实现这个功能,它会监视你对代码的改动,并自动重启 Node.js。使用方法很简单,首先使用 npm 安装 supervisor:

$ npm install -g supervisor

 接下来,使用 supervisor 命令启动 app.js:

$ supervisor app.js
DEBUG: Running node-supervisor with
DEBUG: program \'app.js\'
DEBUG: --watch \'.\'
DEBUG: --extensions \'node|js\'
DEBUG: --exec \'node\'
DEBUG: Starting child process with \'node app.js\'
DEBUG: Watching directory \'/home/byvoid/.\' for changes.
HTTP server is listening at port 3000.

 当代码被修改时,你可以看到终端输出:

DEBUG: crashing child
DEBUG: Starting child process with \'node app.js\'
HTTP server is listening at port 3000.

supervisor 这个小工具可以解决开发中的调试问题。

3.模块和包

  模块(Module)和包(Package)是 Node.js 最重要的支柱。开发一个具有一定规模的程序不可能只用一个文件,通常需要把各个功能拆分、封装,然后组合起来,模块正是为了实现这种方式而诞生的。在浏览器 JavaScript 中,脚本模块的拆分和组合通常使用 HTML 的script 标签来实现。Node.js 提供了 require 函数来调用其他模块,而且模块都是基于文件的,机制十分简单。

  Node.js 的模块和包机制的实现参照了 CommonJS 的标准,但并未完全遵循。不过两者的区别并不大,一般来说你大可不必担心,只有当你试图制作一个除了支持 Node.js之外还要支持其他平台的模块或包的时候才需要仔细研究。通常,两者没有直接冲突的地方。  

  我们经常把 Node.js 的模块和包相提并论,因为模块和包是没有本质区别的,两个概念也时常混用。如果要辨析,那么可以把包理解成是实现了某个功能模块的集合,用于发布和维护。对使用者来说,模块和包的区别是透明的,因此经常不作区分。我们会详细介绍:

3.1什么是模块

  模块是 Node.js 应用程序的基本组成部分文件和模块是一一对应的。换言之,一个Node.js 文件就是一个模块,这个文件可能是 JavaScript 代码、JSON 或者编译过的 C/C++ 扩展。在前面章节的例子中,我们曾经用到了 var http = require(\'http\'),其中 http是 Node.js 的一个核心模块,其内部是用 C++ 实现的,外部用 JavaScript 封装。我们通过require 函数获取了这个模块,然后才能使用其中的对象。

3.2创建及加载模块

  在 Node.js 中,创建一个模块非常简单,因为一个文件就是一个模块,我们要关注的问题仅仅在于如何在其他文件中获取这个模块。Node.js 提供了 exportsrequire 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。让我们以一个例子来了解模块。创建一个 module.js 的文件,内容是:

//module.js
var name;
exports.setName = function(thyName) {
name = thyName;
};
exports.sayHello = function() {
console.log(\'Hello \' + name);
};

 在同一目录下创建 getmodule.js,内容是:

//getmodule.js
var myModule = require(\'./module\');
myModule.setName(\'BYVoid\');
myModule.sayHello();

 运行node getmodule.js,结果是:Hello BYVoid。

  在以上示例中,module.js 通过 exports 对象把 setName 和 sayHello 作为模块的访问接口,在 getmodule.js 中通过 require(\'./module\') 加载这个模块,然后就可以直接访问 module.js 中 exports 对象的成员函数了。这种接口封装方式比许多语言要简洁得多,同时也不失优雅,未引入违反语义的特性,符合传统的编程逻辑。在这个基础上,我们可以构建大型的应用程序,npm 提供的上万个模块都是通过这种简单的方式搭建起来的。

上面这个例子有点类似于创建一个对象,但实际上和对象又有本质的区别,因为require 不会重复加载模块,也就是说无论调用多少次 require,获得的模块都是同一个。

  事实上,exports 本身仅仅是一个普通的空对象,即 {},它专门用来声明接口,本质上是通过它为模块闭包①的内部建立了一个有限的访问接口。

3.3创建包

  包是在模块基础上更深一步的抽象,Node.js 的包类似于 C/C++ 的函数库或者 Java/.Net的类库。它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制。Node.js 根据 CommonJS 规范实现了包机制,开发了 npm来解决包的发布和获取需求。

  Node.js 的包是一个目录,其中包含一个 JSON 格式的包说明文件 package.json。严格符合 CommonJS 规范的包应该具备以下特征:

  •  package.json 必须在包的顶层目录下;
  •  二进制文件应该在 bin 目录下;
  •  JavaScript 代码应该在 lib 目录下;
  •  文档应该在 doc 目录下;
  •  单元测试应该在 test 目录下。
    Node.js 对包的要求并没有这么严格,只要顶层目录下有 package.json,并符合一些规范即可。当然为了提高兼容性,我们还是建议你在制作包的时候,严格遵守 CommonJS 规范。

作为文件夹的模块

  模块与文件是一一对应的。文件不仅可以是 JavaScript 代码或二进制代码,还可以是一个文件夹。最简单的包,就是一个作为文件夹的模块。下面我们来看一个例子,建立一个叫做 somepackage 的文件夹,在其中创建 index.js,内容如下:

//somepackage/index.js
exports.hello = function() {
console.log(\'Hello.\');
};

 然后在 somepackage 之外建立 getpackage.js,内容如下:

//getpackage.js
var somePackage = require(\'./somepackage\');
somePackage.hello();

 运行 node getpackage.js,控制台将输出结果 Hello.。

我们使用这种方法可以把文件夹封装为一个模块,即所谓的包。包通常是一些模块的集合,在模块的基础上提供了更高层的抽象,相当于提供了一些固定接口的函数库。通过定制package.json,我们可以创建更复杂、更完善、更符合规范的包用于发布。

package.json

在前面例子中的 somepackage 文件夹下,我们创建一个叫做 package.json 的文件,内容如下所示:

{
"main" : "./lib/interface.js"
}

 
然后将 index.js 重命名为 interface.js 并放入 lib 子文件夹下。以同样的方式再次调用这个包,依然可以正常使用。Node.js 在调用某个包时,会首先检查包中 package.json 文件的 main 字段,将其作为包的接口模块,如果 package.json 或 main 字段不存在,会尝试寻找 index.js 或 index.node 作为包的接口。

package.json 是 CommonJS 规定的用来描述包的文件,完全符合规范的 package.json 文件应该含有以下字段:

  •  name:包的名称,必须是唯一的,由小写英文字母、数字和下划线组成,不能包含空格。
  •  description:包的简要说明。
  •  version:符合语义化版本识别规范的版本字符串。
  •  keywords:关键字数组,通常用于搜索。
  •  maintainers:维护者数组,每个元素要包含 name、email (可选)、web (可选)字段。
  •  contributors:贡献者数组,格式与maintainers相同。包的作者应该是贡献者数组的第一个元素。
  •  bugs:提交bug的地址,可以是网址或者电子邮件地址。
  •  licenses:许可证数组,每个元素要包含 type (许可证的名称)和 url (链接到许可证文本的地址)字段。
  •  repositories:仓库托管地址数组,每个元素要包含 type (仓库的类型,如 git )、url (仓库的地址)和 path (相对于仓库的路径,可选)字段。
  • dependencies:包的依赖,一个关联数组,由包名称和版本号组成。

    3.4 Node.js 包管理器

    Node.js包管理器,即npm是 Node.js 官方提供的包管理工具,它已经成了 Node.js 包的标准发布平台,用于 Node.js 包的发布、传播、依赖控制。npm 提供了命令行工具,使你可以方便地下载、安装、升级、删除包,也可以让你作为开发者发布并维护包。

    • 获取一个包 : npm [install/i] [package_name]

      本地模式和全局模式:为什么要使用全局模式呢?多数时候并不是因为许多程序都有可能用到它,为了减少多重副本而使用全局模式,而是因为本地模式不会注册 PATH 环境变量。

    • 总而言之,当我们要把某个包作为工程运行时的一部分时,通过本地模式获取,如果要
      在命令行下使用,则使用全局模式安装。
    • npm包的发布

4.调试

  本地调试:node debug script.js

  远程调试

  工具调试等。

分类:

技术点:

相关文章:

猜你喜欢