node笔记

JS是脚本语言,脚本语言都需要一个解析器才能运行。对于写在HTML页面里的JS,浏览器充当了解析器的角色。而对于需要独立运行的JS,NodeJS就是一个解析器。每一种解析器都是一个运行环境,其内部提供的方法也不同。

安装使用

官网下载安装node,支持交互式命令模式。在终端通过$ node即可。常用来测试正则。nodeJs使用CommonJS规范。

模块

使用方式

require:用于在当前模块中加载和使用别的模块,传入一个模块名,返回一个模块导出对象。

1
2
3
const foo  = require('./foo');       //相对引入,后缀js可以省略(绝对引入类似)
const data = require('./data.json'); //可引入静态json
const path = require('path'); //内置模块path

exports:当前模块的导出对象,用于导出模块公有方法和属性。

1
2
3
exports.hello = function () {
console.log('Hello World!');
};

module:可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块的导出对象。

1
2
3
module.exports = function () {
console.log('Hello World!');
};

模块解析规则

内置模块

nodeJs内置模块,传递内置模块名称,不做路径解析,例如require('path')

node_modules目录

node_modules目录用于nodeJs存放模块,例如引入require('foo/bar')。解析如下:

1
2
3
/home/user/node_modules/foo/bar
/home/node_modules/foo/bar
/node_modules/foo/bar

NODE_PATH环境变量

nodeJs允许指定NODE_PATH,环境变量中包含一到多个目录路径,路径之间在Linux下使用:分隔,在Windows下使用;分隔。设置NODE_PATH=/home/user/lib:/home/lib,解析如下:

1
2
/home/user/lib/foo/bar
/home/lib/foo/bar

npm常用

包管理,nodeJs自带。下载三方包使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
npm install <packages>;
npm install <packages>;@<version>; //指定版本
npm install <packages>; -g //全局安装
npm install <packages>; -S //--save:Package will appear in your dependencies.
npm install <packages>; -D //--save-dev: Package will appear in your devDependencies.
npm install <packages>; -O //--save-optional: Package will appear in your optionalDependencies.

npm help <command>; //查看帮助
npm ls //列出当前/node_modules/ 目录下的包
npm init //初始化
npm update <packages> //更新指定安装包
npm uninstall <packages> //卸载指定安装包
npm search <packages> //搜索指定安装包

npm install -g cnpm --registry=https://registry.npm.taobao.org //淘宝镜像cnpm

文件操作

文件操作示例

node内置模块,详细api见http://nodejs.cn/api/fs.html

nodeJs只提供基础操作API。实现小文件拷贝如下:

1
2
3
4
5
6
7
8
9
10
var fs = require('fs'); //内置fs模块
function copy(src, dst) {
// fs.readFileSync从源路径读取;fs.writeFileSync将文件内容写入目标路径
fs.writeFileSync(dst, fs.readFileSync(src));
}
function main(argv) {
copy(argv[0], argv[1]);
}
main(process.argv.slice(2));
//process是一个全局变量,可通过process.argv获得命令行参数。由于argv[0]固定等于NodeJS执行程序的绝对路径,argv[1]固定等于主模块的绝对路径,因此第一个命令行参数从argv[2]这个位置开始。

上述方法在一次读取过多到内存中时,会出现内存爆满的问题。大文件拷贝如下:

1
2
3
4
5
6
7
8
9
var fs = require('fs');
function copy(src, dst) {
//fs.createReadStream创建源文件的只读数据流;fs.createWriteStream创建目标文件的只写数据流
fs.createReadStream(src).pipe(fs.createWriteStream(dst));
}
function main(argv) {
copy(argv[0], argv[1]);
}
main(process.argv.slice(2));

文件操作API

点击标题可跳转node中文网相关API。

Buffer(数据块)

全局构造函数Buffer扩展JS二进制数据类型。Buffer不同于字符串只读,它可以通过[index]的方式修改。slice类似与指针,修改会改变原Buffer。交互式命令模式下运行如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]); //构造Buffer
var bin1 = new Buffer('hello', 'utf-8'); //<Buffer 68 65 6c 6c 6f>
bin.length; //5
bin[0]; // 104 默认10进制
bin[0].toString(16); //0x68 转换十六进制
bin.toString('utf-8') //'hello'
var sub = bin.slice(2);
sub //<Buffer 6c 6c 6f>
sub[0] = 0x65;
bin //<Buffer 68 65 65 6c 6f>

