node的横空出世为JavaScript工程师打开了一片新蓝海,让JavaScript工程师可以在很低的学习成本下开发后端程序,同时由于其擎独树一帜的单线程机制可以开发出高并发的程序,禁不住它的诱惑,我也来试试水。
一.文件系统
我们知道JavaScript语言一直是前端的主流开发语言,而后端一直是c#,PHP,Java等语言的天下,这些语言都有强大的文件处理能力,而这正是JavaScript所不具有的。但是开发后端程序经常有文件的读写操作,所以要想在后端大有可为强大的文件处理能力必不可少,针对这一点node提供了很多处理文件的API。
node.js中处理文件的是fs模块
使用如下
var fs = require('fs');复制代码
这就是引入fs模块的方法是不是很简单,接下来介绍一些fs模块中的常用方法。
1.读文件操作
var fs = require("fs");复制代码
// 异步读取复制代码
fs.readFile('one.txt', function (err, data) {复制代码
if (err) {复制代码
return console.error(err);复制代码
}复制代码
console.log("异步读取: " + data.toString());复制代码
});复制代码
// 同步读取复制代码
var data = fs.readFileSync('one.txt');复制代码
console.log("同步读取: " + data.toString());复制代码
console.log("文件读取完毕");复制代码
终端输出如下:
同步异步方式都读取成功了,不同的是程序的执行顺序(后面重点讲)。
2.fs.open
fs.open(path,authority,callback)复制代码
参数如下意义如下:
path:文件路径
authority:文件权限,具体可参考www.runoob.com/nodejs/node…
callback:回调函数,参数为callbak(err,fd)
var fs = require("fs");复制代码
// 异步打开文件复制代码
console.log("准备打开文件");复制代码
fs.open('one.txt', 'r+', function(err, fd) {复制代码
if (err) {复制代码
return console.error(err);复制代码
}复制代码
console.log("文件打开成功"); 复制代码
});复制代码
console.log('程序执行完毕');复制代码
终端输出如下:
3.获取文件信息
fs.stat(path,callback)
参数说明:
path:文件路径
callback:回调函数,callback(err,stats)注意:stats是fs.Stats对象
fs.stat(path)会将stats类的实例返回给其回调函数。
var fs = require('fs');复制代码
fs.stat('C:/\WiFi_Log.txt', function (err, stats) {复制代码
console.log(stats.isFile()); 复制代码
})复制代码
console.log("程序执行完毕");复制代码
终端输出如下:
stats类中的方法具体可参考:www.runoob.com/nodejs/node…
4.文件写入
fs.writeFile(file, data, callback)复制代码
参数说明:
file:文件名
data:要写入的数据
callback:回调函数callback(err)
其它的一些文件常用文件操作api
读文件fs.read(fd, buffer, offset, length, position, callback)
关闭文件fs.close(fd, callback)
截取文件fs.ftruncate(fd, len, callback)
删除文件fs.unlink(path, callback)
创建文件目录fs.mkdir(path[, options], callback)
读取目录fs.readdir(path, callback)
删除目录fs.rmdir(path, callback)
需要注意的地方
1.注意回调函数的返回值
2.注意文件的读写权限
3.注意文件写入时是覆盖还是追加
4.注意文件先打开再对文件中的内容进行操作
5.注意对文件的操作结束之后关闭文件
二.模块体系
在一中我们的代码中有这样的代码
var fs = require('fs');复制代码
现在解释一下,这行代码中我们引用的是node文件模块。
那么node中什么是模块?怎样创建模块呢?
先回答第一个问题:模块也就是功能模块,一个模块代表着一部分的功能,我们通过调用不同的模块来使用不同的功能,进而完成我们的node程序,例如上面的我们通过fs模块来使用node中的文件处理功能。
在node中文件和模块是一一对应的,也就是说我们创建的一个文件就是一个模块,注意这个文件不一定是JavaScript代码,JSON或者c/c++也可以是node中的一个模块。
第二个文件创建一个功能模块:
首先介绍一下node中的两个对象exports和require,require是取得一个模块的接口,exports是模块文件暴露出来的接口。
我们来试着创建一个模块,并且在另一个文件中对它进行调用
新建a.js代码如下所示:
接着创建b.js并在b.js中调用a.js中的方法
这样我们就创建了一个node.js模块,并且成功的在另一个模块中对其进行了引用。
上面的例子是把一个函数封装为一个模块,接下来我们只封装一个对象。
创建一个文件c.js代码如下
接下来创建另一个文件d.js
代码如下:
注意我这里的语法我这里采用的构造函数创建的对象,ES6语法大家可以参照阮老师的资料:es6.ruanyifeng.com/(强烈推荐)
那么有人可能会说我想要写一个新的模块或者把某一个模块的功能做一部分的更改需要怎么做呢?这就需要我们了解node的模块加载过程也就是在我们require的时候过程到底是怎样的。
好吧!首先我们可以将node的模块简单粗暴的分为文件模块和原生模块,盗个图先
简单解释一下:优先级以此是:缓存原生模块>原生模块>缓存文件模块>文件模块
有人可能会问什么是原生模块:http,fs等都是原生模块
文件模块:1.绝对路径:/a/b/c.js 2.相对路径的文件模块:./a/b/c.js
补充一点:刚刚我们使用的require和exports其实是Common.js规范,我们知道javaScript被开发之出主要是用来进行浏览器端开发的引用方式是通过<script>的形式引入的,但是我们知道的传统的后端语言如PHP有include和require,所以js社区为了弥补js的混乱就制定了Common.js规范来进行约束。
在一个模块中存在存在一个module对象,代表模块自身,而exports是module的属性。
注意:require()参数所代表的的其实是模块标识,它必须是相对路径或者绝对路径或者必须是符合小驼峰命名的字符串,可以没有后缀.js
三.Buffer
我们知道作为后端程序要有处理tcp,以及对应数据流的能力,所以对于二进制的数据也要能够处理而这一点也是javaScript所不具有的,在node中提供了buffer对象来弥补这方面的不足。
buffer对象是一个像数组的的对象,而且它被放在了全局对象上,因此我们可以直接使用。
buffer对象的一些基本API
创建Buffer类
Buffer.alloc(),Buffer.from()具体使用请自行google
写入缓冲区:buf.write()
从缓冲区中读数据:buf.toString()
Buffer对象转化为JSON对象:JSON.stringify()
缓冲区合并:Buffer.concat()
缓冲区比较:
拷贝缓冲区:
前面说过buffer类似arry,所以有一些与数组类似的方法,大家可以自行查阅。
四.stream
stream的英文翻译是流,它是node.js中的一个抽象接口。那么为什么要用流或者说流有什么特点。
第一在node.js中所有stream对象都是EventEmitter的实例。这个就很有意思了,其常用的事件有:data,end,error,finish
下面来个从流中读数据的例子。
创建一个文件e.js
代码如下:
var fs = require("fs");
var data = '';复制代码
// 创建可读流复制代码
var readerStream = fs.createReadStream('one.txt');复制代码
readerStream.setEncoding('UTF8');复制代码
// 处理流事件 复制代码
readerStream.on('data', function(chunk) {复制代码
data += chunk;复制代码
});复制代码
readerStream.on('end',function(){复制代码
console.log(data);复制代码
});复制代码
readerStream.on('error', function(err){复制代码
console.log(err);复制代码
});复制代码
console.log("读取流执行完毕");复制代码
终端输入如下:
看到了吧,通读流我们可以对读取的生命周期(借用react中的术语)进行控制,我们可以在生命周期的各个节点进行一些我们所需的操作。
接下来我们再来一个写入流的例子大家来感受一下
var fs = require("fs");
var data = 'node 写入流程序实例';复制代码
var writerStream = fs.createWriteStream('one.txt');复制代码
writerStream.write(data,'UTF8');复制代码
writerStream.end();复制代码
// 处理流事件 复制代码
writerStream.on('finish', function() {复制代码
console.log("数据写入结束");复制代码
});复制代码
writerStream.on('error', function(err){复制代码
console.log(err);复制代码
});复制代码
console.log("写入流执行完毕");复制代码
接下来介绍一下管道流和链式流
管道流:从一个流中获取数据并将数据传递到另外一个流中。
var fs = require("fs");
var readerStream = fs.createReadStream('one.txt');复制代码
var writerStream = fs.createWriteStream('two.txt');复制代码
// 管道操作复制代码
readerStream.pipe(writerStream);复制代码
console.log("管道流执行完毕");复制代码
链式流:连接输出流到另外一个流并创建多个流操作链的机制
var fs = require("fs");复制代码
var zlib = require('zlib');复制代码
fs.createReadStream('one.txt')复制代码
.pipe(zlib.createGzip())复制代码
.pipe(fs.createWriteStream('two.txt.gz'));复制代码
console.log("文件压缩流完成");复制代码
五.EventEmitter
在上面我们提到了EventEmitter,这节我们就单独谈谈EventEmitter,这是node的一大特色。
首先我们要了解到node中的events模块,events.EventEmitter就是events模块提供的一个对象,eventEmitter的核心就是事件触发与事件监听器。
先演示个例子来感受一下
新建event.js,代码如下所示
var EventEmitter = require('events').EventEmitter; 复制代码
var event = new EventEmitter.EventEmitter(); 复制代码
event.on('some_event', function() { 复制代码
console.log('some_event 事件触发'); 复制代码
}); 复制代码
console.log('evnet事件开始触发');复制代码
event.emit('some_event'); 复制代码
console.log('evnet事件结束');复制代码
稍微解释一下,首先event对象通过event.on()注册了事件some_event的一个监听器,当代码执行到后面的时候event.emit(),发出了事件some_event,此时就调用了some_event的监听器。
需要注意的是对于每个事件我们可以有若干个事件监听器
接下来介绍几种常见的api
once(),为指定事件注册一个单次监听器,即 监听器最多只会触发一次,触发后立刻解除该监听器。
removeListener()移除指定事件的某个监听器
removeAllListeners()移除指定事件的所有监听器
setMaxListeners(n)
你添加的监听器超过 10 个就会输出警告信息, setMaxListeners 函数用于提高监听器的默认限制的数量
未设置setMaxListeners前
setMaxListeners(15)后
listeners(event)返回指定事件的监听器数组。
需要注意的是eventEmitter如果没有响应的监听器,node中会把它当做异常,并且导致退出程序并输出错误信息。
六.路由
既然用node可以开发后端程序,那么如何处理前端的URL请求,这就要用到node的路由机制了。
首先如果想要处理前端的URL请求,那我们就要获得URL请求的参数,通过不同的参数来响应不同的代码。
在这部分我们用到的模块是http和URL模块。
七.GET/POST
GET/POST是最长用到的两种请求,下面来简单的说一下。
创建server.js代码如下所示:
var http = require('http');复制代码
var url = require('url');复制代码
var util = require('util');复制代码
http.createServer(function(req, res){复制代码
res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});复制代码
res.end(util.inspect(url.parse(req.url, true)));复制代码
}).listen(8000);复制代码
接着我们来访问8000端口,在浏览器中输入: http://localhost:8000/type?name=hello&url=www.nihao.com
下面对代码进行一些整改,进行一些解析数据的操作
浏览器端如下所示
post请求的内容有点特殊,其请求的内容全部都在请求体中,http.ServerRequest 并没有一个属性内容为请求体,原因是等待请求体传输可能是一件耗时的工作。
八.多进程
首先我们来了解一下进程的定义:一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是系统进行资源分配和调度运行的基本单位。(大家可以自行百度在不同的教材中对进程的定义可能有所不同)
大家知道node是基于v8引擎的是单线程运行的,但是node主要特点事件驱动来处理并发的,如果是在多核cpu上可以创建多个子线程来提高整个系统的并发性能。
下面我们来看看如何在node中创建子进程
在node中用来创建子进程的模块为child_process
1.用exec()
创建process1.js
console.log("进程 " + process.argv[2] + " 正在执行" );复制代码
创建process2.js
const fs = require('fs');复制代码
const child_process = require('child_process'); 复制代码
for(var t=0; t<5; t++) {复制代码
var worker = child_process.exec('node process1.js '+t, function (error, stdout, stderr) {复制代码
if (error) {复制代码
console.log(error.stack);复制代码
console.log('Error : '+error.code);复制代码
console.log('Signal : '+error.signal);复制代码
}复制代码
console.log('stdout: ' + stdout);复制代码
console.log('stderr: ' + stderr);复制代码
});复制代码
worker.on('exit', function (code) {复制代码
console.log('子进程已退出,退出码 '+code);复制代码
});复制代码
}复制代码
在代码在运行之前我们先了解下子进程的特点,在node中每个子进程总有三个流对象:child.stdin,child.stdout和child.stderr,
通过child_process.exec()创建的子进程标准参数格式如下:child_process.exec(command[, options], callback)
commend :将要运行的命令
callback:回调函数,包含三个参数error,是stdout和stderr
child_process.spawn()
process2.js代码如下所示:
const fs = require('fs');复制代码
const child_process = require('child_process');复制代码
for(var t=0; t<5; t++) {复制代码
var worker = child_process.spawn('node', ['process1.js', t]);复制代码
worker.stdout.on('data', function (data) {复制代码
console.log('stdout: ' + data);复制代码
});复制代码
worker.stderr.on('data', function (data) {复制代码
console.log('stderr: ' + data);复制代码
});复制代码
worker.on('close', function (code) {复制代码
console.log('子进程已退出,退出码 '+code);复制代码
});复制代码
}复制代码
child_process.fork();
process2.js代码如下所示:
const fs = require('fs');复制代码
const child_process = require('child_process');复制代码
for(var t=0; t<5; t++) {复制代码
var worker = child_process.fork("process1.js", [t]); 复制代码
worker.on('close', function (code) {复制代码
console.log('子进程已退出,退出码 ' + code);复制代码
});复制代码
}复制代码
简单解释一下有人可能会问node不是单线程的吗?为什么要创建进程,这不是和PHP,Java等语言一样吗?
node中创建的子进程主要是为了充分利用计算机的性能,我们现在的系统几乎都是多核的试想一下如果在一台多核的机器上只有单个进程单线程在跑这是对机器性能的多大的浪费,所以node中的多进程主要是为了充分利用机器的性能而设计的。
在简单的深入一下,有人会问创建的子进程有啥用?没错创建子进程肯定是为了使用的,接下来我们来介绍一种最经典的模式主从模式。
所谓主从也就是一个主进程一个从进程(在这里我们把它叫做子进程吧),在一个node程序中主进程不执行业务逻辑,子进程来执行,这样主进程只用来接收消息并把不同的业务逻辑分配给不同的子进程,实现解耦同时可以有利于团队之间的开发协作。
关于进程很有很多的东西可以说比如进程支间如何通信等等 ,大家可以自己看看。
九,全局对象
在前端中有个大家都很熟悉的全局对象window,特点是它及其所有属性都可以在程序的任何地方访问,注意在ES6中全局对象和ES5中有点不同。在node中也有全局对象global ,而且我们可以直接访问到global的属性。
下面介绍几个常用的:
__filename:表示当前正在执行的脚本的文件名
__dirname: 表示当前执行脚本所在的目录。
setTimeout():全局函数在指定的毫秒数后执行指定函数,注意这个函数只执行一次
clearTimeout():全局函数用于停止一个之前通过 setTimeout() 创建的定时器。 参数 t 是通过 setTimeout() 函数创建的定时器。
setInterval():全局函数在指定的毫秒数后执行指定函数。
返回一个代表定时器的句柄值。可以使用 clearInterval(t) 函数来清除定时器。
setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
console():console 用于提供控制台标准输出
process:process 是一个全局变量,即 global 对象的属性,它用于描述当前Node.js 进程状态的对象,提供了一个与操作系统的简单接口