导航栏: 首页 评论列表

转载:Node.js 概述

默认分类 2014/04/12 22:49

简介

[Node.js](http://www.qingdou.me/tag/node-js "查看Node.js中的全部文章")是JavaScript在服务器端的一个运行环境,也是一个工具库,用来与服务器端其他软件互动。它的JavaScript解释器,采用了Google公司的V8引擎。

[安装](http://www.qingdou.me/tag/%e5%ae%89%e8%a3%85 "查看安装中的全部文章")与更新

访问官方网站nodejs.org了解安装细节。

安装完成以后,运行下面的命令,查看是否能正常运行。

  1. node --version

更新node.js版本,可以使用下面的命令。

  1. sudo npm install n -g
  2. sudo n stable

上面代码通过n[模块](http://www.qingdou.me/tag/%e6%a8%a1%e5%9d%97 "查看模块中的全部文章"),将node.js更新为最新发布的稳定版。

版本管理工具nvm

如果想在同一台机器,同时运行多个版本的node.js,就需要用到版本管理工具nvm。

首先,需要安装nvm。

  1. git clone https://github.com/creationix/nvm.git ~/.nvm

然后使用下面的命令,激活nvm。

  1. source ~/.nvm/nvm.sh

上面这条命令,每次使用nvm前都要输入,建议将其加入~/.bashrc文件(假定你所使用的shell是bash)。

激活nvm之后,就可以安装指定版本的node.js。

  1. nvm install 0.10

上面这条命令,安装最新的v0.10.x版本的node.js。

安装后,就可以指定使用该版本。

  1. nvm use 0.10

或者,直接进入该版本的REPL环境。

  1. nvm run 0.10

如果在项目根目录下新建一个.nvmrc文件,将版本号写入其中,则nvm use命令就不再需要附加版本号。

  1. nvm use

ls命令用于查看本地所安装的版本。

  1. nvm ls

ls-remote命令用于查看服务器上所有可供安装的版本。

  1. nvm ls-remote

如果要退出已经激活的nvm,使用deactivate命令。

  1. nvm deactivate

基本用法

安装完成后,运行node.js程序,就是使用node命令读取JavaScript脚本。

假定当前目录有一个demo.js的脚本文件,运行时这样写。

  1. node demo 2.3. // 或者 4.5. node demo.js

REPL环境

在命令行键入node命令,后面没有文件名,就进入一个Node.js的REPL环境(Read–eval–print loop,”读取-求值-输出”循环),可以直接运行各种JavaScript命令。

  1. node
  2. > 1+1
  3. 2

如果使用参数 –use_strict,则REPL将在严格模式下运行。

  1. node --use_strict

这个REPL是Node.js与用户互动的shell,各种基本的shell功能都可以在里面使用,比如使用上下方向键遍历曾经使用过的命令。特殊变量下划线(_)表示上一个命令的返回结果。

  1. > 1+1
  2. 2
  3. > _+1
  4. 3

在REPL中,如果运行一个表达式,会直接在命令行返回结果,如果运行一条语句则不会,因为它没有返回值。

  1. > x = 1
  2. 1
  3. > var x = 1

上面代码的第二条命令,没有显示任何结果。因为这是一条语句,不是表达式,所以没有返回值。

异步操作

Node采用V8引擎处理JavaScript脚本,最大特点就是单线程运行,一次只能运行一个任务。这导致Node大量采用异步操作(asynchronous opertion),即任务不是马上执行,而是插在任务队列的尾部,等到前面的任务运行完后再执行。

由于这种特性,某一个任务的后续操作,往往采用回调函数(callback)的形式进行定义。

  1. var isTrue = function(value, callback) {
  2. if (value === true) {
  3. callback(null, "Value was true.");
  4. }
  5. else {
  6. callback(new Error("Value is not true!"));
  7. }
  8. }

上面代码就把进一步的处理,交给回调函数callback。约定俗成,callback的位置总是最后一个参数。值得注意的是,callback的格式也有约定。

  1. var callback = function (error, value) {
  2. if (error) {
  3. return console.log(error);
  4. }
  5. console.log(value);
  6. }

callback的第一个参数是一个Error对象,第二个参数才是真正的数据。如果没有发生错误,第一个参数就传入null。这种写法有一个很大的好处,就是说只要判断回调函数的第一个参数,就知道有没有出错,如果不是null,就肯定出错了。

全局对象和全局变量

Node提供以下一些全局对象,它们是所有模块都可以调用的。

全局函数:

全局变量:

除此之外,还有一些对象实际上是模块内部的局部变量,指向的对象根据模块不同而不同,但是所有模块都适用,可以看作是伪全局变量,主要为module, module.exports, exports等。

module变量指代当前模块。module.exports变量表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。

这里需要特别指出的是,exports变量实际上是一个指向module.exports对象的链接,等同在每个模块头部,有一行这样的命令。

  1. var exports = module.exports;

这造成的结果是,在对外输出模块接口时,可以向exports对象添加方法,但是不能直接将exports变量指向一个函数。

  1. exports = function (x){ console.log(x);};

上面这样的写法是无效的,因为它切断了exports与module.exports之间的链接。但是,下面这样写是可以的。

  1. exports.area = function (r) {
  2. return Math.PI * r * r;
  3. }; 4.5. exports.circumference = function (r) {
  4. return 2 * Math.PI * r;
  5. };

如果你觉得,exports与module.exports之间的区别很难分清,一个简单的处理方法,就是放弃使用exports,只使用module.exports。

模块化结构

概述

Node.js采用模块化结构,按照CommonJS规范定义和使用模块。

模块与文件是一一对应关系,即加载一个模块,实际上就是加载对应的一个模块文件。

require方法用于指定加载模块。

  1. var circle = require('./circle.js');

上面代码表明,从当前目录下的circle.js文件,加载circle模块。因为require方法默认加载的就是js文件,因此可以把js后缀名省略。

  1. var circle = require('./circle');

下面是其他一些模块加载的例子。

  1. var http = require('http');
  2. var express = require('express');
  3. var routes = require('./app/routes');

上面代码分别用require方法加载了三个模块。如果require方法的参数只是一个模块名,不带有路径,则表示该模块为核心模块或全局模块。比如,上面代码中的http为node.js自带的核心模块,express为npm命令安装的全局模块。如果require方法的参数带有路径,则表示该模块为项目自带的本地模块,必须告诉require该模块的路径。路径可以是绝对路径(以斜杠/开头),也可以是相对路径(以非斜杠开头),表示模块文件相对于当前调用require方法的脚本文件的位置,比如上面代码的routes模块的位置,在当前脚本文件所在目录的app子目录下。

如果require方法的参数不带有路径,而且加载的也不是核心模块与原生模块,则node.js按照以下从上到下的顺序,去寻找模块文件。比如,假定有一个位于/home/aaa/projects/目录下的脚本文件,包含了一行下面这样的加载命令。

  1. var bar = require('bar.js');

node.js依次到下面的目录,去寻找bar.js。

可以看到,如果没有指明模块文件所在位置,node.js会依次从当前目录向上,一级级在node_modules子目录下寻找模块文件。这样做的好处下,不同的项目可以依赖不同版本的某个模块,而不会发生版本冲突。

如果没有找到该模块,会抛出一个错误。

有时候,一个模块本身就是一个目录,目录中包含多个文件。这时候,需要在模块目录下的package.json文件中,用main属性指明模块的入口文件。下面就是一个例子,假定该模块的所有文件包含在some-library目录中。

  1. { "name" : "some-library",
  2. "main" : "./lib/some-library.js" }

当使用require(‘./some-library’)命令加载该模块时,实际上加载的是./some-library/lib/some-library.js文件。

如果模块目录中没有package.json文件,node.js会尝试在模块目录中寻找index.js或index.node文件进行加载。

加载模块以后,就可以调用模块中定义的方法了。

模块一旦被加载以后,就会被系统缓存。如果第二次还加载该模块,则会返回缓存中的版本,这意味着模块实际上只会执行一次。如果希望模块执行多次,则可以让模块返回一个函数,然后多次调用该函数。

核心模块

Node.js自带一系列的核心模块,下面就是其中的一部分:

这些模块可以不用安装就使用。

除了使用核心模块,还可以使用第三方模块,以及自定义模块。

自定义模块

Node.js模块采用CommonJS规范。只要符合这个规范,就可以自定义模块。

下面是一个最简单的模块,假定新建一个foo.js文件,写入以下内容。

  1. // foo.js 2.3. module.exports = function(x) {
  2. console.log(x);
  3. };

上面代码就是一个模块,它通过module.exports变量,对外输出一个方法。

这个模块的使用方法如下。

  1. // index.js 2.3. var m = require('./foo'); 4.5. m("这是自定义模块");

上面代码通过require命令加载模块文件foo.js(后缀名省略),将模块的对外接口输出到变量m,然后调用m。这时,在命令行下运行index.js,屏幕上就会输出“这是自定义模块”。

  1. node index
  2. # 这是自定义模块

module变量是整个模块文件的顶层变量,它的exports属性就是模块向外输出的接口。如果直接输出一个函数(就像上面的foo.js),那么调用模块就是调用一个函数。但是,模块也可以输出一个对象。下面对foo.js进行改写。

  1. // foo.js 2.3. var out = new Object(); 4.5. function p(string) {
  2. console.log(string);
  3. } 8.9. out.print = p; 10.11. module.exports = out;

上面的代码表示模块输出out对象,该对象有一个print属性,指向一个函数。下面是这个模块的使用方法。

  1. // index.js 2.3. var m = require('./foo'); 4.5. m.print("这是自定义模块");

上面代码表示,由于具体的方法定义在模块的print属性上,所以必须显式调用print属性。

fs模块

fs是filesystem的缩写,该模块提供本地文件的读写能力。

  1. var fs = require('fs'); 2.3. fs.readFile('example_log.txt', function (err, logData) { 4.5. if (err) throw err; 6.7. var text = logData.toString(); 8.9. });

上面代码使用readFile方法读取文件。readFile方法的第一个参数是文件名,第二个参数是回调函数。这两个参数中间,还可以插入一个可选参数,表示文件的编码。

  1. fs.readFile('example_log.txt', 'utf8', function (err, logData) {
  2. // ...
  3. });

可用的文件编码包括“ascii”、“utf8”和“base64”。如果这个参数没有提供,默认是utf8。

如果想要同步读取文件,可以使用readFileSync方法。

  1. var data = fs.readFileSync('./file.json');

写入文件要使用writeFile方法。

  1. fs.writeFile('./file.txt', data, function (err) {
  2. if (err) {
  3. console.log(err.message);
  4. return;
  5. }
  6. console.log('Saved successfully.');
  7. });

readdir方法用于读取目录,返回一个所包含的文件和子目录的数组。

  1. fs.readdir(process.cwd(), function (err, files) {
  2. if (err) {
  3. console.log(err);
  4. return;
  5. }
  6. console.log(files);
  7. });

Stream模式

Stream是数据处理的一种形式,可以用来取代回调函数。举例来说,传统形式的文件处理,必须先将文件全部读入内存,然后调用回调函数,如果遇到大文件,整个过程将非常耗时。Stream则是将文件分成小块读入内存,每读入一次,都会触发相应的事件。只要监听这些事件,就能掌握进展,做出相应处理,这样就提高了性能。Node内部的很多IO处理都采用Stream,比如HTTP连接、文件读写、标准输入输出。

Stream既可以读取数据,也可以写入数据。读写数据时,每读入(或写入)一段数据,就会触发一次data事件,全部读取(或写入)完毕,触发end事件。如果发生错误,则触发error事件。

fs模块的createReadStream方法用于新建读取数据流,createWriteStream方法用于新建写入数据流。使用这两个方法,可以做出一个用于文件复制的脚本copy.js。

  1. // copy.js 2.3. var fs = require('fs');
  2. console.log(process.argv[2], '->', process.argv[3]); 5.6. var readStream = fs.createReadStream(process.argv[2]);
  3. var writeStream = fs.createWriteStream(process.argv[3]); 8.9. readStream.on('data', function (chunk) {
  4. writeStream.write(chunk);
  5. }); 12.13. readStream.on('end', function () {
  6. writeStream.end();
  7. }); 16.17. readStream.on('error', function (err) {
  8. console.log("ERROR", err);
  9. }); 20.21. writeStream.on('error', function (err) {
  10. console.log("ERROR", err);
  11. });

上面代码非常容易理解,使用的时候直接提供源文件路径和目标文件路径,就可以了。

  1. node cp.js src.txt dest.txt

Streams对象都具有pipe方法,起到管道作用,将一个数据流输入另一个数据流。所以,上面代码可以重写成下面这样:

  1. var fs = require('fs');
  2. console.log(process.argv[2], '->', process.argv[3]); 3.4. var readStream = fs.createReadStream(process.argv[2]);
  3. var writeStream = fs.createWriteStream(process.argv[3]); 6.7. readStream.on('open', function () {
  4. readStream.pipe(writeStream);
  5. }); 10.11. readStream.on('end', function () {
  6. writeStream.end();
  7. });

http模块

实例:搭建一个HTTP服务器

使用Node.js搭建HTTP服务器非常简单。

  1. var http = require('http'); 2.3. http.createServer(function (request, response){
  2. response.writeHead(200, {'Content-Type': 'text/plain'});
  3. response.end('Hello World\n');
  4. }).listen(8080, "127.0.0.1"); 7.8. console.log('Server running on port 8080.');

上面代码第一行 var http = require(“http”),表示加载http模块。然后,调用http模块的createServer方法,创造一个服务器实例,将它赋给变量http。

ceateServer方法接受一个函数作为参数,该函数的req参数是一个对象,表示客户端的HTTP请求;res参数也是一个对象,表示服务器端的HTTP回应。rese.writeHead方法表示,服务器端回应一个HTTP头信息;response.end方法表示,服务器端回应的具体内容,以及回应完成后关闭本次对话。最后的listen(8080)表示启动服务器实例,监听本机的8080端口。

将上面这几行代码保存成文件app.js,然后用node调用这个文件,服务器就开始运行了。

  1. node app.js

这时命令行窗口将显示一行提示“Server running at port 8080.”。打开浏览器,访问http://localhost:8080,网页显示“Hello world!”。

上面的例子是当场生成网页,也可以事前写好网页,存在文件中,然后利用fs模块读取网页文件,将其返回。

  1. var http = require('http');
  2. var fs = require('fs'); 3.4. http.createServer(function (request, response){ 5.6. fs.readFile('data.txt', function readData(err, data) {
  3. response.writeHead(200, {'Content-Type': 'text/plain'});
  4. response.end(data);
  5. }); 10.11. }).listen(8080, "127.0.0.1"); 12.13. console.log('Server running on port 8080.');

下面的修改则是根据不同网址的请求,显示不同的内容,已经相当于做出一个网站的雏形了。

  1. var http = require("http"); 2.3. http.createServer(function(req, res) { 4.5. // 主页
  2. if (req.url == "/") {
  3. res.writeHead(200, { "Content-Type": "text/html" });
  4. res.end("Welcome to the homepage!");
  5. } 10.11. // About页面
  6. else if (req.url == "/about") {
  7. res.writeHead(200, { "Content-Type": "text/html" });
  8. res.end("Welcome to the about page!");
  9. } 16.17. // 404错误
  10. else {
  11. res.writeHead(404, { "Content-Type": "text/plain" });
  12. res.end("404 error! File not found.");
  13. } 22.23. }).listen(8080, "localhost");

处理POST请求

当客户端采用POST方法发送数据时,服务器端可以对data和end两个事件,设立监听函数。

  1. var http = require('http'); 2.3. http.createServer(function (req, res) {
  2. var content = ""; 5.6. req.on('data', function (chunk) {
  3. content += chunk;
  4. }); 9.10. req.on('end', function () {
  5. res.writeHead(200, {"Content-Type": "text/plain"});
  6. res.write("You've sent: " + content);
  7. res.end();
  8. }); 15.16. }).listen(8080);

data事件会在数据接收过程中,每收到一段数据就触发一次,接收到的数据被传入回调函数。end事件则是在所有数据接收完成后触发。

发出请求:request方法

request方法用于发出HTTP请求。

  1. var http = require('http'); 2.3. //The url we want is: 'www.random.org/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new'
  2. var options = {
  3. host: 'www.random.org',
  4. path: '/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new'
  5. }; 8.9. callback = function(response) {
  6. var str = ''; 11.12. //another chunk of data has been recieved, so append it tostr``
  7. response.on('data', function (chunk) {
  8. str += chunk;
  9. }); 16.17. //the whole response has been recieved, so we just print it out here
  10. response.on('end', function () {
  11. console.log(str);
  12. });
  13. } 22.23. var req = http.request(options, callback); 24.25. req.write("hello world!");
  14. req.end();

request对象的第一个参数是options对象,用于指定请求的域名和路径,第二个参数是请求完成后的回调函数。

如果使用POST方法发出请求,只需在options对象中设定即可。

  1. var options = {
  2. host: 'www.example.com',
  3. path: '/',
  4. port: '80',
  5. method: 'POST'
  6. };

指定HTTP头信息,也是在options对象中设定。

  1. var options = {
  2. headers: {'custom': 'Custom Header Demo works'}
  3. };

搭建HTTPs服务器

搭建HTTPs服务器需要有SSL证书。对于向公众提供服务的网站,SSL证书需要向证书颁发机构购买;对于自用的网站,可以自制。

自制SSL证书需要OpenSSL,具体命令如下。

  1. openssl genrsa -out key.pem
  2. openssl req -new -key key.pem -out csr.pem
  3. openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem
  4. rm csr.pem

上面的命令生成两个文件:ert.pem(证书文件)和 key.pem(私钥文件)。有了这两个文件,就可以运行HTTPs服务器了。

  1. var https = require('https');
  2. var fs = require('fs'); 3.4. var options = {
  3. key: fs.readFileSync('key.pem'),
  4. cert: fs.readFileSync('cert.pem')
  5. }; 8.9. var a = https.createServer(options, function (req, res) {
  6. res.writeHead(200);
  7. res.end("hello world\n");
  8. }).listen(8000);

上面代码显示,HTTPs服务器与HTTP服务器的最大区别,就是createServer方法多了一个options参数。运行以后,就可以测试是否能够正常访问。

  1. curl -k https://localhost:8000

events模块

基本用法

events模块是node.js对“发布/订阅”模式(publish/subscribe)的部署。也就说,通过events模块的EventEmitter属性,建立一个消息中心;然后通过on方法,为各种事件指定回调函数,从而将程序转为事件驱动型,各个模块之间通过事件联系。

  1. var EventEmitter = require("events").EventEmitter; 2.3. var ee = new EventEmitter();
  2. ee.on("someEvent", function () {
  3. console.log("event has occured");
  4. }); 7.8. ee.emit("someEvent");

上面代码在加载events模块后,通过EventEmitter属性建立了一个EventEmitter对象实例,这个实例就是消息中心。然后,通过on方法为someEvent事件指定回调函数。最后,通过emit方法触发someEvent事件。

emit方法还接受第二个参数,用于向回调函数提供参数。

  1. ee.on("someEvent", function (data){
  2. console.log(data);
  3. }); 4.5. ee.emit("someEvent", data);

默认情况下,Node.js允许同一个事件最多可以触发10个回调函数。

  1. ee.on("someEvent", function () { console.log("event 1"); });
  2. ee.on("someEvent", function () { console.log("event 2"); });
  3. ee.on("someEvent", function () { console.log("event 3"); });

超过10个回调函数,会发出一个警告。这个门槛值可以通过setMaxListeners方法改变。

  1. ee.setMaxListeners(20);

events模块的作用,还表示在其他模块可以继承这个模块,因此也就拥有了EventEmitter接口。

  1. var util = require("util");
  2. var EventEmitter = require("events").EventEmitter; 3.4. function UserList (){} 5.6. util.inherits(UserList, EventEmitter); 7.8. UserList.prototype.save = function (obj) {
  3. // save into database
  4. this.emit("saved-user", obj);
  5. };

上面代码新建了一个构造函数UserList,然后让其继承EventEmitter,因此UserList就拥有了EventEmitter的接口。最后,为UserList的实例定义一个save方法,表示将数据储存进数据库,在储存完毕后,使用EventEmitter接口的emit方法,触发一个saved-user事件。

事件类型

events模块默认支持一些事件。

  1. ee.on("newListener", function (evtName){
  2. console.log("New Listener: " + evtName);
  3. }); 4.5. ee.on("removeListener", function (evtName){
  4. console.log("Removed Listener: " + evtName);
  5. }); 8.9. function foo (){} 10.11. ee.on("save-user", foo);
  6. ee.removeListener("save-user", foo); 13.14. // New Listener: removeListener
  7. // New Listener: save-user
  8. // Removed Listener: save-user

上面代码会触发两次newListener事件,以及一次removeListener事件。

EventEmitter对象的其他方法

(1)once方法

该方法类似于on方法,但是回调函数只触发一次。

  1. ee.once("firstConnection", function (){
  2. console.log("本提示只出现一次");
  3. });

(2)removeListener方法

该方法用于移除回调函数。它接受两个参数,第一个是事件名称,第二个是回调函数名称。这就是说,不能用于移除匿名函数。

  1. function onlyOnce () {
  2. console.log("You'll never see this again");
  3. ee.removeListener("firstConnection", onlyOnce);
  4. } 5.6. ee.on("firstConnection", onlyOnce);

上面代码起到与once方法类似效果。

(3)removeAllListeners方法

该方法用于移除某个事件的所有回调函数。

  1. ee.removeAllListeners("firstConnection");

如果不带参数,则表示移除所有事件的所有回调函数。

  1. ee.removeAllListeners();

(4)listener方法

该方法接受一个事件名称作为参数,返回该事件所有回调函数组成的数组。

  1. function onlyOnce () {
  2. console.log(ee.listeners("firstConnection"));
  3. ee.removeListener("firstConnection", onlyOnce);
  4. console.log(ee.listeners("firstConnection"));
  5. } 6.7. ee.on("firstConnection", onlyOnce)
  6. ee.emit("firstConnection");
  7. ee.emit("firstConnection"); 10.11. // [ [Function: onlyOnce] ]
  8. // []

上面代码显示两次回调函数组成的数组,第一次只有一个回调函数onlyOnce,第二次是一个空数组,因为removeListener方法取消了回调函数。

process模块

process模块用来与当前进程互动,可以通过全局变量process访问,不必使用require命令加载。

属性

process对象提供一系列属性,用于返回系统信息。

下面是主要属性的介绍。

(1)stdout

process.stdout用来控制标准输出,也就是在命令行窗口向用户显示内容。它的write方法等同于console.log。

  1. exports.log = function() {
  2. process.stdout.write(format.apply(this, arguments) + '\n');
  3. };

(2)argv

process.argv返回命令行脚本的各个参数组成的数组。

先新建一个脚本文件argv.js。

  1. // argv.js 2.3. console.log("argv: ",process.argv);
  2. console.log("argc: ",process.argc);

在命令行下调用这个脚本,会得到以下结果。

  1. node argv.js a b c
  2. # [ 'node', '/path/to/argv.js', 'a', 'b', 'c' ]

上面代码表示,argv返回数组的成员依次是命令行的各个部分。要得到真正的参数部分,可以把argv.js改写成下面这样。

  1. // argv.js 2.3. var myArgs = process.argv.slice(2);
  2. console.log(myArgs);

方法

process对象提供以下方法:

process.chdir()改变工作目录的例子。

  1. process.cwd()
  2. # '/home/aaa' 3.4. process.chdir('/home/bbb') 5.6. process.cwd()
  3. # '/home/bbb'

process.nextTick()的例子,指定下次事件循环首先运行的任务。

  1. process.nextTick(function () {
  2. console.log('Next event loop!');
  3. });

上面代码可以用setTimeout改写,但是nextTick的效果更高、描述更准确。

  1. setTimeout(function () {
  2. console.log('Next event loop!');
  3. }, 0)

事件

(1)exit事件

当前进程退出时,会触发exit事件,可以对该事件指定回调函数。

  1. process.on('exit', function () {
  2. fs.writeFileSync('/tmp/myfile', 'This MUST be saved on exit.');
  3. });

(2)uncaughtException事件

当前进程抛出一个没有被捕捉的意外时,会触发uncaughtException事件。

  1. process.on('uncaughtException', function (err) {
  2. console.error('An uncaught error occurred!');
  3. console.error(err.stack);
  4. });

child_process模块

child_process模块用于新建子进程。子进程的运行结果储存在系统缓存之中(最大200KB),等到子进程运行结束以后,主进程再用回调函数读取子进程的运行结果。

  1. var childProcess = require('child_process'); 2.3. var ls = childProcess.exec('ls -l', function (error, stdout, stderr) {
  2. if (error) {
  3. console.log(error.stack);
  4. console.log('Error code: '+error.code);
  5. }
  6. console.log('Child Process STDOUT: '+stdout);
  7. }); 10.11. ls.on('exit', function (code) {
  8. console.log('Child process exited with exit code '+code);
  9. });

上面代码的exec方法会新建一个子进程,然后缓存它的运行结果,运行结束后调用回调函数。由于上面运行的是ls命令,它会自然结束,所以不会触发exit事件,因此上面代码最后监听exit事件的部分,其实是多余的。

cluster模块

Node.js默认单进程运行,对于多核CPU的计算机来说,这样做效率很低,因为只有一个核在运行,其他核都在闲置。cluster模块就是为了解决这个问题而提出的。

cluster模块允许设立一个主进程和若干个worker进程,由主进程监控和协调worker进程的运行。

  1. var cluster = require('cluster');
  2. var os = require('os'); 3.4. if (cluster.isMaster){
  3. for (var i = 0, n = os.cpus().length; i < n; i += 1){
  4. cluster.fork();
  5. }
  6. }else{
  7. http.createServer(function(req, res) {
  8. res.writeHead(200);
  9. res.end("hello world\n");
  10. }).listen(8000);
  11. }

上面代码先判断当前进程是否为主进程(cluster.isMaster),如果是的,就按照CPU的核数,新建若干个worker进程;如果不是,说明当前进程是worker进程,则在该进程启动一个服务器程序。

配置文件package.json

每个项目的根目录下面,一般都有一个package.json文件,定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。npm install 命令根据这个配置文件,自动下载所需的模块,也就是配置项目所需的运行和开发环境。

下面是一个最简单的package.json文件,只定义两项元数据:项目名称和项目版本。

  1. {
  2. "name" : "xxx",
  3. "version" : "0.0.0",
  4. }

上面代码说明,package.json文件内部就是一个json对象,该对象的每一个成员就是当前项目的一项设置。比如name就是项目名称,version是版本(遵守“大版本.次要版本.小版本”的格式)。

下面是一个更完整的package.json文件。

  1. {
  2. "name": "Hello World",
  3. "version": "0.0.1",
  4. "author": "张三",
  5. "description": "第一个node.js程序",
  6. "keywords":["node.js","javascript"],
  7. "repository": {
  8. "type": "git",
  9. "url": "https://path/to/url"
  10. },
  11. "license":"MIT",
  12. "engines": {"node": "0.10.x"},
  13. "bugs":{"url":"http://path/to/bug","email":"bug@example.com"},
  14. "contributors":[{"name":"李四","email":"lisi@example.com"}],
  15. "dependencies": {
  16. "express": "latest",
  17. "mongoose": "~3.8.3",
  18. "handlebars-runtime": "~1.0.12",
  19. "express3-handlebars": "~0.5.0",
  20. "MD5": "~1.2.0"
  21. },
  22. "devDependencies": {
  23. "bower": "~1.2.8",
  24. "grunt": "~0.4.1",
  25. "grunt-contrib-concat": "~0.3.0",
  26. "grunt-contrib-jshint": "~0.7.2",
  27. "grunt-contrib-uglify": "~0.2.7",
  28. "grunt-contrib-clean": "~0.5.0",
  29. "browserify": "2.36.1",
  30. "grunt-browserify": "~1.3.0",
  31. }
  32. }

上面代码中,前面部分各个成员的含义都很明显,需要注意的是engines这一项,它指明了该项目所需要的node.js版本。后面的dependencies和devDependencies两项,分别指定了项目运行所依赖的模块、项目开发所需要的模块。

dependencies和devDependencies这两项,都指向一个对象。该对象的各个成员,分别由模块名和对应的版本要求组成。对应的版本可以加上各种限定,主要有以下几种:

package.json文件可以手工编写,也可以使用npm init命令自动生成。

  1. npm init

这个命令采用互动方式,要求用户回答一些问题,然后在当前目录生成一个基本的package.json文件。所有问题之中,只有项目名称(name)和项目版本(version)是必填的,其他都是选填的。

有了package.json文件,直接使用npm install命令,就会在当前目录中安装所需要的模块。

  1. npm install

如果一个模块不在package.json文件之中,可以单独安装这个模块,并使用相应的参数,将其写入package.json文件之中。

  1. npm install express --save
  2. npm install express --save-dev

上面代码表示单独安装express模块,–save参数表示将该模块写入dependencies属性,–save-dev表示将该模块写入devDependencies属性。

模块管理器npm

npm简介

npm有两层含义。一层含义是Node.js的开放式模块登记和管理系统,网址为http://npmjs.org。另一层含义是Node.js默认的模块管理器,是一个命令行下的软件,用来安装和管理node模块。

npm不需要单独安装。在安装node的时候,会连带一起安装npm。node安装完成后,可以用下面的命令,查看一下npm的帮助文件。

  1. # npm命令列表
  2. npm help 3.4. # 各个命令的简单用法
  3. npm -l

下面的命令分别查看npm的版本和配置。

  1. npm -version 2.3. npm config list -l

npm的版本可以在Node更新的时候一起更新。如果你想单独更新npm,使用下面的命令。

  1. npm update -global npm

上面的命令之所以最后一个参数是npm,是因为npm本身也是Node.js的一个模块。

查看模块信息

npm的info命令可以查看每个模块的具体信息。比如,查看underscore模块信息的命令是:

  1. npm info underscore

上面命令返回一个JavaScript对象,包含了underscore模块的详细信息。

  1. { name: 'underscore',
  2. description: 'JavaScript\'s functional programming helper library.',
  3. 'dist-tags': { latest: '1.5.2', stable: '1.5.2' },
  4. repository:
  5. { type: 'git',
  6. url: 'git://github.com/jashkenas/underscore.git' },
  7. homepage: 'http://underscorejs.org',
  8. main: 'underscore.js',
  9. version: '1.5.2',
  10. devDependencies: { phantomjs: '1.9.0-1' },
  11. licenses:
  12. { type: 'MIT',
  13. url: 'https://raw.github.com/jashkenas/underscore/master/LICENSE' },
  14. files:
  15. [ 'underscore.js',
  16. 'underscore-min.js',
  17. 'LICENSE' ],
  18. readmeFilename: 'README.md'}

上面这个JavaScript对象的每个成员,都可以直接从info命令查询。

  1. npm info underscore description
  2. # JavaScript's functional programming helper library. 3.4. npm info underscore homepage
  3. # http://underscorejs.org 6.7. npm info underscore version
  4. # 1.5.2

模块的安装

每个模块可以“全局安装”,也可以“本地安装”。两者的差异是模块的安装位置,以及调用方法。

“全局安装”指的是将一个模块直接下载到Node的安装目录中,各个项目都可以调用。“本地安装”指的是将一个模块下载到当前目录的node_modules子目录,然后只有在当前目录和它的子目录之中,才能调用这个模块。一般来说,全局安装只适用于工具模块,比如npm和grunt。

默认情况下,npm install 命令是“本地安装”某个模块。

  1. npm install [package name]

npm也支持直接输入github地址。

  1. npm install git://github.com/package/path.git
  2. npm install git://github.com/package/path.git#0.1.0

使用安装命令以后,模块文件将下载到当前目录的 node_modules 子目录。

使用global参数,可以“全局安装”某个模块。

  1. sudo npm install -global [package name]

global参数可以被简化成g参数。

  1. sudo npm install -g [package name]

install命令总是安装模块的最新版本,如果要安装模块的特定版本,可以在模块名后面加上@和版本号。

  1. npm install package_name@version

一旦安装了某个模块,就可以在代码中用require命令调用这个模块。

  1. var backbone = require('backbone') 2.3. console.log(backbone.VERSION)

模块的升级和删除

npm update 命令可以升级本地安装的模块。

  1. npm update [package name]

加上global参数,可以升级全局安装的模块。

  1. npm update -global [package name]

npm uninstall 命令,删除本地安装的模块。

  1. npm uninstall [package name]

加上global参数,可以删除全局安装的模块。

  1. sudo npm uninstall [package name] -global

模块的查看和搜索

npm list命令,默认列出当前目录安装的所有模块。如果使用global参数,就是列出全局安装的模块。

  1. npm list 2.3. npm -global list

向服务器端搜索某个模块,使用search命令(可使用正则搜索)。

  1. npm search [搜索词]

如果不加搜索词,npm search 默认返回服务器端的所有模块。

npm run

在package.json文件有一项scripts,用于指定脚本命令,供npm直接调用。

  1. "scripts": {
  2. "watch": "watchify client/main.js -o public/app.js -v",
  3. "build": "browserify client/main.js -o public/app.js",
  4. "start": "npm run watch & nodemon server.js"
  5. },

上面代码在scripts项,定义了三个脚本命令,并且每个命令有一个别名。使用的时候,在命令行键入npm run后面加上别名,就能调用相应的脚本命令。

  1. npm run watch
  2. npm run build
  3. npm run start

npm link

一般来说,每个项目都会在项目目录内,安装所需的模块文件。也就是说,各个模块是局部安装。但是有时候,我们希望模块是一个符号链接,连到外部文件,这时候就需要用到npm link命令。

现在模块A(moduleA)的安装目录下运行npm link命令。

  1. /path/to/moduleA $ npm link

上面的命令会在npm的安装目录内,生成一个符号链接文件。

  1. /usr/local/share/npm/lib/node_modules/moduleA -> /path/to/moduleA

然后,转到你需要放置该模块的项目目录,再次运行npm link命令,并指定模块名。

  1. /path/to/my-project $ npm link moduleA

上面命令等同于生成了本地模块的符号链接。

  1. /path/to/my-project/node_modules/moduleA -> /usr/local/share/npm/lib/node_modules/moduleA

然后,就可以在你的项目中,加载该模块了。

  1. require('moduleA')

如果你的项目不再需要该模块,可以在项目目录内使用npm unlink命令,删除符号链接。

  1. /path/to/my-project $ npm unlink moduleA

模块的发布

在发布你的模块之前,需要先设定个人信息。

  1. npm set init.author.name "xxx"
  2. npm set init.author.email "xxx@gmail.com"
  3. npm set init.author.url "http://xxx.com"

然后,请npm系统申请用户名。

  1. npm adduser

运行上面的命令之后,屏幕上会提示输入用户名,然后是输入Email地址和密码。

上面所有的这些个人信息,全部保存在~/.npmrc文件之中。

npm模块就是一个遵循CommonJS规范的JavaScript脚本文件。此外,在模块目录中还必须有一个提供自身信息的package.json文件,一般采用npm init命令生成这个文件。

  1. npm init

运行上面的命令,会提示回答一系列问题,结束后自动生成package.json文件。

package.json文件中的main属性,指定模块加载的入口文件,默认是index.js。在index.js文件中,除了模块代码以外,主要使用require命令加载其他模块,使用module.exports变量输出模块接口。

下面是一个例子,将HTML文件中的特殊字符转为HTML实体。

  1. /**
  2. * Escape special characters in the given string of html.
  3. *
  4. * @param {String} html
  5. * @return {String}
  6. */
  7. module.exports = {
  8. escape: function(html) {
  9. return String(html)
  10. .replace(/&/g, '&')
  11. .replace(/"/g, '"')
  12. .replace(/'/g, ''')
  13. .replace(/</g, '<')
  14. .replace(/>/g, '>');
  15. }, 16.17. /**
  16. * Unescape special characters in the given string of html.
  17. *
  18. * @param {String} html
  19. * @return {String}
  20. */
  21. unescape: function(html) {
  22. return String(html)
  23. .replace(/&/g, '&')
  24. .replace(/"/g, '"')
  25. .replace(/'/g, '\'')
  26. .replace(/</g, '<')
  27. .replace(/>/g, '>');
  28. }
  29. };

完成代码以后,再加一个README.md文件,用来给出说明文本。

最后,使用npm publish命令发布。

  1. npm publish

本文来自:http://javascript.ruanyifeng.com/nodejs/basic.html

转载请注明:青豆前端 » Node.js 安装以及模块


>> 留言评论