// 复制新的Buffer
var dup = new Buffer(bin.length);
bin.copy(dup);
dup[0] = 0x48;
console.log(bin); // => <Buffer 68 65 6c 6c 6f>
console.log(dup); // => <Buffer 48 65 65 6c 6f>

Stream(数据流)

NodeJS中通过各种Stream来提供对数据流的操作。所有的流都是 EventEmitter 的实例。大文件拷贝如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//处理数据前暂停数据读取,并在处理数据后继续读取数据
var rs = fs.createReadStream(src);
rs.on('data', function (chunk) {
rs.pause();
doSomething(chunk, function () {
rs.resume();
});
});
rs.on('end', function () {
cleanUp();
});

//只写数据流,可能存在读取速度大于写入速度的问题
var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst);
rs.on('data', function (chunk) {
ws.write(chunk);
});
rs.on('end', function () {
ws.end();
});

//改造.数据从只读数据流到只写数据流的搬运,并包括了防爆仓控制。nodeJs提供的.pipe实现也是类似机制。
var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst);
rs.on('data', function (chunk) {
if (ws.write(chunk) === false) {
rs.pause();
}
});
rs.on('end', function () {
ws.end();
});
ws.on('drain', function () {
rs.resume();
});

File System(文件系统)

NodeJS通过fs内置模块提供对文件的操作。大致分三类:

  • 文件属性读写。常用fs.statfs.chmodfs.chown等等。
  • 文件内容读写。常用的有fs.readFilefs.readdirfs.writeFilefs.mkdir等等。
  • 底层文件操作。常用的有fs.openfs.readfs.writefs.close等等。

异步回调,同步API比异步尾部多Sync。

Path(路径)

NodeJS提供了path内置模块来简化路径相关操作,并提升代码可读性。
常用的几个如下:

  • path.normalize将传入的路径转换为标准路径。
1
2
3
4
5
6
7
var cache = {};
function store(key, value) {
cache[path.normalize(key)] = value;
}
store('foo/bar', 1);
store('foo//baz//../bar', 2);
console.log(cache); // => { "foo/bar": 2 }
  • path.join将传入的多个路径拼接为标准路径。
1
path.join('foo/', 'baz/', '../bar'); // => "foo/bar"
  • path.extname根据不同文件扩展名做不同操作时。
1
path.extname('foo/bar.js'); // => ".js"

遍历

递归算法

1
2
3
4
5
6
7
8
//代码简洁,重复掉用函数自身,消耗性能。
function factorial(n){
if(n === 1){
return 1;
} else {
return n * factorial(n - 1);
}
}

遍历算法

深度优先(优先子节点)+先序遍历算法(成功就返回结果,中断循环)。遍历顺序是A > B > D > E > C > F。

1
2
3
4
5
    A
/ \
B C
/ \ \
D E F

同步遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function travel(dir, callback) {
fs.readdirSync(dir).forEach(function (file) {
var pathname = path.join(dir, file);
if (fs.statSync(pathname).isDirectory()) {
travel(pathname, callback);
} else {
callback(pathname);
}
});
}

travel('/', function (pathname) {
console.log(pathname);
});
------------------------
/home/user/foo/x.js
/home/user/bar/y.js
/home/user/z.css

异步遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function travel(dir, callback, finish) {
fs.readdir(dir, function (err, files) {
(function next(i) {
if (i < files.length) {
var pathname = path.join(dir, files[i]);

fs.stat(pathname, function (err, stats) {
if (stats.isDirectory()) {
travel(pathname, callback, function () {
next(i + 1);
});
} else {
callback(pathname, function () {
next(i + 1);
});
}
});
} else {
finish && finish();
}
}(0));
});
}

文本编码

NodeJS编写前端工具时。常用的文本编码有UTF8和GBK两种,但UTF8文件还可能带有BOM。
BOM(Byte Order Marker)用于标记一个文本文件使用Unicode编码,其本身是一个Unicode字符(”\uFEFF”),位于文本文件头部。在不同的Unicode编码下,BOM字符对应的二进制字节如下:

Bytes Encoding
FE FE UTF16BE
FE FE UTF16LE
EF BB BF UTF8

Linux系统使用的 UTF32;
UTF16-LE和UTF16-BE,与计算机的CPU构架有关,常用*86系统,通常为UTF16LE(Little Endian);
一个中文字符需要3个字节才能表示。

