node learning
引入events模块
- 就一个简单的例子,倒是是如何使用的
- 引入模块
const EventEmitter = require('events').EventEmitter;
也可以用es6的解构赋值const {EventEmitter} = require('events');
const EventEmitter = require('events').EventEmitter;
const myEmitter = new EventEmitter();
//用events之前
function fn() {
console.log('使用普通的回调')
}
setTimeout((fn) => {
fn();
},3000);
//使用events之后
setTimeout(() => {
//在这里使用这个事件
myEmitter.emit('funone');
},3000);
//注册一个事件
myEmitter.on('funone', funOne);
function funOne() {
console.log('myEmitter的输出');
}
引入url
- URL构造函数
- 获得一个URL对象
new URL('要解析的地址')
- url的resolve方法
url.resolve('目标地址', '拼接地址')
- 获得一个URL对象
获取URL对象
//方法1
const {URL} = require('url');
let urlExample = new URL('https://www.shiguangkey.com/video/1599?videoId=22524&classId=2356&playback=1');
//方法2
const URL = require('url').URL;
let urlExample = new URL('https://www.shiguangkey.com/video/1599?videoId=22524&classId=2356&playback=1');
//方法3 习惯用这个
const url = require('url');
let urlExample = new url.URL('https://www.shiguangkey.com/video/1599?videoId=22524&classId=2356&playback=1');
//urlExample是什么东西,一堆解析出来的东西
/*
URL {
href: 'https://www.shiguangkey.com/video/1599?videoId=22524&classId=2356&playback=1',
origin: 'https://www.shiguangkey.com',
protocol: 'https:',
username: '',
password: '',
host: 'www.shiguangkey.com',
hostname: 'www.shiguangkey.com',
port: '',
pathname: '/video/1599',
search: '?videoId=22524&classId=2356&playback=1',
searchParams: URLSearchParams { 'videoId' => '22524', 'classId' => '2356', 'playback' => '1' },
hash: ''
}
*/
url模块的resolve
const url = require('url');
// aa/bb/cc 当第一个参数不是根目录,并且以/结尾,等于 aa/bb/* 然后第二个参数替代*
console.log(url.resolve('aa/bb/', 'cc'));
// aa/cc 当第一个参数不是根目录,并且不以/结尾,等于 aa/* 然后第二个参数替代*
console.log(url.resolve('aa/bb', 'cc'));
// aa/cc 当第一个参数不是根目录,并且以/结尾,等于 aa/ 然后第二个参数以./代表当前目录,
console.log(url.resolve('aa/bb', './cc'));
// cc 当第一个参数不是根目录,并且以/结尾,等于 aa/ 然后第二个参数以./代表当前目录,
console.log(url.resolve('aa/bb', '../cc'));
// cc 当第一个参数不是根目录,第二个参数 /开头,代表第二个参数是根目录,所以直接 cc
console.log(url.resolve('aa/bb', '/cc'));
//http://example.com 这一块第一个参数已经是跟目录了,第二参数又根目录,所以第二个无效
console.log(url.resolve('http://example.com/', '/one'))
引入querystring模块 => querystring就是查找字符串,上例子
- eg:
//?videoId=22524&classId=2356&playback=1
//上面就是这个字符串就是请求里面的参数什么的,要把这个变成一个可遍历对象
const qs = require('querystring');
//slice主要去掉那个?
qs.parse('?videoId=22524&classId=2356&playback=1'.slice(1));
//结果
/*{
videoId: '22524',
classId: '2356',
playback: '1'
}
*/
引入assert模块 => 断言,如果不是我们期望值就会报错,如果是我们期望值则继续执行
- 直接上例子
const assert = require('assert');
assert.equal(1+2, 3, '第一个参数执行的结果不等于第二个参数报错')
assert.notEqual(1+2, 3, '第一个参数执行的结果等于第二个参数报错s')
assert.deepEqual({}, {}, '第一个参数和第二个参数的属性名和属性值要相等')
引入fs模块(文件系统模块,负责读写)
- 异步读取文件(文本文件)
let fs = require('fs');
fs.readFile('文件,写好对应的路径', '写好对应的编码', function(err,data) {
if(err) {
//打印出错误参数
console.log(err);
}else {
//打印成功的参数
//成功的时候 err会是一个null值
console.log(data);
}
})
- 读书二进制文件(图片呀之类的)
let fs = require('fs');
fs.readFile('文件,写好对应的路径', function(err,data) {
if(err) {
//打印出错误参数
console.log(err);
}else {
//会返回的是一个Buffer对象
console.log(data.length);
}
})
- 同步读取文件
let fs = require('fs');
//同步读取发现文件错误,需要用try...catch..来捕获
try {
let data = fs.readFileSync('文件路径', 'utf-8');
}catch (err) {
console.log(err);
}
- 异步写入文件
- 如果传入的数据是String,默认按UTF-8编码写入文本文件,如果传入的参数是Buffer,则写入的是二进制文件
- 如果文件不存在会自动新建一个
- 注意每次写入都会覆盖之前的文件里面内容
let fs = require('fs');
let data = '我是被写入文件的文字';
//异步写入文件
fs.writeFile('./two.txt', data, function(err) {
if (err) {
console.log(err);
}else {
console.log('success');
}
})
- 同步写入文件
let fs = require('fs');
let data = '我是被写入文件的文字';
//同步写入文件
//需要捕获错误就是try catch
try {
fs.writeFileSync('文件路径', data);
console.log('success');
}catch (err) {
console.log(err);
}
- stat => 获取文件的大小,创建时间等信息
- 是否是文件 isFile()
- 是否是文件夹 isDirectory()
- 文件大小 size
- 文件创建时间 birthtime
- 文件更改时间 mtime
let fs = require('fs');
fs.stat('filepath', function(err, stat) {
if (err) {
console.log(err);
}else {
console.log('isFile: ' + stat.isFile());
console.log(stat.isDirectory());
console.log(stat.size);
console.log(stat.birthtime);
console.log(stat.mtime);
}
})
- stat 同步写法
let fs = require('fs');
let file = fs.statSync('filePath');
try {
//是否一个文件
console.log(file.isFile());
//是否一个目录
console.log(file.isDirectory());
//大小
console.log(file.size);
//创建时间
console.log(file.birthtime);
console.log(file.mtime);
} catch (err) {
console.log('捕获错误');
}
- 判断文件,文件夹是否存在
let fs = require('fs');
fs.existsSync('./xx.txt');
//结果就会返回false或者true
同步还是异步
* 绝大部分反复执行的逻辑业务代码,异步,不然就会花费大量i/o,失去使用node的最大意义
* 读取配置文件,或者结束时候写入状态,这些开始结束只执行一次,可以使用同步操作
stream => Node.js提供的又一个仅在服务区端可用的模块,目的是支持“流”这种数据结构,流模块抽象,所以都由其他模块继承使用
* 所有读取数据的都继承自 stream.Readable
* 所有写入的流都继承自 stream.Writeable
* 流分成输入流和输出流2种
* 输入流 => 你敲打键盘输入,一个个字符就像输入流
* 输出流 => 程序处理你输入的字符,显示在显示屏里面,这就像输出流
* 在node.js中流也是一个对象,我们只需要响应流的事件就可以了
* data事件表示流的数据已经可以读取了
* end事件表示这个流已经到末尾了,没有数据可以读取了
* error事件表示出错了
- 一个最简单的输出流例子
- 要注意,data事件可能会有多次,每次传递的chunk是流的一部分数据(不是很懂data会有很多次在输出流里面)
let fs = require('fs');
//不写字符集也可以单独设置
//stream.setEncoding('utf-8');
let stream = fs.createReadStream('./one.txt', 'utf-8');
//chunk 是数据块的意思
stream.on('data', function(chunk) {
console.log('data');
console.log(chunk);
})
stream.on('end', function() {
console.log('ending啦');
})
stream.on('error', function(err) {
console.log('报错了');
console.log(err);
})
- 用输入流的写入文件,不断的调用write,最后用end结尾
- 每次end还是会完全覆盖之前的内容
let fs = require('fs');
let stream = fs.createWriteStream('./one.txt', 'utf-8');
stream.write('怎么搞的呀\n')
stream.write('googog\n')
stream.end();
- pipe => 就像可以把两个水管串成一个更长的水管一样,两个流也可以串起来。(用于复制文件)
- 一个Readable流和一个Writeable流串联起来,数据自动从Readable流进入Writable流,这种操作叫pipe
- 看一个pipe的例子,eg:
let fs = require('fs');
let rs = fs.createReadStream('文件名字a');
let ws = fs.createWriteStream('文件名字b');
rs.pipe(ws);
//默认情况下,当Readable流的数据读取完毕,end事件触发后,将自动关闭Writable流
//若不想关闭
rs.pipe(ws, {end: false})
- stream原生模块使用流
const Readable = require('stream').Readable;
const rs = new Readable;
//往流里面添加东西
rs.push(1);
rs.push(2);
//通过也是继承了stream的process.stdout来输出
rs.pipe(process.stdout)
//或者用on事件
rs.on('data', (chunk) => {
console.log(chunk);s
})
//结果: 12
引入http模块
- http => 应用程序并不直接和HTTP协议打交道,而是操作http模块提供的request和response对象
- request对象封装了HTTP请求,我们调用request对象的属性和方法就可以拿到所有HTTP请求的信息
- response对象封装了HTTP响应,我们操作response对象的方法,就可以把HTTP响应返回给浏览器
- 实现一个最简单的web程序,对于所有的请求都返回helloworld
- request的method就是请求方式 POST or GET
- request的url就是请求的地址
- response的writeHead就是把200的成功请求写入响应头
- response的end就把html内容写入进去,然后就结束
- server.listen就是监听8888端口
- response.setHeader(‘Access-Control-Allow-Origin’, ‘*’)设置跨域
//引入http模块
let http = require('http');
//创建一个服务,并传入一个回调函数
let server = http.createServer(function(request, response) {
//设置cors跨域
response.setHeader('Access-Control-Allow-Origin', '*');
console.log('请求方式:' + request.method)
console.log('请求地址:' + request.url);
response.writeHead(200, {'Content-Type': 'text/html'});
//end只能写字符串和buffer
response.end('<h1>Hello World!</h1>')
//这样拿到请求头
JSON.stringify(request.headers);
})
//监听
server.listen(8888);
console.log('Server is running at http://127.0.0.1:8888/');
//监听也有回调函数
server.listen(8888, () => {
console.log('Server is running at http://127.0.0.1:8888')
})
- 代理 => 服务器之间是不存在跨域滴
- 设置option
- 设置req 里面要写上data和end事件
- req的error事件
- 执行req的write和end方法结尾
//请求的代理服务器
const options = {
hostname: 'localhost',
port: 8888,
path: '/heart',
method: 'POST'
};
//发起请求
const req = http.request(options, res => {
res.on('data', chunk => {
response.end(chunk);
})
res.on('end', () => {
console.log('炸裂');
})
})
//写好异常抛出
req.on('error', error => {
console.log('报错啦', error);
})
//官网写write的(⊙﹏⊙),我照抄
req.write('');
req.end();
文件服务器 => Web程序我们可以设定一个目录,然后让Web程序变成一个文件服务器(组合案例)
* 解析request.url中的路径,通过parse()将一个字符串解析为一个Url对象
* 处理本地文件目录需要使用Node.js提供的path模块
* 用http发起请求
* 最后使用fs输入输出流来把东西返回到前端页面
- 解析request.url中的路径
let url = require('url');
//这样就可以打印出解析
console.log(url.parse('http://user:pass@host.com:8080/path/to/file?query=string#hash'));
/*
打印出来的结构
Url {
protocol: 'http:',
slashes: true,
auth: 'user:pass',
host: 'host.com:8080',
port: '8080',
hostname: 'host.com',
hash: '#hash',
search: '?query=string',
query: 'query=string',
pathname: '/path/to/file',
path: '/path/to/file?query=string',
href: 'http://user:pass@host.com:8080/path/to/file?query=string#hash' }
*/
- path获取当前目录路径
为什么要用path?因为path做了兼容,在不同系统下面的拼接是不一样的,node的这个path模块就做了相应的兼容
- path(这里直接代表引入的path模块)
path.resolve(__dirname); path.resolve('.');
这样都可以获得当前的文件夹路径path.parse(__filename)
解析出当前文件的路径,文件名,扩展名,完整文件名和扩展名- eg:
let path = require('path');
//当前文件的目录
let workDir = path.resolve('.');
//结果:f:\ui-template-library
// 拼接文件地址
// 组合完整的文件路径:当前目录+'pub'+'index.html':
let filePath = path.join(workDir, 'pub', 'index.html');
console.log(filePath);
//结果:f:\ui-template-library\pub\index.html
- 综合案例(文件服务器)
let fs = require('fs');
let http = require('http');
let path = require('path');
let url = require('url');
//获取当前目录
let root = path.resolve('.');
//创建一个服务
let server = http.createServer(function (request,response) {
//请求的地址
let requestUrl = request.url;
//解析请求的地址获取请求的文件路径
let pathname = url.parse(requestUrl).pathname;
//拼接本地根目录路径组成完成的本地绝对路径
//解释一下为什么不用+拼接字符串因为你拼接会是 f:\ui-template-library + /node-note.md
let filepath = path.join(root,pathname);
//获取文件信息 => 判断拿到的是不是一个文件
fs.stat(filepath, function (err, stat) {
if (stat.isFile() && !err){
//响应200表示读取成功
response.writeHead(200);
//用pipe把读取的文件响应回去前端
//解释一下,这里不再创建输出流是因为response对象本身就是一个Writable Stream
fs.createReadStream(filepath).pipe(response);
} else {
//出错了或者文件不存在
console.log('404' + request.url);
//响应404
response.writeHead(404);
response.end('404 NOT FOUND')
}
})
})
//监听8080端口
server.listen(8080);
console.log('Server is running at http://127.0.0.1:8080/');
- 综合案例联系补充 => 在浏览器输入http://localhost:8080/时,会返回404,原因是程序识别出HTTP请求的不是文件,而是目录。请修改file_server.js,如果遇到请求的路径是目录,则自动在目录下依次搜索index.html、default.html,如果找到了,就返回HTML文件的内容。
crypto => crypto模块的目的是为了提供通用的加密和哈希算法
1.MD5和SHA1
//引入模块
let crypto = require('crypto');
//用模块创建一个md5的哈希对象
let md5 = crypto.createHash('md5');
//使用md5加密
md5.update('is ok');
//可以多次调用update
md5.update('no problem');
//计算所有需要被哈希化的数据摘要,可以指定编码,这里用hex
//如果要用utf-8 是携程cp.digest('utf8')
//这里输出的是加密的结果
console.log(md5.digest('hex'));
- 如果要使用sha1 就把createHash(‘这里的值修改就对了’)
- 还有更加安全的 sha256和sha512
2.Hmac => Hmac算法也是一种哈希算法,它可以利用MD5或SHA1等哈希算法。不同的是,Hmac还需要一个密钥
疑问:这个密钥有什么鬼用
//引入模块
let crypto = require('crypto');
//用模块创建一个md5的哈希对象
let hmac = crypto.createHash('sha256', 'secret-key');
hmac.update('is ok');
hmac.update('no problem');
console.log(hmac.digest('hex'));
- 只要密钥发生了变化,那么同样的输入数据也会得到不同的签名,因此,可以把Hmac理解为用随机数“增强”的哈希算法。
3.AES => AES是一种常用的对称加密算法,加解密都用同一个密钥,crypto模块提供了AES支持,但是需要自己封装好函数
- 给个例子,暂时不深究
const crypto = require('crypto');
//加密方法
function aesEncrypt(data, key) {
const cipher = crypto.createCipher('aes192', key);
var crypted = cipher.update(data, 'utf8', 'hex');
crypted += cipher.final('hex');
return crypted;
}
//解密方法
function aesDecrypt(encrypted, key) {
const decipher = crypto.createDecipher('aes192', key);
var decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
var data = 'Hello, this is a secret message!';
var key = 'Password!';
var encrypted = aesEncrypt(data, key);
var decrypted = aesDecrypt(encrypted, key);
console.log('Plain text: ' + data);
console.log('Encrypted text: ' + encrypted);
console.log('Decrypted text: ' + decrypted);
//结果:
//Encrypted text: 8a944d97bdabc157a5b7a40cb180e713f901d2eb454220d6aaa1984831e17231f87799ef334e3825123658c80e0e5d0c
//Decrypted text: Hello, this is a secret message!