目录
简介
Node.js是JavaScript在服务器端的一个运行环境,也是一个工具库,用来与服务器端其他软件互动。它的JavaScript解释器,采用了Google公司的V8引擎。
安装与更新
访问官方网站nodejs.org了解安装细节。
安装完成以后,运行下面的命令,查看是否能正常运行。
node --version
// 或者
node -v
更新node.js版本,可以通过node.js的n模块完成。
sudo npm install n -g
sudo n stable
上面代码通过n模块,将node.js更新为最新发布的稳定版。
n模块也可以指定安装的版本。
sudo n 0.8.21
版本管理工具nvm
如果想在同一台机器,同时运行多个版本的node.js,就需要用到版本管理工具nvm。
首先,需要安装nvm。
git clone https://github.com/creationix/nvm.git ~/.nvm
然后使用下面的命令,激活nvm。
source ~/.nvm/nvm.sh
上面这条命令,每次使用nvm前都要输入,建议将其加入~/.bashrc文件(假定你所使用的shell是bash)。
激活nvm之后,就可以安装指定版本的node.js。
nvm install 0.10
上面这条命令,安装最新的v0.10.x版本的node.js。
安装后,就可以指定使用该版本。
nvm use 0.10
或者,直接进入该版本的REPL环境。
nvm run 0.10
如果在项目根目录下新建一个.nvmrc文件,将版本号写入其中,则nvm use命令就不再需要附加版本号。
nvm use
ls命令用于查看本地所安装的版本。
nvm ls
ls-remote命令用于查看服务器上所有可供安装的版本。
nvm ls-remote
如果要退出已经激活的nvm,使用deactivate命令。
nvm deactivate
基本用法
安装完成后,运行node.js程序,就是使用node命令读取JavaScript脚本。
假定当前目录有一个demo.js的脚本文件,运行时这样写。
node demo
// 或者
node demo.js
REPL环境
在命令行键入node命令,后面没有文件名,就进入一个Node.js的REPL环境(Read–eval–print loop,"读取-求值-输出"循环),可以直接运行各种JavaScript命令。
node
> 1+1
2
如果使用参数 --use_strict,则REPL将在严格模式下运行。
node --use_strict
这个REPL是Node.js与用户互动的shell,各种基本的shell功能都可以在里面使用,比如使用上下方向键遍历曾经使用过的命令。特殊变量下划线(_)表示上一个命令的返回结果。
> 1+1
2
> _+1
3
在REPL中,如果运行一个表达式,会直接在命令行返回结果,如果运行一条语句则不会,因为它没有返回值。
> x = 1
1
> var x = 1
上面代码的第二条命令,没有显示任何结果。因为这是一条语句,不是表达式,所以没有返回值。
异步操作
Node采用V8引擎处理JavaScript脚本,最大特点就是单线程运行,一次只能运行一个任务。这导致Node大量采用异步操作(asynchronous opertion),即任务不是马上执行,而是插在任务队列的尾部,等到前面的任务运行完后再执行。
由于这种特性,某一个任务的后续操作,往往采用回调函数(callback)的形式进行定义。
var isTrue = function(value, callback) { if (value === true) { callback(null, "Value was true."); } else { callback(new Error("Value is not true!")); } }
上面代码就把进一步的处理,交给回调函数callback。约定俗成,callback的位置总是最后一个参数。值得注意的是,callback的格式也有约定。
var callback = function (error, value) { if (error) { return console.log(error); } console.log(value); }
callback的第一个参数是一个Error对象,第二个参数才是真正的数据。如果没有发生错误,第一个参数就传入null。这种写法有一个很大的好处,就是说只要判断回调函数的第一个参数,就知道有没有出错,如果不是null,就肯定出错了。
全局对象和全局变量
Node提供以下一些全局对象,它们是所有模块都可以调用的。
- global:表示Node所在的全局环境,类似于浏览器中的window对象。
- process:指向Node内置的process模块,允许开发者与当前进程互动。
- console:指向Node内置的console模块,提供命令行环境中的标准输入、标准输出功能。
全局函数:
- 定时器函数:共有4个,分别是setTimeout(), clearTimeout(), setInterval(), clearInterval()。
- require:用于加载模块。
全局变量:
- _filename:指向当前运行的脚本文件名。
- _dirname:指向当前运行的脚本所在的目录。
除此之外,还有一些对象实际上是模块内部的局部变量,指向的对象根据模块不同而不同,但是所有模块都适用,可以看作是伪全局变量,主要为module, module.exports, exports等。
module变量指代当前模块。module.exports变量表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。
- module.id 模块的识别符,通常是模块的文件名。
- module.filename 模块的文件名。
- module.loaded 返回一个布尔值,表示模块是否已经完成加载。
- module.parent 返回使用该模块的模块。
- module.children 返回一个数组,表示该模块要用到的其他模块。
这里需要特别指出的是,exports变量实际上是一个指向module.exports对象的链接,等同在每个模块头部,有一行这样的命令。
var exports = module.exports;
这造成的结果是,在对外输出模块接口时,可以向exports对象添加方法,但是不能直接将exports变量指向一个函数。
exports = function (x){ console.log(x);};
上面这样的写法是无效的,因为它切断了exports与module.exports之间的链接。但是,下面这样写是可以的。
exports.area = function (r) { return Math.PI * r * r; }; exports.circumference = function (r) { return 2 * Math.PI * r; };
如果你觉得,exports与module.exports之间的区别很难分清,一个简单的处理方法,就是放弃使用exports,只使用module.exports。
模块化结构
概述
Node.js采用模块化结构,按照CommonJS规范定义和使用模块。模块与文件是一一对应关系,即加载一个模块,实际上就是加载对应的一个模块文件。
require方法用于指定加载模块。
var circle = require('./circle.js');
上面代码表明,从当前目录下的circle.js文件,加载circle模块。因为require方法默认加载的就是js文件,因此可以把js后缀名省略。
var circle = require('./circle');
require方法的参数是模块文件的名字。它分成两种情况,第一种情况是参数中含有文件路径(比如上例),这时路径是相对于当前脚本所在的目录,第二种情况是参数中不含有文件路径(比如下例)。
var bar = require('bar');
如果require方法的参数不带有路径,则node.js依次按照以下顺序,去寻找模块文件。
node.js依次到下面的目录,去寻找bar模块。
- ./node_modules/bar
- ../node_modules/bar
- ../../node_modules/bar
- /node_modules/bar
可以看到,如果没有指明模块所在位置,Node会依次从当前目录向上,一级级在node_modules子目录下寻找模块。如果没有找到该模块,会抛出一个错误。这样做的好处是,不同的项目可以在自己的目录中,安装同一个模块的不同版本,而不会发生版本冲突。
有时候,一个模块本身就是一个目录,目录中包含多个文件。这时候,Node在package.json文件中,寻找main属性所指明的模块入口文件。
{
"name" : "bar", "main" : "./lib/bar.js" }
上面代码中,模块的启动文件为lib子目录下的bar.js。当使用require('bar')命令加载该模块时,实际上加载的是bar/lib/some-library.js
文件。下面写法会起到同样效果。
var bar = require('bar/lib/bar.js')
如果模块目录中没有package.json文件,node.js会尝试在模块目录中寻找index.js或index.node文件进行加载。
模块一旦被加载以后,就会被系统缓存。如果第二次还加载该模块,则会返回缓存中的版本,这意味着模块实际上只会执行一次。如果希望模块执行多次,则可以让模块返回一个函数,然后多次调用该函数。
核心模块
Node.js自带一系列的核心模块,下面就是其中的一部分:
- http:提供HTTP服务器功能。
- url:解析URL。
- fs:与文件系统交互。
- querystring:解析URL的查询字符串。
- child_process:新建子进程。
- util:提供一系列实用小工具。
- path:处理文件路径。
- crypto:提供加密和解密功能,基本上是对OpenSSL的包装。
这些模块可以不用安装就使用。
除了使用核心模块,还可以使用第三方模块,以及自定义模块。
自定义模块
Node.js模块采用CommonJS规范。只要符合这个规范,就可以自定义模块。
下面是一个最简单的模块,假定新建一个foo.js文件,写入以下内容。
// foo.js
module.exports = function(x) { console.log(x); };
上面代码就是一个模块,它通过module.exports变量,对外输出一个方法。
这个模块的使用方法如下。
// index.js
var m = require('./foo'); m("这是自定义模块");
上面代码通过require命令加载模块文件foo.js(后缀名省略),将模块的对外接口输出到变量m,然后调用m。这时,在命令行下运行index.js,屏幕上就会输出“这是自定义模块”。
node index
# 这是自定义模块
module变量是整个模块文件的顶层变量,它的exports属性就是模块向外输出的接口。如果直接输出一个函数(就像上面的foo.js),那么调用模块就是调用一个函数。但是,模块也可以输出一个对象。下面对foo.js进行改写。
// foo.js
var out = new Object(); function p(string) { console.log(string); } out.print = p; module.exports = out;
上面的代码表示模块输出out对象,该对象有一个print属性,指向一个函数。下面是这个模块的使用方法。
// index.js
var m = require('./foo'); m.print("这是自定义模块");
上面代码表示,由于具体的方法定义在模块的print属性上,所以必须显式调用print属性。
fs模块
fs是filesystem的缩写,该模块提供本地文件的读写能力。
(1)readfile方法
var fs = require('fs'); fs.readFile('example_log.txt', function (err, logData) { if (err) throw err; var text = logData.toString(); });
上面代码使用readFile方法读取文件。readFile方法的第一个参数是文件名,第二个参数是回调函数。这两个参数中间,还可以插入一个可选参数,表示文件的编码。
fs.readFile('example_log.txt', 'utf8', function (err, logData) { // ... });
可用的文件编码包括“ascii”、“utf8”和“base64”。如果这个参数没有提供,默认是utf8。
(2)readFileSync方法
如果想要同步读取文件,可以使用readFileSync方法。
var data = fs.readFileSync('./file.json');
(3)writeFile方法
写入文件要使用writeFile方法。
fs.writeFile('./file.txt', data, function (err) { if (err) { console.log(err.message); return; } console.log('Saved successfully.'); });
(4)readdir方法
readdir方法用于读取目录,返回一个所包含的文件和子目录的数组。
fs.readdir(process.cwd(), function (err, files) { if (err) { console.log(err); return; } var count = files.length; var results = {}; files.forEach(function (filename) { fs.readFile(filename, function (data) { results[filename] = data; count--; if (count <= 0) { // 对所有文件进行处理 } }); }); });
(5)fs.exists(path, callback)
exists方法用来判断给定路径是否存在,然后不管结果如何,都会调用回调函数。
fs.exists('/path/to/file', function (exists) { util.debug(exists ? "it's there" : "no file!"); });
上面代码表明,回调函数的参数是一个表示文件是否存在的布尔值。
需要注意的是,不要在open方法之前调用exists方法,open方法本身就能检查文件是否存在。
下面的例子是如果给定目录存在,就删除它。
if(fs.exists(outputFolder)) { console.log("Removing "+outputFolder); fs.rmdir(outputFolder); }
Stream模式
Stream是数据处理的一种形式,可以用来取代回调函数。举例来说,传统形式的文件处理,必须先将文件全部读入内存,然后调用回调函数,如果遇到大文件,整个过程将非常耗时。Stream则是将文件分成小块读入内存,每读入一次,都会触发相应的事件。只要监听这些事件,就能掌握进展,做出相应处理,这样就提高了性能。Node内部的很多IO处理都采用Stream,比如HTTP连接、文件读写、标准输入输出。
Stream是一个抽象接口,定义了readable、writable、drain、data、end、close等事件。它既可以读取数据,也可以写入数据。读写数据时,每读入(或写入)一段数据,就会触发一次data事件,全部读取(或写入)完毕,触发end事件。如果发生错误,则触发error事件。
fs模块的createReadStream方法用于新建读取数据流,createWriteStream方法用于新建写入数据流。使用这两个方法,可以做出一个用于文件复制的脚本copy.js。
// copy.js
var fs = require('fs'); console.log(process.argv[2], '->', process.argv[3]); var readStream = fs.createReadStream(process.argv[2]); var writeStream = fs.createWriteStream(process.argv[3]); readStream.on('data', function (chunk) { writeStream.write(chunk); }); readStream.on('end', function () { writeStream.end(); }); readStream.on('error', function (err) { console.log("ERROR", err); }); writeStream.on('error', function (err) { console.log("ERROR", err); });
上面代码非常容易理解,使用的时候直接提供源文件路径和目标文件路径,就可以了。
node cp.js src.txt dest.txt
Streams对象都具有pipe方法,起到管道作用,将一个数据流输入另一个数据流。所以,上面代码可以重写成下面这样:
var fs = require('fs'); console.log(process.argv[2], '->', process.argv[3]); var readStream = fs.createReadStream(process.argv[2]); var writeStream = fs.createWriteStream(process.argv[3]); readStream.on('open', function () { readStream.pipe(writeStream); }); readStream.on('end', function () { writeStream.end(); });
http模块
实例:搭建一个HTTP服务器
使用Node.js搭建HTTP服务器非常简单。
var http = require('http'); http.createServer(function (request, response){ response.writeHead(200, {'Content-Type': 'text/plain'}); response.end('Hello World\n'); }).listen(8080, "127.0.0.1"); 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调用这个文件,服务器就开始运行了。
node app.js
这时命令行窗口将显示一行提示“Server running at port 8080.”。打开浏览器,访问http://localhost:8080,网页显示“Hello world!”。
上面的例子是当场生成网页,也可以事前写好网页,存在文件中,然后利用fs模块读取网页文件,将其返回。
var http = require('http'); var fs = require('fs'); http.createServer(function (request, response){ fs.readFile('data.txt', function readData(err, data) { response.writeHead(200, {'Content-Type': 'text/plain'}); response.end(data); }); }).listen(8080, "127.0.0.1"); console.log('Server running on port 8080.');
下面的修改则是根据不同网址的请求,显示不同的内容,已经相当于做出一个网站的雏形了。
var http = require("http"); http.createServer(function(req, res) { // 主页 if (req.url == "/") { res.writeHead(200, { "Content-Type": "text/html" }); res.end("Welcome to the homepage!"); } // About页面 else if (req.url == "/about") { res.writeHead(200, { "Content-Type": "text/html" }); res.end("Welcome to the about page!"); } // 404错误 else { res.writeHead(404, { "Content-Type": "text/plain" }); res.end("404 error! File not found."); } }).listen(8080, "localhost");
回调函数的req(request)对象,拥有以下属性。
- url:发出请求的网址。
- method:HTTP请求的方法。
- headers:HTTP请求的所有HTTP头信息。
处理POST请求
当客户端采用POST方法发送数据时,服务器端可以对data和end两个事件,设立监听函数。
var http = require('http'); http.createServer(function (req, res) { var content = ""; req.on('data', function (chunk) { content += chunk; }); req.on('end', function () { res.writeHead(200, {"Content-Type": "text/plain"}); res.write("You've sent: " + content); res.end(); }); }).listen(8080);
data事件会在数据接收过程中,每收到一段数据就触发一次,接收到的数据被传入回调函数。end事件则是在所有数据接收完成后触发。
发出请求:request方法
request方法用于发出HTTP请求。
var http = require('http'); //The url we want is: 'www.random.org/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new' var options = { host: 'www.random.org', path: '/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new' }; callback = function(response) { var str = ''; //another chunk of data has been recieved, so append it to `str` response.on('data', function (chunk) { str += chunk; }); //the whole response has been recieved, so we just print it out here response.on('end', function () { console.log(str); }); } var req = http.request(options, callback); req.write("hello world!"); req.end();
request对象的第一个参数是options对象,用于指定请求的域名和路径,第二个参数是请求完成后的回调函数。
如果使用POST方法发出请求,只需在options对象中设定即可。
var options = {
host: 'www.example.com', path: '/', port: '80', method: 'POST' };
指定HTTP头信息,也是在options对象中设定。
var options = {
headers: {'custom': 'Custom Header Demo works'} };
搭建HTTPs服务器
搭建HTTPs服务器需要有SSL证书。对于向公众提供服务的网站,SSL证书需要向证书颁发机构购买;对于自用的网站,可以自制。
自制SSL证书需要OpenSSL,具体命令如下。
openssl genrsa -out key.pem
openssl req -new -key key.pem -out csr.pem
openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem
rm csr.pem
上面的命令生成两个文件:ert.pem(证书文件)和 key.pem(私钥文件)。有了这两个文件,就可以运行HTTPs服务器了。
var https = require('https'); var fs = require('fs'); var options = { key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem') }; var a = https.createServer(options, function (req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(8000);
上面代码显示,HTTPs服务器与HTTP服务器的最大区别,就是createServer方法多了一个options参数。运行以后,就可以测试是否能够正常访问。
curl -k https://localhost:8000
events模块
基本用法
events模块是node.js对“发布/订阅”模式(publish/subscribe)的部署。也就说,通过events模块的EventEmitter属性,建立一个消息中心;然后通过on方法,为各种事件指定回调函数,从而将程序转为事件驱动型,各个模块之间通过事件联系。
var EventEmitter = require("events").EventEmitter; var ee = new EventEmitter(); ee.on("someEvent", function () { console.log("event has occured"); }); ee.emit("someEvent");
上面代码在加载events模块后,通过EventEmitter属性建立了一个EventEmitter对象实例,这个实例就是消息中心。然后,通过on方法为someEvent事件指定回调函数。最后,通过emit方法触发someEvent事件。
emit方法还接受第二个参数,用于向回调函数提供参数。
ee.on("someEvent", function (data){ console.log(data); }); ee.emit("someEvent", data);
默认情况下,Node.js允许同一个事件最多可以触发10个回调函数。
ee.on("someEvent", function () { console.log("event 1"); }); ee.on("someEvent", function () { console.log("event 2"); }); ee.on("someEvent", function () { console.log("event 3"); });
超过10个回调函数,会发出一个警告。这个门槛值可以通过setMaxListeners方法改变。
ee.setMaxListeners(20);
events模块的作用,还表示在其他模块可以继承这个模块,因此也就拥有了EventEmitter接口。
var util = require("util"); var EventEmitter = require("events").EventEmitter; function UserList (){ EventEmitter.call(this); } util.inherits(UserList, EventEmitter); UserList.prototype.save = function (obj) { // save into database this.emit("saved-user", obj); };
上面代码新建了一个构造函数UserList,然后让其继承EventEmitter,因此UserList就拥有了EventEmitter的接口。最后,为UserList的实例定义一个save方法,表示将数据储存进数据库,在储存完毕后,使用EventEmitter接口的emit方法,触发一个saved-user事件。使用的时候,监听saved-user事件即可。
var ul = new UserList(); ul.on('saved-user', function(data){ console.log('保存成功'); })
事件类型
events模块默认支持一些事件。
- newListener事件:添加新的回调函数时触发。
- removeListener事件:移除回调时触发。
ee.on("newListener", function (evtName){ console.log("New Listener: " + evtName); }); ee.on("removeListener", function (evtName){ console.log("Removed Listener: " + evtName); }); function foo (){} ee.on("save-user", foo); ee.removeListener("save-user", foo); // New Listener: removeListener // New Listener: save-user // Removed Listener: save-user
上面代码会触发两次newListener事件,以及一次removeListener事件。
EventEmitter对象的其他方法
(1)once方法
该方法类似于on方法,但是回调函数只触发一次。
ee.once("firstConnection", function (){ console.log("本提示只出现一次"); });
(2)removeListener方法
该方法用于移除回调函数。它接受两个参数,第一个是事件名称,第二个是回调函数名称。这就是说,不能用于移除匿名函数。
function onlyOnce () {
console.log("You'll never see this again"); ee.removeListener("firstConnection", onlyOnce); } ee.on("firstConnection", onlyOnce);
上面代码起到与once方法类似效果。
(3)removeAllListeners方法
该方法用于移除某个事件的所有回调函数。
ee.removeAllListeners("firstConnection");
如果不带参数,则表示移除所有事件的所有回调函数。
ee.removeAllListeners();
(4)listener方法
该方法接受一个事件名称作为参数,返回该事件所有回调函数组成的数组。
function onlyOnce () {
console.log(ee.listeners("firstConnection")); ee.removeListener("firstConnection", onlyOnce); console.log(ee.listeners("firstConnection")); } ee.on("firstConnection", onlyOnce) ee.emit("firstConnection"); ee.emit("firstConnection"); // [ [Function: onlyOnce] ] // []
上面代码显示两次回调函数组成的数组,第一次只有一个回调函数onlyOnce,第二次是一个空数组,因为removeListener方法取消了回调函数。
process模块
process模块用来与当前进程互动,可以通过全局变量process访问,不必使用require命令加载。
属性
process对象提供一系列属性,用于返回系统信息。
- process.pid:当前进程的进程号。
- process.version:Node的版本,比如v0.10.18。
- process.platform:当前系统平台,比如Linux。
- process.title:默认值为“node”,可以自定义该值。
- process.argv:当前进程的命令行参数数组。
- process.env:指向当前shell的环境变量,比如process.env.HOME。
- process.execPath:运行当前进程的可执行文件的绝对路径。
- process.stdout:指向标准输出。
- process.stdin:指向标准输入。
- process.stderr:指向标准错误。
下面是主要属性的介绍。
(1)stdout
process.stdout用来控制标准输出,也就是在命令行窗口向用户显示内容。它的write方法等同于console.log。
exports.log = function() { process.stdout.write(format.apply(this, arguments) + '\n'); };
(2)argv
process.argv返回命令行脚本的各个参数组成的数组。
先新建一个脚本文件argv.js。
// argv.js
console.log("argv: ",process.argv); console.log("argc: ",process.argc);
在命令行下调用这个脚本,会得到以下结果。
node argv.js a b c # [ 'node', '/path/to/argv.js', 'a', 'b', 'c' ]
上面代码表示,argv返回数组的成员依次是命令行的各个部分。要得到真正的参数部分,可以把argv.js改写成下面这样。
// argv.js
var myArgs = process.argv.slice(2); console.log(myArgs);
方法
process对象提供以下方法:
- process.exit():退出当前进程。
- process.cwd():返回运行当前脚本的工作目录的路径。_
- process.chdir():改变工作目录。
- process.nextTick():将一个回调函数放在下次事件循环的顶部。
process.chdir()改变工作目录的例子。
process.cwd()
# '/home/aaa'
process.chdir('/home/bbb') process.cwd() # '/home/bbb'
process.nextTick()的例子,指定下次事件循环首先运行的任务。
process.nextTick(function () {
console.log('Next event loop!'); });
上面代码可以用setTimeout改写,但是nextTick的效果更高、描述更准确。
setTimeout(function () {
console.log('Next event loop!'); }, 0)
事件
(1)exit事件
当前进程退出时,会触发exit事件,可以对该事件指定回调函数。
process.on('exit', function () { fs.writeFileSync('/tmp/myfile', 'This MUST be saved on exit.'); });
(2)uncaughtException事件
当前进程抛出一个没有被捕捉的意外时,会触发uncaughtException事件。
process.on('uncaughtException', function (err) { console.error('An uncaught error occurred!'); console.error(err.stack); });
cluster模块
Node.js默认单进程运行,对于多核CPU的计算机来说,这样做效率很低,因为只有一个核在运行,其他核都在闲置。cluster模块就是为了解决这个问题而提出的。
cluster模块允许设立一个主进程和若干个worker进程,由主进程监控和协调worker进程的运行。
var cluster = require('cluster'); var os = require('os'); if (cluster.isMaster){ for (var i = 0, n = os.cpus().length; i < n; i += 1){ cluster.fork(); } }else{ http.createServer(function(req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(8000); }
上面代码先判断当前进程是否为主进程(cluster.isMaster),如果是的,就按照CPU的核数,新建若干个worker进程;如果不是,说明当前进程是worker进程,则在该进程启动一个服务器程序。
配置文件package.json
每个项目的根目录下面,一般都有一个package.json文件,定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。npm install 命令根据这个配置文件,自动下载所需的模块,也就是配置项目所需的运行和开发环境。
下面是一个最简单的package.json文件,只定义两项元数据:项目名称和项目版本。
{
"name" : "xxx", "version" : "0.0.0", }
上面代码说明,package.json文件内部就是一个json对象,该对象的每一个成员就是当前项目的一项设置。比如name就是项目名称,version是版本(遵守“大版本.次要版本.小版本”的格式)。
下面是一个更完整的package.json文件。
{
"name": "Hello World", "version": "0.0.1", "author": "张三", "description": "第一个node.js程序", "keywords":["node.js","javascript"], "repository": { "type": "git", "url": "https://path/to/url" }, "license":"MIT", "engines": {"node": "0.10.x"}, "bugs":{"url":"http://path/to/bug","email":"bug@example.com"}, "contributors":[{"name":"李四","email":"lisi@example.com"}], "scripts": { "start": "node index.js" }, "dependencies": { "express": "latest", "mongoose": "~3.8.3", "handlebars-runtime": "~1.0.12", "express3-handlebars": "~0.5.0", "MD5": "~1.2.0" }, "devDependencies": { "bower": "~1.2.8", "grunt": "~0.4.1", "grunt-contrib-concat": "~0.3.0", "grunt-contrib-jshint": "~0.7.2", "grunt-contrib-uglify": "~0.2.7", "grunt-contrib-clean": "~0.5.0", "browserify": "2.36.1", "grunt-browserify": "~1.3.0", } }
上面代码中,有些成员的含义很明显,但有几项需要解释一下。
(1)engines
engines指明了该项目所需要的node.js版本。
(2)scripts
scripts指定了运行脚本命令的npm命令行缩写,比如start指定了运行npm run start时,所要执行的命令。
下面的设置指定了npm run preinstall、npm run postinstall、npm run start、npm run test时,所要执行的命令。
"scripts": {
"preinstall": "echo here it comes!", "postinstall": "echo there it goes!", "start": "node index.js", "test": "tap test/*.js" }
(3)dependencies,devDependencies
dependencies和devDependencies两项,分别指定了项目运行所依赖的模块、项目开发所需要的模块。
dependencies和devDependencies这两项,都指向一个对象。该对象的各个成员,分别由模块名和对应的版本要求组成。对应的版本可以加上各种限定,主要有以下几种:
- 指定版本:比如1.2.2,遵循“大版本.次要版本.小版本”的格式规定,安装时只安装指定版本。
- 波浪号(tilde)+指定版本:比如~1.2.2,表示安装1.2.x的最新版本(不低于1.2.2),但是不安装1.3.x,也就是说安装时不改变大版本号和次要版本号。
- 插入号(caret)+指定版本:比如ˆ1.2.2,表示安装1.x.x的最新版本(不低于1.2.2),但是不安装2.x.x,也就是说安装时不改变大版本号。需要注意的是,如果大版本号为0,则插入号的行为与波浪号相同,这是因为此时处于开发阶段,即使是次要版本号变动,也可能带来程序的不兼容。
- latest:安装最新版本。
package.json文件可以手工编写,也可以使用npm init命令自动生成。
npm init
这个命令采用互动方式,要求用户回答一些问题,然后在当前目录生成一个基本的package.json文件。所有问题之中,只有项目名称(name)和项目版本(version)是必填的,其他都是选填的。
有了package.json文件,直接使用npm install命令,就会在当前目录中安装所需要的模块。
npm install
如果一个模块不在package.json文件之中,可以单独安装这个模块,并使用相应的参数,将其写入package.json文件之中。
npm install express --save
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的帮助文件。
# npm命令列表
npm help
# 各个命令的简单用法
npm -l
下面的命令分别查看npm的版本和配置。
npm -version
npm config list -l
npm的版本可以在Node更新的时候一起更新。如果你想单独更新npm,使用下面的命令。
npm update -global npm
上面的命令之所以最后一个参数是npm,是因为npm本身也是Node.js的一个模块。
查看模块信息
npm的info命令可以查看每个模块的具体信息。比如,查看underscore模块信息的命令是:
npm info underscore
上面命令返回一个JavaScript对象,包含了underscore模块的详细信息。
{ name: 'underscore', description: 'JavaScript\'s functional programming helper library.', 'dist-tags': { latest: '1.5.2', stable: '1.5.2' }, repository: { type: 'git', url: 'git://github.com/jashkenas/underscore.git' }, homepage: 'http://underscorejs.org', main: 'underscore.js', version: '1.5.2', devDependencies: { phantomjs: '1.9.0-1' }, licenses: { type: 'MIT', url: 'https://raw.github.com/jashkenas/underscore/master/LICENSE' }, files: [ 'underscore.js', 'underscore-min.js', 'LICENSE' ], readmeFilename: 'README.md'}
上面这个JavaScript对象的每个成员,都可以直接从info命令查询。
npm info underscore description
# JavaScript's functional programming helper library.
npm info underscore homepage
# http://underscorejs.org
npm info underscore version
# 1.5.2
模块的安装
每个模块可以“全局安装”,也可以“本地安装”。两者的差异是模块的安装位置,以及调用方法。
“全局安装”指的是将一个模块直接下载到Node的安装目录中,各个项目都可以调用。“本地安装”指的是将一个模块下载到当前目录的node_modules子目录,然后只有在当前目录和它的子目录之中,才能调用这个模块。一般来说,全局安装只适用于工具模块,比如npm和grunt。
默认情况下,npm install 命令是“本地安装”某个模块。
npm install [package name]
npm也支持直接输入github地址。
npm install git://github.com/package/path.git
npm install git://github.com/package/path.git#0.1.0
使用安装命令以后,模块文件将下载到当前目录的 node_modules 子目录。
使用global参数,可以“全局安装”某个模块。
sudo npm install -global [package name]
global参数可以被简化成g参数。
sudo npm install -g [package name]
install命令总是安装模块的最新版本,如果要安装模块的特定版本,可以在模块名后面加上@和版本号。
npm install package_name@version
一旦安装了某个模块,就可以在代码中用require命令调用这个模块。
var backbone = require('backbone') console.log(backbone.VERSION)
模块的升级和删除
npm update 命令可以升级本地安装的模块。
npm update [package name]
加上global参数,可以升级全局安装的模块。
npm update -global [package name]
npm uninstall 命令,删除本地安装的模块。
npm uninstall [package name]
加上global参数,可以删除全局安装的模块。
sudo npm uninstall [package name] -global
npm list:列出当前项目的模块
npm list命令,默认列出当前目录安装的所有模块。如果使用global参数,就是列出全局安装的模块。
npm list
npm -global list
npm search:模块搜索
向服务器端搜索某个模块,使用search命令(可使用正则搜索)。
npm search [搜索词]
如果不加搜索词,npm search 默认返回服务器端的所有模块。
npm run
在package.json文件有一项scripts,用于指定脚本命令,供npm直接调用。
"scripts": {
"watch": "watchify client/main.js -o public/app.js -v", "build": "browserify client/main.js -o public/app.js", "start": "npm run watch & nodemon server.js", "test": "node test/all.js" },
上面代码在scripts项,定义了三个脚本命令,并且每个命令有一个别名。使用的时候,在命令行键入npm run后面加上别名,就能调用相应的脚本命令。
npm run watch
npm run build
npm run start
npm run test
其中,start和test属于特殊命令,可以省略run。
npm start
npm test
npm link
一般来说,每个项目都会在项目目录内,安装所需的模块文件。也就是说,各个模块是局部安装。但是有时候,我们希望模块是一个符号链接,连到外部文件,这时候就需要用到npm link命令。
现在模块A(moduleA)的安装目录下运行npm link命令。
/path/to/moduleA $ npm link
上面的命令会在npm的安装目录内,生成一个符号链接文件。
/usr/local/share/npm/lib/node_modules/moduleA -> /path/to/moduleA
然后,转到你需要放置该模块的项目目录,再次运行npm link命令,并指定模块名。
/path/to/my-project $ npm link moduleA
上面命令等同于生成了本地模块的符号链接。
/path/to/my-project/node_modules/moduleA -> /usr/local/share/npm/lib/node_modules/moduleA
然后,就可以在你的项目中,加载该模块了。
require('moduleA')
如果你的项目不再需要该模块,可以在项目目录内使用npm unlink命令,删除符号链接。
/path/to/my-project $ npm unlink moduleA
模块的发布
在发布你的模块之前,需要先设定个人信息。
npm set init.author.name "xxx"
npm set init.author.email "xxx@gmail.com"
npm set init.author.url "http://xxx.com"
然后,请npm系统申请用户名。
npm adduser
运行上面的命令之后,屏幕上会提示输入用户名,然后是输入Email地址和密码。
上面所有的这些个人信息,全部保存在~/.npmrc文件之中。
npm模块就是一个遵循CommonJS规范的JavaScript脚本文件。此外,在模块目录中还必须有一个提供自身信息的package.json文件,一般采用npm init命令生成这个文件。
npm init
运行上面的命令,会提示回答一系列问题,结束后自动生成package.json文件。
package.json文件中的main属性,指定模块加载的入口文件,默认是index.js。在index.js文件中,除了模块代码以外,主要使用require命令加载其他模块,使用module.exports变量输出模块接口。
下面是一个例子,将HTML文件中的特殊字符转为HTML实体。
/**
* Escape special characters in the given string of html.
*
* @param {String} html
* @return {String} */ module.exports = { escape: function(html) { return String(html) .replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/</g, '<') .replace(/>/g, '>'); }, /** * Unescape special characters in the given string of html. * * @param {String} html * @return {String} */ unescape: function(html) { return String(html) .replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, '\'') .replace(/</g, '<') .replace(/>/g, '>'); } };
完成代码以后,再加一个README.md文件,用来给出说明文本。
最后,使用npm publish命令发布。
npm publish