使用nodeJs读取文件需要去掉BOM,如下:

1
2
3
4
5
6
7
8
9
function readText(pathname) {
var bin = fs.readFileSync(pathname);

if (bin[0] === 0xEF && bin[1] === 0xBB && bin[2] === 0xBF) {
bin = bin.slice(3);
}

return bin.toString('utf-8');
}

使用iconv-lite第三方包转换nodeJs不支持的GBK为UTF8,如下:

1
2
3
4
5
6
7
var iconv = require('iconv-lite');

function readGBKText(pathname) {
var bin = fs.readFileSync(pathname);

return iconv.decode(bin, 'gbk');
}

NodeJS中自带了一种binary编码可以用来实现单字节编码,用来处理未知文本编码的情况

1
2
3
4
5
function replace(pathname) {
var str = fs.readFileSync(pathname, 'binary');
str = str.replace('foo', 'bar');
fs.writeFileSync(pathname, str, 'binary');
}

网络操作

简单示例

1
2
3
4
5
6
var http = require('http');

http.createServer(function (request, response) {
response.writeHead(200, { 'Content-Type': 'text-plain' });
response.end('Hello World\n');
}).listen(8124);

linux系统下,监听1024以下端口需要root权限

网络操作API

http(HTTP)

http模块提供两种使用方式:

  • 作为服务端使用时,创建一个HTTP服务器,监听HTTP客户端请求并返回响应。
  • 作为客户端使用时,发起一个HTTP客户端请求,获取服务端响应。

服务端请求:

1
2
3
4
5
6
7
8
9
//请求内容
POST / HTTP/1.1
User-Agent: curl/7.26.0
Host: localhost
Accept: */*
Content-Length: 11
Content-Type: application/x-www-form-urlencoded

Hello World
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
http.createServer(function (request, response) {
var body = [];
console.log(request.method);
// POST
console.log(request.headers);
//{ 'user-agent': 'curl/7.26.0',host: 'localhost',accept: '*/*','content-length': '11','content-type': 'application/x-www-form-urlencoded'}

request.on('data', function (chunk) {
body.push(chunk);
});

request.on('end', function () {
body = Buffer.concat(body);
console.log(body.toString());
// Hello world
});
}).listen(80);

服务端响应:

1
2
3
4
5
6
7
8
//响应内容
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 11
Date: Tue, 05 Nov 2013 05:31:38 GMT
Connection: keep-alive

Hello World
1
2
3
4
5
6
7
8
9
10
11
http.createServer(function (request, response) {
response.writeHead(200, { 'Content-Type': 'text/plain' });

request.on('data', function (chunk) {
response.write(chunk);
});

request.on('end', function () {
response.end();
});
}).listen(80)

客户端请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var options = {
hostname: 'www.example.com',
port: 80,
path: '/upload',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
};

var request = http.request(options, function (response) {});

request.write('Hello World');
request.end();
//get存在快捷API
http.get('http://www.example.com/', function (response) {});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
http.get('http://www.example.com/', function (response) {
var body = [];

console.log(response.statusCode);
console.log(response.headers);

response.on('data', function (chunk) {
body.push(chunk);
});

response.on('end', function () {
body = Buffer.concat(body);
console.log(body.toString());
});
});

------------------------------------
200
{ 'content-type': 'text/html',
server: 'Apache',
'content-length': '801',
date: 'Tue, 05 Nov 2013 06:08:41 GMT',
connection: 'keep-alive' }
<!DOCTYPE html>
...

https(HTTPS)

https和http类似,区别在于https模块需要额外处理SSL证书。

服务端创建服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//比http多一个option对象,通过key和cert字段指定了HTTPS服务器使用的私钥和公钥。
var options = {
key: fs.readFileSync('./ssl/default.key'),
cert: fs.readFileSync('./ssl/default.cer')
};

var server = https.createServer(options, function (request, response) {
// ...
});

//NodeJS支持SNI技术,可以根据HTTPS客户端请求使用的域名动态使用不同的证书,因此同一个HTTPS服务器可以使用多个域名提供服务。
server.addContext('foo.com', {
key: fs.readFileSync('./ssl/foo.com.key'),
cert: fs.readFileSync('./ssl/foo.com.cer')
});

