前言
在学Node.js
之前需要掌握以下技能的基础知识:
ES6
常用语法http
协议基础JavaSacript
的运行机制- 线程和进程的概念
假定你已经掌握了相关的基础知识了,那么我们今天开始正式来学习Node.js
吧!
环境准备
- 上 Node.js官网 下载安装包,下稳定版本即可。
- 傻瓜式安装,下一步下一步即可,默认是在C盘
- 新版本不需要在手动配置环境变量了,而且会默认给你装好
npm
包管理器下载完成后,打开cmd
输入以下命令测试:
node -v
npm -v
如果出现一个版本号说明安装成功啦!
- 有时候
npm
很慢,那么需要国内的镜像来替代它,首选淘宝镜像,据说10分钟同步更新,速度很快!
npm install -g cnpm --registry=https://registry.npm.taobao.org
- 测试
cnpm
cnpm -v
成功啦!下面我们开启Node.js
的学习之旅吧!
HTTP
此时此刻,我异常激动,终于学习到了服务器端啦。和其他web服务器一样,Node.js
有HTTP服务,文件的读写等操作,而且我们不必对TCP
和HTTP
协议本身进行操作,Node.js
直接把它们内置成了自己模块:http
、fs
等。和ES6
的模块化不一样,Node.js
有着自己的模块化,我们一开始就是从内置模块学习的,模块化本身我们一开始先不学习,先学会用它的内置模块,然后过一段时间的我们就有自己的体会啦,到时候我再来自己总结。
我们先来学习一下这个模块中的两个很重要的对象:request
和response
request对象封装了HTTP请求,我们调用request对象的属性和方法就可以拿到所有HTTP请求的信息
response对象封装了HTTP响应,我们操作response对象的方法,就可以把HTTP响应返回给浏览器
- 引入
http
模块
const http = require('http');
- 创建一个
http
服务,request
和response
(可以简写)作为回调函数的参数,表示当浏览器访问这个服务器的时候执行回调函数
const server = http.createServer((req, res) => {
//1.对http请求的操作,比如解析url
...
// 2.对http请求的响应,比如返回状态码,响应头,响应体
...
});
- 监听端口,以8080端口为例
server.listen(8080)
对这个过程简单了解后,下面我们可以来试试:
// 引入http模块
const http = require('http');
// 创建一个端口号为8887的http服务
http.createServer(function (request, response) {
console.log('request come', request.url)
// 允许任意url跨域,设置响应头
response.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/html'
});
// 对任意请求返回一个html
response.end('<h1>hello world</h1>');
}).listen(8887);
console.log('server listening on 8887');
启动这个服务,然后我们在浏览器输入
http://localhost:8887/
浏览器显示了:
同时我们看控制台也打印了请求的url信息:
我们也可以打开浏览器开发者工具查看Network:
响应头里面就是我们刚刚设置的字段,当然还有默认的字段。
响应体放回了一个html
让浏览器自己解析:
到目前为止我们已经算是对http
模块有了一个基本的了解了,但是这种数据返回给浏览器的类型很局限,那如果是图片、音频和视频文件这种二进制的数据该怎么发呢,那么此时此刻又需要另外一个模块了:fs
模块,并且配合http
模块一起使用。
fs
fs
模块是一个读取文件的模块,那么读取文件的操作又分为同步的操作和异步的操作,而这个模块都提供了相关的方法,我们先以本地文件的读写作为切入点。
在此之前你需要明白同步和异步的概念,这个非常重要,我简单来说一说。
同步的操作是必须等待一个操作完成才能继续执行下面的代码,以读文件为例,必须读完这个文件之后,返回了一个结果才能往后继续操作。
异步的操作是不用等待的,知识把这个将来要出现的结果放在了回调函数之中,继续执行后面的代码,等异步出现结果了再去拿到这个结果进行操作。
有了这个概念后我们开始我们的文件操作吧!以异步操作为例:(同步的方法就不用了)
- 引入
fs
模块(和上面的http
模块一样)
const fs = require('fs');
- 使用读写文件的方法
// 1.读文件 有三个参数:路径、文件编码、回调函数(有两个参数,err和data)
fs.readFile(path, encoding, callback)
// 2.写文件 有三个参数:路径、数据、回调
fs.writeFile(path,data,callback)
实操一波:
我们先写入一个文件test.txt
,然后再读取这个文件
const fs = require('fs');
fs.writeFile('./test.txt', 'hello', err => {
if (err) {
console.log('写入失败!');
} else {
console.log('test文件写入成功!');
}
});
运行后发现成功了!
而且项目中多了一个文件
然后我们对这个文件进行读取操作:
const fs = require('fs');
fs.readFile('./test.txt', 'utf-8', (err, data) => {
if (!err) {
console.log(`文件成功读取,文件内容是:${data}`);
} else {
console.log('读取文件错误!');
}
});
控制台输出了hello
,也就是说成功读到了数据。
在这里需要注意一个点就是:当读取二进制文件时,不传入文件编码时,回调函数的data参数将返回一个Buffer
对象。在Node.js
中,Buffer
对象就是一个包含零个或任意个字节的数组(注意和Array
不同)。
上面使用模板字符串将data
拼到了字符串里面,也就是说将Buffer
对象转化了字符串显示了,如果单独显示就是一个Buffer
对象,直接console
,我们看看:
如果要显示我们能看得懂的数据,有两种方式:
- 第一在
readFile
这个方法里面加上第二个参数,即编码格式 - 调用
toString
方法显式转化为字符串
当然如果需要转化成Buffer
需要使用Buffer
对象的from
方法。
下面补充一个stat
方法,这个方法是获取文件大小,创建时间等信息的。
const fs = require('fs');
fs.stat('test.txt', (err, stat) => {
if (err) {
console.log(err);
} else {
// 是否是文件:
console.log('isFile: ' + stat.isFile());
// 是否是目录:
console.log('isDirectory: ' + stat.isDirectory());
if (stat.isFile()) {
// 文件大小:
console.log('size: ' + stat.size);
// 创建时间, Date对象:
console.log('birth time: ' + stat.birthtime);
// 修改时间, Date对象:
console.log('modified time: ' + stat.mtime);
}
}
});
控制台输出:
但是,如果要读取一个很大的文件的时候,上面的那种操作很麻烦,效率也不高,那么接下来需要介绍一个很神奇的数据结构:流(stream
)
stream
流是一种抽象的数据结构。流的特点是数据是有序的,而且必须依次读取,或者依次写入,不能像Array那样随机定位。
stream
十分抽象,但对于文件的读写十分有用,它也是一个对象,并且都是EventEmitter
(以后会将到) 的实例,常见的事件有:
-
data
- 当有数据可读时触发。 -
end
- 没有更多的数据可读时触发。 -
error
- 在接收和写入过程中发生错误时触发。 -
finish
- 所有数据已被写入到底层系统时触发。
下面来看看使用流来进行文件读写操作:
和上面一样,先写入一个文件先
const fs = require('fs');
// 创建一个可以写入的流,写入到文件 test.txt 中,写入的数据全部以'utf-8'编码
let ws = fs.createWriteStream('./test.txt', 'utf-8');
// 源源不断写入数据...
ws.write('start\n');
ws.write('hello node!\n');
ws.write('end');
// 标记文件末尾
ws.end();
//处理事件
ws.on('finish', () => {
console.log('文件写入成功!');
});
ws.on('error', ()=> {
console.log('文件写入失败');
});
控制台显示
这说明,数据已经全部写入了文件,并且触发了finish
事件。
同样我们读取这个文件也有两个事件:
const fs = require('fs');
// 创建一个读取流,以'utf-8'编码
let rs = fs.createReadStream('./test.txt', 'utf-8');
// 事件监听
// 有数据时触发
rs.on('data', chunk => {
console.log('start read');
console.log(chunk);
});
// 读完触发
rs.on('end', () => {
console.log('end read ');
});
// 出错触发
rs.on('error', err => {
console.log(err.stack);
});
控制台输出:
除此之外还有一个很神奇的东西:管道流
管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。
有点像两根自来水管接在一起了,前面一根的水流到了后面那一根,流着同样的水。也就是一个文件里面数据流到了另外一个文件,其实有点像复制。
我们来瞧瞧:
const fs = require('fs');
// 创建一个读取流
let rs = fs.createReadStream('./test.txt');
// 创建一个可以写入的流,写入到文件 test.txt 中
let ws = fs.createWriteStream('./test2.txt');
//将读到的文件数据写到另一个文件
rs.pipe(ws);
此时此刻我们看多了一个test2.txt
文件,内容和test.txt
一模一样
感觉就是复制了一份一样是不是,很神奇很好玩。
常用的内置模块暂告一段落啦,以后遇到别的我会继续来补充的。