Node是什么
Node.js不是一门语言,是一个js运行环境。Node.js是一个基于Chrome V8引擎的JavaScript运行环境。Node.js使用了一个事件驱动、非阻塞式I/O的模型,使其轻量又高效。此外Node自带包管理工具npm(node package manager),下载前后端的包可以使用npm指令。
Node.js与js的区别
js:
- ECMAScript
- DOM
- BOM
Node.js:
- 没有DOM和BOM
- ECMAScript + es6 的相关语法在node脚本下都能使用
- 支持commonJS规范
在node环境下如何运行脚本文件
通过node 文件名来运行脚本文件,每次修改js文件需要重新执行才会生效,使用指令npm i -g nodemon安装nodemon,安装后通过nodemon 文件名运行脚本文件,可以监视文件改动,自动重启。
REPL指令
- ctrl + c 退出当前终端
- ctrl + c 连按两次,退出 Node REPL
- ctrl + d 退出 Node REPL
Node中的异步编程
Node.js异步编程的直接体现在回调函数上。
异步:就是一个不等待的过程。非阻塞。
优点:性能提高,可以处理大量的并发请求
阻塞代码
readFile
fs.readFile('/etc/passwd', 'utf8', callback);
参数:
- 文件路径
- 编码格式,如果不指定
utf-8格式,获取到的data是一个Buffer对象 - 回调函数
const fs = require('fs');
//同步方法,会产生阻塞效果
const data = fs.readFileSync('./runcode.js');
//Buffer流
console.log(data);
//调用Buffer的toString方法
console.log(data.toString());
//会等读取文件执行完后再执行
console.log('阻塞式');
非阻塞代码
异步编程,不阻塞,大大提高程序性能。
const fs = require('fs');
//异步编程
fs.readFile('./runcode.js', (err, data) => {
//第一个参数为 err ,node中的错误优先机制
if (err) {
//error.stack 属性是一个字符串,描述代码中 Error 被实例化的位置。
console.log(err.stack);
return;
}
console.log(data.toString());
})
//readFile不会阻塞下列代码执行,而readFile需要时间,所以下列代码会先执行
console.log('非阻塞式');
setTimeout(()=>{},time)就是典型的非阻塞。阻塞是按顺序执行的,而非阻塞是不需要按顺序执行的。
util.promisify(original)
const fs = require('fs');
// 使用promise
const {
promisify
} = require('util');
// promisify 将fs.readFile包装成promise对象
const readFile = promisify(fs.readFile);
async function asyncReadFile() {
try {
//让异步代码同步化
const data = await readFile('./runcode.js');
console.log(data.toString());
} catch (error) {
//打印错误的位置
console.log(error.stack);
}
}
asyncReadFile();
Generator
const fs = require('fs');
const {
promisify
} = require('util');
const readFile = promisify(fs.readFile);
function* read() {
yield readFile('./runcode.js');
}
//获取Generator对象
let gen = read();
gen.next().value.then((res) => {
console.log(res.toString());
}).catch((err) => {
console.log(err);
})
Buffer对象
// 创建一个长度为 10 的 Buffer对象
const buf1 = Buffer.alloc(10);
console.log(buf1);
// 创建一个长度为 10、且用 0x1 填充的 Buffer。
const buf2 = Buffer.alloc(10, 1);
console.log(buf2);
// 通过数据创建,创建一个包含UTF-8 编码字节
const buf3 = Buffer.from('hello world');
console.log(buf3);
// 创建一个包含字节 [1, 2, 3] 的 Buffer。
const buf4 = Buffer.from([1, 2, 3]);
console.log(buf4);
//写入
buf1.write('hello');
console.log(buf1);
//当填充满后不再填充
buf1.write('hello buffer');
//读取
console.log(buf1.toString()); //hello buff
//转换编码格式,默认 utf-8
console.log(buf1.toString('base64')); //aGVsbG8gYnVmZg==
//合并
const buf5 = Buffer.concat([buf1, buf3]);
console.log(buf5.toString());//hello buffhello world
此外还有Buffer.compare(buf1, buf2)主要用于 Buffer 实例数组的排序等函数。
Stream流
- Writable - 可写入数据的流(例如 fs.createWriteStream())
- Readable - 可读取数据的流(例如 fs.createReadStream())
管道流
管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。
复制文件
const fs = require('fs');
//创建可读流
const readerStream = fs.createReadStream('./readme.md');
//创建可写的流
const writerStream = fs.createWriteStream('./test.txt');
//通过管道进行传输
readerStream.pipe(writerStream);
console.log('复制完毕');
链式流
链式是通过连接输出流到另外一个流并创建多个流操作链的机制。链式流一般用于管道操作
压缩文件
const fs = require('fs');
const zlib = require('zlib');
const gzip = zlib.createGzip();
fs.createReadStream('./test.txt').pipe(gzip).pipe(fs.createWriteStream('./test.zip'));
console.log('文件压缩成功');
解压文件
const fs = require('fs');
const zlib = require('zlib');
const gunzip = zlib.createGunzip();
fs.createReadStream('./test.zip').pipe(gunzip).pipe(fs.createWriteStream('./1.txt'));
console.log('文件解压成功');
events
-
事件循环
- Node.js 是单进程单线程应用程序,但是因为 V8 引擎提供的异步执行回调接口,通过这些接口可以处理大量的并发,所以性能非常高。
- Node.js 几乎每一个 API 都是支持回调函数的。
- Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现。
- Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数.
-
事件驱动程序
- Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。
- 当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。
- 这个模型非常高效可扩展性非常强,因为 webserver 一直接受请求而不等待任何读写操作。(这也称之为非阻塞式IO或者事件驱动IO)
- 在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。
整个事件驱动的流程就是这么实现的,非常简洁。有点类似于观察者模式,事件相当于一个主题(Subject),而所有注册到这个事件上的处理函数相当于观察者(Observer)。
Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件。
// 引入 events 模块
const events = require('events');
// 创建 eventEmitter 对象
const eventEmitter = new events.EventEmitter();
// 绑定事件处理程序 定好一个主题
eventEmitter.on('conn', () => {
console.log('连接成功');
//触发另外的事件处理
eventEmitter.emit('data_receive');
});
// 绑定事件
eventEmitter.on('data_receive', () => {
console.log('数据接收成功');
})
// 观察者 观察对应的主题要做什么事情
eventEmitter.emit('conn');
console.log('程序执行完毕');
模块系统
为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。 模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。
Node.js提供了exports和require两个对象,其中exports是模块公开的接口,require用于从外部获取一个模块的接口。
创建模块
exports.name = () => {
console.log('Holo');
}
//也可抛出多个
exports.age = () => {
console.log(18);
}
//还可抛出数组、变量、对象等
引入模块
let Obj = require('./hello');
Obj.name()
//
Obj.age();
抛出构造函数
function Dog() {
let name;
this.setName = function (myName) {
name = myName;
}
this.getName = function () {
console.log('hello' + name);
}
}
module.exports = Dog;
引入
const Dog = require('./module');
const d1 = new Dog();
d1.setName('小黄');
d1.getName();
如果通过module.exports抛出对象,抛出的就是当前的对象,如果是通过exports抛出,会是一个键值对的形式挂载到对象上{ name: [Function], age: [Function], dog: '小黄' }。并且exports在一个文件中可以有多个抛出,而module.exports只能有一个抛出。
模块加载策略
Node.js 的 require方法中的文件查找策略如下:
由于Node.js中存在4类模块(原生模块和3种文件模块)
- http、fs、path等,原生模块。
- ./mod或../mod,相对路径的文件模块。
- /pathtomodule/mod,绝对路径的文件模块。
- mod,非原生模块的文件模块
尽管require方法极其简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同。如下图所示:
常用内置模块
fs模块
os模块
url模块
注意:使用WHATWG标准的新API可以直接const myURL = new URL('https://user:pass@sub.host.com:8080/p/a/t/h?query=string#hash');,不需要再去导入const url = require('url');。
path模块
path.join()方法会将所有给定的 path 片段连接到一起(使用平台特定的分隔符作为定界符),然后规范化生成的路径。
__dirname: 表示当前执行脚本所在的目录。
path.resolve()方法会将路径或路径片段的序列解析为绝对路径。
const path = require('path');
//二者都能获取绝对路径名
console.log(path.join(__dirname, 'demo1.js'));
console.log(path.resolve('demo1.js'));
path.extname()方法会返回 path 的扩展名
坑
注意:如果在服务器端使用path.resolve()方法返回的是服务器上的一个绝对路径,使用path.join()方法返回的是当前电脑上的绝对路径。建议使用path.join(),以免出错。
http模块
搭建服务器
//引入http模块
const http = require('http');
//创建Server对象
const app = http.createServer((req, res) => {
//中文会出现乱码,需要进行设置
res.end('hello node.js');
})
//监听端口号
app.listen(3000);
对不同的url做不同的响应处理
//引入http模块
const http = require('http');
//引入fs模块
const fs = require('fs');
//创建Server对象
const app = http.createServer((req, res) => {
/*
url: '/about',
method: 'GET',
*/
const {
url,
method
} = req;
//对路由进行判断
if (url === '/index' && method === 'GET') {
//返回一个首页给浏览器
fs.readFile('./static/index.html', 'utf-8', (err, data) => {
if (err) {
//设置状态码
res.statusCode = 500;
//返回错误信息
res.end('500 - Interval Serval Error!');
}
//成功
res.statusCode = 200;
//设置响应头 表明返回回去的一定是一个html
//一般情况下不会特意去指定,如果需要指定,一定要根据文件类型去指定
//mime 文件类型 https://www.w3school.com.cn/media/media_mimeref.asp
res.setHeader('Content-Type', 'text/html');
//返回信息
res.end(data);
});
} else if (url === '/about' && method === 'GET') {
fs.readFile('./static/about.html', 'utf-8', (err, data) => {
if (err) {
res.statusCode = 500;
res.end('500 - Interval Serval Error!');
}
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(data);
})
} else if (req.headers.accept.indexOf('image/*') !== -1 && method === 'GET') {
//对图片进行处理 注意:这边就是 `image`,匹配的是一个mime类型,而不是你的文件夹名
console.log(url);
//流的方式
fs.createReadStream(__dirname + url).pipe(res);
} else if (url === '/user' && method === 'GET') {
//处理json数据
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify([{
name: 'Alex'
}]))
} else {
//中文会出现乱码,需要进行设置
res.end('hello node.js');
}
})
//监听端口号
app.listen(3000);
express框架快速上手
- 在当前项目目录下
npm init初始化
package name: (node) --- 包名
version: (1.0.0) 1.0.1 --- 版本
description: --- 描述
entry point: (Buffer.js) index.js --- 入口文件
test command: --- 测试
git repository: --- git地址
keywords: Alex --- 关键字
author: Alex --- 作者
license: (ISC) --- 认证
也可以直接npm init --yes直接创建,然后再去修改配置文件。
- 安装express
npm install express --save为了能够安装更快,也可使用淘宝镜像安装cnpm i express -s
HelloWorld
//引入express
const express = require('express')
//引入fs
const fs = require('fs');
//引入path
const path = require('path');
//创建app实例
const app = express()
const port = 3000
//匹配的路由地址
app.get('/', (req, res) => res.send('Hello World!'))
//index路由
app.get('/index', (req, res) => {
fs.readFile('./static/index.html', 'utf-8', (err, data) => {
if (err) {
//设置状态码
res.statusCode = 500;
//返回错误信息
res.end('500 - Interval Serval Error!');
}
//成功
res.statusCode = 200;
//设置响应头 表明返回回去的一定是一个html
res.setHeader('Content-Type', 'text/html');
//返回信息
res.end(data);
});
})
//匹配所有路由
app.get('*', (req, res) => {
res.setHeader('Content-Type', 'image/*');
// console.log(path.resolve(req.url)); // D:\static\images\timg.jpeg
// console.log(path.join(__dirname, req.url)); // D:\code\MicrosoftVSCodeProjects\Study\node\static\images\timg.jpeg
fs.readFile(path.join(__dirname, req.url), (err, data) => {
if (err) {
throw err;
}
//end()或send()
res.send(data);
})
})
//监听端口
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
实现Mini的Express框架
MExpress.js
const Application = require('./application')
function MExpress() {
return new Application();
}
module.exports = MExpress;
application.js
// 需求
// 1.实现http服务器
// 2.实现get路由请求
const http = require('http');
const url = require('url');
class Application {
constructor() {
this.router = [];
}
get(path, cb) {
this.router.push({
path: path,
method: "GET",
handle: cb
})
}
listen() {
// 创建http服务器
http.createServer((req, res) => {
// 获取前端给我的url路径 pathname
const { pathname } = url.parse(req.url);
for(const route of this.router){
if(route.path === pathname){
route.handle(req,res);
return;
}
// 处理其他的路由
if(route.path === '*'){
route.handle(req,res);
return;
}
}
}).listen(...arguments)
}
}
module.exports = Application;