server.addContext('bar.com', {
key: fs.readFileSync('./ssl/bar.com.key'),
cert: fs.readFileSync('./ssl/bar.com.cer')
});

客户端与http无区别,但如果目标服务器自制SSL,并非颁发机构购买的,默认情况下https模块会拒绝连接,提示说有证书安全问题。可禁用检查,满足开发环境自制SSL证书。

1
2
3
4
5
6
7
8
9
10
var options = {
hostname: 'www.example.com',
port: 443,
path: '/',
method: 'GET',
rejectUnauthorized: false //禁用SSL证书有效性检查
};

var request = https.request(options, function (response) {});
request.end();

URL(网址)

1
2
3
4
5
6
7
8
9
                          href
-----------------------------------------------------------------
host path
--------------- ----------------------------
http: // user:pass @ host.com : 8080 /p/a/t/h ?query=string #hash
----- --------- -------- ---- -------- ------------- -----
protocol auth hostname port pathname search hash
------------
query
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// url.parse可以将URL字符串解析为URL对象
url.parse('http://user:pass@host.com:8080/p/a/t/h?query=string#hash');
/* =>
{ protocol: 'http:',
auth: 'user:pass',
host: 'host.com:8080',
port: '8080',
hostname: 'host.com',
hash: '#hash',
search: '?query=string',
query: 'query=string',
pathname: '/p/a/t/h',
path: '/p/a/t/h?query=string',
href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' }
*/

//url.format可以将URL对象转换URL字符串
url.format({
protocol: 'http:',
host: 'www.example.com',
pathname: '/p/a/t/h',
search: 'query=string'
});
/* =>
'http://www.example.com/p/a/t/h?query=string'
*/

//url.resolve方法可以用于拼接URL
url.resolve('http://www.example.com/foo/bar', '../baz');
/* =>
http://www.example.com/baz
*/

Query String(查询字符串)

querystring模块用于实现URL参数字符串与参数对象的互相转换

1
2
3
4
5
6
7
8
9
querystring.parse('foo=bar&baz=qux&baz=quux&corge');
/* =>
{ foo: 'bar', baz: ['qux', 'quux'], corge: '' }
*/

querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' });
/* =>
'foo=bar&baz=qux&baz=quux&corge='
*/

Zlib(压缩)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//判断了客户端是否支持gzip,并在支持的情况下使用zlib模块返回gzip之后的响应体数据
http.createServer(function (request, response) {
var i = 1024,
data = '';

while (i--) {
data += '.';
}

if ((request.headers['accept-encoding'] || '').indexOf('gzip') !== -1) {
zlib.gzip(data, function (err, data) {
response.writeHead(200, {
'Content-Type': 'text/plain',
'Content-Encoding': 'gzip'
});
response.end(data);
});
} else {
response.writeHead(200, {
'Content-Type': 'text/plain'
});
response.end(data);
}
}).listen(80);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var options = {
hostname: 'www.example.com',
port: 80,
path: '/',
method: 'GET',
headers: {
'Accept-Encoding': 'gzip, deflate'
}
};

http.request(options, function (response) {
var body = [];

response.on('data', function (chunk) {
body.push(chunk);
});

response.on('end', function () {
body = Buffer.concat(body);

if (response.headers['content-encoding'] === 'gzip') {
zlib.gunzip(body, function (err, data) {
console.log(data.toString());
});
} else {
console.log(data.toString());
}
});
}).end();

Net(网络)

net模块可用于创建Socket服务器或Socket客户端。

进程管理

进程管理示例

通过终端命令简化文件复制功能:

1
2
3
4
5
6
7
8
9
10
11
var child_process = require('child_process');
var util = require('util');

function copy(source, target, callback) {
child_process.exec(
util.format('cp -r %s/* %s', source, target), callback);
}

copy('a', 'b', function (err) {
// ...
});

进程管理API

Process

在NodeJS中,可以通过process对象感知和控制NodeJS自身进程的方方面面。process是一个全局对象,在任何地方都可以直接使用。

Child Process

使用child_process模块可以创建和控制子进程。该模块提供的API中最核心的是.spawn,其余API都是针对特定使用场景对它的进一步封装,算是一种语法糖。

Cluster

cluster模块是对child_process模块的进一步封装,专用于解决单进程NodeJS Web服务器无法充分利用多核CPU的问题。

参考链接
https://github.com/nqdeng/7-days-nodejs

------本文结束 感谢阅读------