Enode.js 是基于v8 JavaScript引擎的 JavaScript运行时环境
任何可以使用 JavaScript来实现的应用都最终都会 使用 JavaScript 实现
当我们 执行 node ./index.js 的时候其实,后面可以拼接参数,参数就是 node 的全局对象 process ,比如 node ./index.js ddg age=20
argv: [
'E:\\node\\node.exe',
'F:\\web前端开发\\自学\\code\\01_learn-node\\02_给node传递参数\\index.js',
'ddg',
'age=20'
],
// console.log(process);
// process 是 node 的 全局对象
// process 第一层节点,有一个 argv 节点, 它表示的就是参数
// argv : argument vector 的缩写,表示 传入的具体参数
console.log(process.argv);
process.argv.forEach(item => {
console.log(item);
})
// console.log(process);
// process 是 node 的 全局对象
// process 第一层节点,有一个 argv 节点, 它表示的就是参数
// argv : argument vector 的缩写,表示 传入的具体参数
console.log(process.argv);
console.log(process.argv[2]);
console.log(process.argv[3]);
console.clear() // 清空 控制台
process.argv.forEach(item => {
console.log(item);
})
function foo() {
bar()
}
function bar() {
console.trace()
// trace 是可以打印 函数 的 调用栈的
// 输出结构如下
// at bar 表示 这个输出 在 bar 函数 里面执行
// at foo 表示 bar 函数 是在 foo 函数里面被调用
// at Object 表示 foo 在全局调用, node 会把他当成 匿名函数调用
/*
Trace
at bar (F:\web前端开发\自学\code\01_learn-node\02_给node传递参数\02_Node程序的输出.js:20:13)
at foo (F:\web前端开发\自学\\code\01_learn-node\02_给node传递参数\02_Node程序的输出.js:17:5)
at Object. (F:\web前端开发\自学\code\01_learn-node\02_给node传递参数\02_Node程序的输出.js:36:1)
at Module._compile (internal/modules/cjs/loader.js:1133:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1153:10)
at Module.load (internal/modules/cjs/loader.js:977:32)
at Function.Module._load (internal/modules/cjs/loader.js:877:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
at internal/main/run_main_module.js:18:47
*/
}
foo()
node的输出方式官方文档
这些全局对象实际上是模块中的变量,只是每个模块都有,看起来像是全局变量,在命令行交互中是不可以使用的
包括 __dirname、__filename、exports、module、require()
console.log(__dirname);
// F:\web前端开发\自学\code\01_learn-node\03_node中的全局变量
console.log(__filename);
// F:\web前端开发\自学\code\01_learn-node\03_node中的全局变量\01_特殊的全局对象.js
可以看官方文档
console.log(global);
console.log(global.process);
// 定义变量
var name = "呆呆狗"
console.log(name);
console.log(global.name);// undefined
// node 的顶级对象 是 global ,浏览器的顶级对象是 window,我们在浏览器运行的js文件中var定义的全局变量,是会被挂载到 window 上的,而 global 不会
// global 默认会挂载 process
// 再 node 中,其实每一个文件都是一个模块
commonJS 是一个 规范,最初提出来是再浏览器以外的地方使用,并且当时被命名为 Server.js,后来为了体现他的广泛性,修改为 CommonJS , 平时我们也会简称为 CJS
node 中每一个 js文件都是一个单独的模块,这个模块包括 CommonJS 规范的核心变量 :export module.exports require
// bar.js
const name = "ddg"
const age =20
function sayHello(name){
console.log('hello' + name);
}
exports.name = name
exports.age = age
exports.sayHello = sayHello
// main.js
const bar = require('./bar')
// 就相当于 bar = exports
// bar 也可以 换成 { name . age .sayHello } 解构赋值
console.log(bar);//{ name: 'ddg', age: 20, sayHello: [Function: sayHello] }
console.log(bar.name);
console.log(bar.age);
bar.sayHello('ddg')
其实就是导出的 module,源码中 module.exports = exports
查找规则
paths: [
'F:\\web前端开发\\自学\\code\\01_learn-node\\04_js-module\\02_commonjs\\node_modules',
'F:\\web前端开发\\自学\\code\\01_learn-node\\node_modules',
'F:\\web前端开发\\自学\\code\\node_modules',
'F:\\web前端开发\\自学\\node_modules',
'F:\\web前端开发\\自学\\node_modules',
'F:\\web前端开发\\node_modules',
'F:\\node_modules'
]
// console.log(module)
此图中,就是循环引入, 就是 图 数据结构, 图结构在遍历的时候,node 采用了 深度优先搜索,所以 main => aaa => ccc => ddd => eee
走到 eee 里面没有引入了,会返回上一层,看看上一层还有没有引入其他的,没有在返回,直到 main,看到还引入了 bbb 则 引入 bbb => ccc
AMD 主要是应用于浏览器的一种模块化规范
它采用的是 异步加载模块
事实上 AMD 的规范 还要早于 CommonJS,但是 CommonJS目前依然在被使用,而 AMD 使用的较少了
用起来 是 有点复杂了。需要引入一个 require.js
也是一个异步加载模块
但是他将 commonJS 的 优点吸收过来了
seajs 需要用这个依赖文件
他是 js 的一个模块化系统
导出的方式
导入的方式
在开发和封装功能的时候,通常希望将暴露 的所有接口放到一个文件中
export { } from './bar.js'
// 这样表示的是,先导入,然后 再导出
默认导出
export default function (){}
import format from ‘./’
我们可以直接给这个默认导出的函数起一个名字
如果 把 import 加载一个模块,放到 逻辑代码中,是不可以的
因为 es module 在被 JS 引擎解析时,就必须要知道他的依赖关系,由于这个时候,无法执行,所以不能确定依赖关系,所有会报错
let flag = true
if (flag) {
import('./bar.js').then(aaa => {
console.log()
})
}
const path = require('path')
// 1、获取路径的信息
const filepath = '/User/ddg/abc.md'
console.log(path.dirname(filepath));
// /User/ddg
console.log(path.basename(filepath));
// abc.md
console.log(path.extname(filepath));
// .md
// 2、join 路径拼接
const basepath = "/User/ddg"
const filename= '/abc.md'
const joinUrl = path.join(basepath,filename)
console.log(joinUrl);// \User\ddg\abc.md
// 3、resolve 路径拼接
// 它会解析第一个参数 的 / ./ ../ ,
// / 表示这个文件地址的根磁盘
// 如果第一个参数 没有加 / ./ ../ 那么,则会拼接上 绝对路径
// 如果第一个往后的参数有 / 则会 直接返回最后一个参数的路径
const joinUrl2 = path.resolve(basepath,filename)
console.log(joinUrl2);// F:\User\ddg\abc.md
读取文件信息的三种方式
const fs = require('fs')
// 读取文件的信息
const filepath = './abc.txt'
// 1、同步操作 读取文件信息
const info = fs.statSync(filepath)
// console.log('后续要执行的代码会被阻塞');
// console.log(info);
// 2、异步操作 读取文件信息
fs.stat(filepath, (err, info) => {
if(err){
console.log(err);
return
}
console.log(info);
})
// console.log('后续要执行的代码会被阻塞');
// 3、promise
fs.promises.stat(filepath).then(info=>{
console.log(info);
}).catch((err)=>{{console.log(err);}})
console.log('后续要执行的代码会被阻塞');
文件描述符
const fs = require('fs')
fs.open('./abc.txt', (err, fd) => {
if (err) {
console.log(err);
return
}
// 通过描述符获取信息
fs.fstat(fd, (err, info) => {
if(err){
console.log(err);
return
}
console.log(info);
})
})
文件的读写
const fs = require('fs')
// 文件写入
fs.writeFile('./abc.txt','呆呆狗2',{flag:"a"},err=>{
console.log(err);
})
/*
1、 w 打开文件写入 默认值。 会覆盖掉原先的内容
2、 w+ 打开文件进行读写,如果不存在则创建文件
3、 r+ 打开文件进行读写,如果不存在那么抛出异常
4、 r 打开文件读取,读取时的默认值
5、 a 打开要写入的文件,将流放在文件末尾。如果不存在则创建文件
6、 a+ 打开文件以进行读写,将流放在文件末尾,如果不存在则创建文件
*/
// 文件读取
fs.readFile('./abc.txt',{encoding:'utf-8'},(err,data)=>{
console.log(data);
// 不设置 encoding:'utf-8' 就显示二进制的编码
})
对文件夹的操作
const fs = require('fs')
// 1、创建文件夹
const dirname = './ddg'
if (!fs.existsSync(dirname)) {
fs.mkdir(dirname,err=>{
console.log(err);
})
}
// 2、读取文件夹中的所有文件
fs.readdir(dirname,(err,files)=>{
console.log(files);// [ 'a.txt', 'b.txt' ]
})
// 3、重命名 文件
// 旧路径 新路径 回调函数
fs.rename('./ddg','./ddg2',err=>{
console.log(err);
})
node中的核心API 都是基于异步事件驱动的。
在这个体系中,某些对象(发射器(Emiteers))发出某一个事件
我们可以监听这个事件 (监听器 Listeners),并且传入的回调函数,这个回调函数会在监听到事件时调用
发出事件和监听事件 都是通过 EventEmitter 类 来完成的,他们都属于 events 对象
emitter.on(eventName,listener): // 监听事件,也可以使用 addListener
emitter.off(eventName,listener): // 移除事件,也可以使用 removeListener
emitter.emit(eventName[,...args]): // 发出事件,可以携带一些参数
每一个项目都对应一个配置文件(package.json),包括项目名称、使用的插件、版本号、项目描述等等
npm init -y
比如版本 2.23.8
^2.23.8 表示 第一个数保持不变的, 第二个和第三个 永远安装最新的版本
~2.23.8 表示 第一个和第二个保持不变的,第三个永远安装最新的版本
计算机追踪所有的内容:文字、数字、图片、音频、视频都终会使用二进制来表示
对于前端来说,很少会和二进制打交道,但是对于服务器端为了做很多的功能,必须直接去操作二进制的数据
所以 node 为了 可以方便开发者完成更多功能,提供给了我们一个类 Buffer , 并且它是全局的
Buffer 看成是一个存储二进制的数组,数组中的每一项,可以保存 8位 二进制
const message = "hello world"
// 第一种 1、创建 Buffer
// 打印出来的,是和 message 一一对应的,打印出来的 每一组数字,是 16进制的
//const buffer = new Buffer(message)
//console.log(buffer); //
// 第二种 2、
const buffer2 = Buffer.from(message)
console.log(buffer2);
const message = "呆呆狗"
// 其实 一个汉字 对应 三个字节码,
const buffer2 = Buffer.from(message)
console.log(buffer2);
//
// 解码
console.log(buffer2.toString());// 呆呆狗
const fs = require('fs')
// 1、对文本文件的操作
fs.readFile("./foo.txt", { encoding: 'utf-8' }, (err, data) => {
console.log(data);
// 如果不传 utf-8 输出的,其实是 Buffer。本质上,我们读取到的东西都是二进制
})
// 2、对 图片的操作
fs.readFile('./one.png', (err, data) => {
console.log(data); // 读取到的 也是 Buffer
// 我们可以在写入 文件
fs.writeFile('./one_copy.png', data, err => {
// 这里的 data 表示要写入谁 就是 图片的 Buffer
console.log(err);
})
})
// npm i sharp 先安装依赖,然后 裁剪,然后输出
sharp('./one.png').resize(200,200).toFile('./baz.png')
事件循环 可以理解成 我们编写 js 代码 和 浏览器 或者 node 之间的一个桥梁
浏览器的事件循环是 一个我们编写的 js 代码和 浏览器 API 调用 的一个桥梁,桥梁之间他们通过回调函数进行沟通
node 的事件循环是一个我们编写 js 代码 和 系统调用 (file、system、network) 之间的一个桥梁,桥梁 之间他们通过回调函数进行沟通的
进程: 计算机已经运行的程序
线程:操作系统能够运行运算调度的最小单位
进程更像是线程的容器
操作系统就像是一个工厂,工厂里面有很多车间,这个车间就是进程,工人就是线程
第一道
setTimeout(function () {
console.log("set1");
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2");
});
});
new Promise(function (resolve) {
console.log("pr1");
resolve();
}).then(function () {
console.log("then1");
});
setTimeout(function () {
console.log("set2");
});
console.log(2);
queueMicrotask(() => {
console.log("queueMicrotask1")
});
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3");
});
// pr1
// 2
// then1
// queuemicrotask1
// then3
// set1
// then2
// then4
// set2
分析:
- 先输出 pr1 , then1 进入 微任务队列
- 输出 2 , queueMicrotask 创建一个微任务
- then3 加入微任务
- 主线程 执行完毕,执行 第一波微任务 依次输出 then1,queueMicrotask,then3,此时已经没有微任务了,继续执行下一个宏任务
- 第一个定时器, 输出 set1 ,把 第一个定时器的 then 加入微任务队列
- 输出 then2
- 这一次的宏任务执行完毕,继续执行这一次的微任务,输出 then4
- 执行第二个定时器
第二道
async function async1 () {
console.log('async1 start')
await async2();
console.log('async1 end')
}
async function async2 () {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1();
new Promise (function (resolve) {
console.log('promise1')
resolve();
}).then (function () {
console.log('promise2')
})
console.log('script end')
// script start
// async1 start
// async2
// promise1
// script end
// aysnc1 end
// promise2
// setToueout
注意:await 下面的代码是微任务
如果我们 希望在程序中对一个文件进行操作,那么我们就需要打开这个文件内: 通过 文件描述符
js 可以对一个文件进行操作嘛?
看起来是可以的,但是事实上我们任何程序中的文件操作 都是 需要进行 系统调用
事实上,对文件的操作,是一个操作系统的系统调用 (IO系统,IO是输入、输出)
操作系统为我们提供了两种调用方式 :阻塞时调用 和 非阻塞式调用
阻塞式:调用结果返回之前,当前线程处于阻塞态(阻塞态CPU是不会分配时间片的),调用线程只有在得到调用结果之后,才会继续执行
非阻塞式:带哦用执行之后,当前线程不会停止执行,只需要过一段时间来检查有没有结果返回即可
所以在开发中的很多 耗时操作,都可以基于这样的 非阻塞式 调用
非阻塞式调用 也存在一定问题,,我们不一定获取到 需要 读取的结果。不知道返回来的数据是否是完整的,所以为了知道是否读取完成,我们需要频繁的去确定读取到的数据是否是完整的,这个过程称之为 轮询操作
这个轮询工作有谁完整?
我们开发中不只是一个文件的读写,可能是多个文件,也可能是 网络的IO、数据库的IO、子线程调用
libuv 提供了一个 线程池
一次事件循环,就是一次 tick
微任务队列
事件循环 执行顺序就是
面试题1
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout0')
}, 0)
setTimeout(function () {
console.log('setTimeout2')
}, 300)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick1'));
async1();
process.nextTick(() => console.log('nextTick2'));
new Promise(function (resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function () {
console.log('promise3')
})
console.log('script end')
// script start
// async1 start
// async2
// promise1
// promise2
// script end
// nextTick1
// nextTick2
// async1 end
// promise3
// setTimeout0
// setImmediate
// setTimeout2
第二道
setTimeout(() => {
console.log("setTimeout");
}, 0);
setImmediate(() => {
console.log("setImmediate");
});
// 问题: setTimeout setImmediate
不一定先输出谁
执行 main script 也需要事件,执行完以后 要 初始化事件循环 也需要事件
初始化事件循环 和 main script 是同时进行的,但事件循环初始化完毕后,就要执行事件循环了
不好理解。。。。。
const http = require('http')
const server = http.createServer((req, res) => {
res.end('hello world')
})
server.listen(8000, () => {
// 端口 尽量不要写 1024 以下的
console.log('启动成功');
})
// 第一种
const http = require('http')
const server = http.createServer((req, res) => {
res.end('hello world')
})
server.listen(8000, () => {
// 端口 尽量不要写 1024 以下的
console.log('server1启动成功');
})
// 第二种
const server2 = new http.Server((req, res) => {
res.end('Server2')
})
server2.listen(8001, () => {
console.log('server2启动成功');
// 端口话如果不写,系统会默认分配一个,项目中一般都会写到环境变量中
// 也可以通过 server2.address().port 来获取到系统分配的端口号
})
const http = require('http')
const url = require('url')
const qs = require('querystring')
const server = http.createServer((req, res) => {
// request 对象中封装了客户端给我们服务器传递过来的 所有信息
console.log(req.url);
console.log(req.method);
console.log(req.headers);// 请求头
// if (req.url === '/login') {
// res.end('欢迎回来')
// } else if (req.url === '/users') {
// res.end('用户列表')
// } else {
// res.end('错误请求,检查~')
// }
// 如果 遇到/login?username=呆呆狗&password=123 呢
// 最好的方式是用 内置模块 url
const ressult = url.parse(req.url)
console.log(ressult); // 有一个 pathname 这个表示基准路径,query表示?后面的东西
// 如果我们想获取后面的查询参数,则可以根据内置模块 querystring
const qsSecond = qs.parse(ressult.query) // 会反应一个对象,键值对形式
console.log(qsSecond);
res.end('hello world')
})
server.listen(8000, () => {
// 端口 尽量不要写 1024 以下的
console.log('启动成功');
})
const http = require('http')
const url = require('url')
const qs = require('querystring')
const server = http.createServer((req, res) => {
// request 对象中封装了客户端给我们服务器传递过来的 所有信息
const { pathname } = url.parse(req.url)
if (req.method === 'POST') {
// 拿到 body 的数据
req.setEncoding('utf-8')
req.on('data', (data) => {
console.log(data); // 这里的 data 是字符串
let dataNew = JSON.parse(data)
})
// 可以通过监听这个事件,获取post 的 body值,获取的是 Buffer
// 1、data.toString()
// 2、在监听事件前面声明, req.setEncoding('utf-8') utf-8 表示文件
// 图片视频 等等,要设置成 req.setEncoding('binary')
}
res.end('hello world')
})
server.listen(8000, () => {
// 端口 尽量不要写 1024 以下的
console.log('启动成功');
})
content-type : 是这次请求携带的数据的类型
content-length :文件的大小和长度
connection : 的 keep-alive
http是基于 TCP 协议的,但是通常在进行一次请求和响应结束后会立刻中断
在 http1.0 中,如果想要继续保持链接
在 http1.1中,所有连接默认是 connection : keep-alive 的
不同的 web 服务器会有不同的保持 keep-alive 的时间
node 默认是 5S
const http = require('http')
const server = http.createServer((req, res) => {
// res.end('hello world')
res.write("响应结果一")
res.end()
// res.end 其实是 执行两个东西,一个是写入结果,一个是 res.end()
})
server.listen(8000, () => {
// 端口 尽量不要写 1024 以下的
console.log('启动成功');
})
const http = require('http')
const server = http.createServer((req, res) => {
// 设置状态码
// 方式一: 直接给属性赋值
res.statusCode = 401
// 方式二:和 head 一起设置
res.writeHead(503, {
})
// res.end('hello world')
res.write("响应结果一")
res.end()
// res.end 其实是 执行两个东西,一个是写入结果,一个是 res.end()
})
server.listen(8000, () => {
// 端口 尽量不要写 1024 以下的
console.log('启动成功');
})
const http = require('http')
const server = http.createServer((req, res) => {
// 响应的hader
// 设置方式 1
res.setHeader("Content-Type", "text/plain;charset=utf8")
// 设置方式2
res.writeHead(200, {
// 第一个参数是状态码,第二个 可以设置 响应的hader
"Content-Type": 'text/plain;charset=utf8',
// 如果我们要返回一个 '你好
' 呢?
// 这就要设置成 text/html;charset=utf-8 了
})
// res.end('hello world')
res.write("响应结果一")
res.end()
// res.end 其实是 执行两个东西,一个是写入结果,一个是 res.end()
})
server.listen(8000, () => {
// 端口 尽量不要写 1024 以下的
console.log('启动成功');
})
const http = require('http')
http.get('http://localhost:8888', (res) => {
res.on('data', (data) => {
// 获取 请求的结果
console.log(data.toString());
})
res.on('end', () => {
// 监听是否获取到了所有的结果i
console.log('获取到了所有的结果');
})
})
// http 没有 http.post 方法
const result = http.request({
method: 'POST',
hostname: 'localhost',
port: 8888,
}, (res) => {
res.on('data', (data) => {
// 获取 请求的结果
console.log(data.toString());
})
res.on('end', () => {
// 监听是否获取到了所有的结果i
console.log('获取到了所有的结果');
})
})
result.end()
// 传递的是一个 xxxxxxxx.png
const http = require('http');
const fs = require('fs');
const qs = require('querystring');
const server = http.createServer((req, res) => {
if (req.url === '/upload') {
if (req.method === 'POST') {
req.setEncoding('binary');
let body = '';
const totalBoundary = req.headers['content-type'].split(';')[1];
const boundary = totalBoundary.split('=')[1];
req.on('data', (data) => {
body += data;
});
req.on('end', () => {
console.log(body);
// 处理body
// 1.获取image/png的位置
const payload = qs.parse(body, "\r\n", ": ");
const type = payload["Content-Type"];
// 2.开始在image/png的位置进行截取
const typeIndex = body.indexOf(type);
const typeLength = type.length;
let imageData = body.substring(typeIndex + typeLength);
// 3.将中间的两个空格去掉
imageData = imageData.replace(/^\s\s*/, '');
// 4.将最后的boundary去掉
imageData = imageData.substring(0, imageData.indexOf(`--${boundary}--`));
fs.writeFile('./foo.png', imageData, 'binary', (err) => {
res.end("文件上传成功~");
})
})
}
}
});
server.listen(8000, () => {
console.log("文件上传服务器开启成功~");
})
方式一
// 安装脚手架
npm i express-generator -g
// 创建项目
express express-demo
// 安装依赖
npm i
// 启动项目
node bin/www
npm init -y
npm i express
const express = require('express')
// express 其实是一个函数
const app = express()
// 监听默认路径
app.get('/', (req, res, next) => {
res.end('hello ')
})
app.post('/', (req, res, next) => {
res.end('hello post')
})
app.post('/login', (req, res, next) => {
res.end('login')
})
// 开启监听
app.listen(8000, () => {
console.log('express 初体验启动成功~');
})
const express = require('express')
const app = express()
// app.use 注册一个全局的中间价
app.use((req, res, next) => {
console.log('01第一个普通的中间间');
// 不论我们发起什么请求,都会执行 这个 console.log('01第一个普通的中间间')
//res.end('hello wolrd')// end 不会妨碍 next 但每个中间件都有 res.end 会报错
next() // 调用下一个中间件
})
app.use((req, res, next) => {
console.log('02第2个普通的中间间');
res.end('hello wolrd')
})
app.listen(8000, () => {
console.log('服务器启动成功·');
})
const express = require('express')
const app = express()
// 路径匹配中间件 ,路径中间件也可以有 多个相同的,只需要写一个 next 就可以执行多个
// 没有 next 永远匹配到第一个 中间件
app.use('/home', (req, res, next) => {
console.log('home 01');
res.end('home 01')
})
app.listen(8000, () => {
console.log('服务器启动成功·');
})
const express = require('express')
const app = express()
// 配置 解析json的中间件
app.use(express.json())
// 配置解析urlencoded 的中间件
app.use(express.urlencoded({
extended: false
}))
app.post('/login', (req, res, next) => {
console.log(req.body);
})
app.listen(8000, () => {
console.log('服务器启动成功·');
})
extended: false
:表示使用系统模块querystring来处理,也是官方推荐的extended: true
:表示使用第三方模块qs来处理非文件的键值对数据
const express = require('express')
// 1、导入
const multer = require('multer')
const app = express()
// 配置 解析json的中间件
app.use(express.json())
// 配置解析urlencoded 的中间件
app.use(express.urlencoded({
extended: false
}))
// 2、创建
const upload = multer()
// 3、创建
// 注意 如果解析的 form-data 不是文件,可以用 any
app.use(upload.any())
// form-data 的 请求数据怎么解析? 这里要用到 express 推荐的一个库,multer
// form-data 的数据,目前大多都是 上传文件
app.post('/login', (req, res, next) => {
console.log(req.body);
})
app.listen(8000, () => {
console.log('服务器启动成功·');
})
实现multer 文件上传
注意 永远不要把 multer 作为全局中间件使用
const express = require('express')
// 1、导入
const multer = require('multer')
const app = express()
// 配置 解析json的中间件
app.use(express.json())
// 配置解析urlencoded 的中间件
app.use(express.urlencoded({ extended: false }))
// 2、创建
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, './uploads/')
},// 目的地
filename: (req, file, cb) => {
cb(null, "foo.png")
// 名字不会写死的
// Date.now() 当前的时间戳
// path.extname(file.originalname) file.originalname 是原始文件的名字
// path.extname 取出后缀名
}
})
const upload = multer({
//dest: './uploads/',
// 上传文件的存储路径
storage
})
// 这个创建的配置,也可以自定义,因为,不定义的话 上传的文件,是没有后缀名的
// 3、创建
// 注意 如果解析的 form-data 不是文件,可以用 any
// 注意 永远不要把 multer 作为全局中间件使用
app.use(upload.any())
// form-data 的 请求数据怎么解析? 这里要用到 express 推荐的一个库,multer
// form-data 的数据,目前大多都是 上传文件
app.post('/login', (req, res, next) => {
console.log(req.body);
})
app.post('/upload', upload.single('file'), (req, res, next) => {
// 如果是上传单个文件,是 single ,多个文件是 array
console.log('文件上传成功');
console.log(req.files);// 上传文件后的信息
res.end('上传成功')
})
app.listen(8000, () => {
console.log('服务器启动成功·');
})
// express 官方的 morgan 库
const loggerWriter = fs.createWriteStream('./log/access.log',{ flag: 'a+'})
app.use(morgan('combined', {stream : loggerWriter}))
const express = require('express')
// express 其实是一个函数
const app = express()
// 监听默认路径
app.get('/home/:id/:name', (req, res, next) => {
console.log(req.params);
res.end('hello')
})
app.get('/home', (req, res, next) => {
console.log(req.query);
res.end('hello')
})
// 开启监听
app.listen(8000, () => {
console.log('express 初体验启动成功~');
})
const express = require('express')
// express 其实是一个函数
const app = express()
// 监听默认路径
app.get('/', (req, res, next) => {
res.end('hello ')
})
app.post('/', (req, res, next) => {
res.end('hello post')
})
app.post('/login', (req, res, next) => {
res.end('login')
})
// 开启监听
app.listen(8000, () => {
console.log('express 初体验启动成功~');
})
将路由抽离为单独模块的步骤如下:
入口文件
const express = require('express')
const userRouter = require('./routers/users')
// express 其实是一个函数
const app = express()
app.use(userRouter)
// app.use('/api',userRouter) ,则表示添加了路由前缀 localhost:8000/api/
// 开启监听
app.listen(8000, () => {
console.log('express 初体验启动成功~');
})
路由文件
const express = require('express')
const router = express.Router()
router.get('/', (req, res, next) => {
res.json({ name: '呆呆狗', age: 20 })
})
router.post('/', (req, res, next) => {
res.end('hello post')
})
router.post('/login', (req, res, next) => {
res.end('login')
})
module.exports = router
const express = require('express')
// express 其实是一个函数
const app = express()
app.use(express.static('./build'))
// 开启监听
app.listen(8000, () => {
console.log('express 初体验启动成功~');
})
app.get('/',function(req,res){ // 1、路由
throw new Error('服务器内部发生了错误!')// 2、抛出一个自定义的错误
// 如果没有错误级别的中间件,代码到这里就结束了
res.send('Home Page')
})
// 错误的中间件,必须要在所有路由之后
// 也可以用 next(new Error()) 抛出错误
app.use(function(err,req,res,next){ // 1、错误级别的中间件
console.log('发生了错误'+err.message)// 2、在服务器打印错误消息
res.send('Error!'+err.message)// 3、向客户端响应错误相关的内容
// 其实在真实开发中,这里用的是 switch 语句,判断响应的错误,然后 返回
})
const express = require('express');
const app = express();
const USERNAME_DOES_NOT_EXISTS = "USERNAME_DOES_NOT_EXISTS";
const USERNAME_ALREADY_EXISTS = "USERNAME_ALREADY_EXISTS";
app.post('/login', (req, res, next) => {
// 加入在数据中查询用户名时, 发现不存在
const isLogin = false;
if (isLogin) {
res.json("user login success~");
} else {
// res.type(400);
// res.json("username does not exists~")
next(new Error(USERNAME_DOES_NOT_EXISTS));
}
})
app.post('/register', (req, res, next) => {
// 加入在数据中查询用户名时, 发现不存在
const isExists = true;
if (!isExists) {
res.json("user register success~");
} else {
// res.type(400);
// res.json("username already exists~")
next(new Error(USERNAME_ALREADY_EXISTS));
}
});
app.use((err, req, res, next) => {
let status = 400;
let message = "";
console.log(err.message);
switch(err.message) {
case USERNAME_DOES_NOT_EXISTS:
message = "username does not exists~";
break;
case USERNAME_ALREADY_EXISTS:
message = "USERNAME_ALREADY_EXISTS~"
break;
default:
message = "NOT FOUND~"
}
res.status(status);
res.json({
errCode: status,
errMessage: message
})
})
app.listen(8000, () => {
console.log("路由服务器启动成功~");
});
koa node.js的下一代 web 框架
事实上 express 和 koa 是同一个开发团队开发的
const Koa = require('koa')
const app = new Koa()
// koa 把所有的中间件都执行完以后,都没有返回结果的话,会返回 not found
app.use((ctx, next) => {
// 第一个参数是上下文
// 这个上下文 包含 request 和 response
console.log(ctx.request);
ctx.response.body = "hello world"
})
app.listen(8000, () => {
console.log('http://localhost:8000');
})
koa 没有提供 app.get/post ,没有提供 匹配路径的路由,没有提供连续注册
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next) => {
// 第一个参数是上下文
// 这个上下文 包含 request 和 response\
if (ctx.request.url === '/login') {
if (ctx.request.method === 'GET') {
console.log('来到了这里面');
ctx.response.body = "Login Suceess"
}
}
ctx.response.body = "请求成功~"
})
app.listen(8000, () => {
console.log('http://localhost:8000');
})
koa 本身没有路由,需要借助第三方
npm i koa-router
路由文件
const Router = require('koa-router')
const router = new Router({
prefix: "/users", // 路径前缀
})
router.put('/', (ctx, next) => {
ctx.response.body = "put 成功~"
})
module.exports = router
入口文件
const Koa = require('koa')
const userRouter = require('./router/user')
const app = new Koa()
app.use(userRouter.routes())
app.use(userRouter.allowedMethods())
// allowedMethods 用于判断某一个 method 是否支持
// 如果没有实现某个请求,就会自动报错 405
app.listen(8000, () => {
console.log('http://localhost:8000');
})
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const userRouter = new Router({
prefix: "/users", // 路径前缀
})
userRouter.get('/:id', (ctx, next) => {
console.log(ctx.request.params);
console.log(ctx.request.query);
})
app.use(userRouter.routes())
app.listen(8000, () => {
console.log('http://localhost:8000');
})
npm i koa-bodyparser
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
const app = new Koa()
app.use(bodyParser())
app.use((ctx, next) => {
console.log(ctx.request.body);
ctx.response.body = "hello"
})
app.listen(8000, () => {
console.log('http://localhost:8000');
})
npm i koa-multer
const Koa = require('koa')
const multer = require('koa-multer')
const app = new Koa()
const upload = multer()
// 真实开发中,不建议 吧 multer 写入全局中间件
app.use(upload.any())
app.use((ctx, next) => {
// 注意,解析 form-data 这里 用的是 req.body
console.log(ctx.req.body);
ctx.response.body = "hello"
})
app.listen(8000, () => {
console.log('http://localhost:8000');
})
const Koa = require('koa')
const Router = require('koa-router')
const multer = require('koa-multer')
const app = new Koa()
const uploadRouter = new Router({ prefix: '/upload' })
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, './uploades/')
},// 目的地
filename: (req, file, cb) => {
cb(null, "foo.png")
// 名字不会写死的
// Date.now() 当前的时间戳
// path.extname(file.originalname) file.originalname 是原始文件的名字
// path.extname 取出后缀名
}
})
const upload = multer({ storage })
uploadRouter.post('/avatar', upload.single('avatar'), (ctx, next) => {
console.log(ctx.req.file);
ctx.response.body = "上传头衔成功~"
})
app.use(uploadRouter.routes())
app.listen(8000, () => {
console.log('http://localhost:8000');
})
输出结果
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next) => {
ctx.response.body = {
ddg: '呆呆狗'
}
// 也可以 用 ctx.body = 返回数据
// 本质上说,你写 ctx.body 其实 就是 ctx.response.body
ctx.status = 203
// 对象返回的是 json 格式
})
app.listen(8000, () => {
console.log('http://localhost:8000');
})
npm i koa-static
const Koa = require('koa')
const static = require('koa-static')
const app = new Koa()
app.use(static('./build'))
app.listen(8000, () => {
console.log('静态资源服务器启动成功~');
})
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next) => {
const isLogin = false
if (!isLogin) {
ctx.app.emit('error', new Error('你还没有登录~'), ctx)
}
})
app.on('error', (err, ctx) => {
// 一般来说也是先 switch 判断
ctx.status = 200,
ctx.body = "你还没有登录"
})
app.listen(8000, () => {
console.log('服务器启动成功~');
})
从架构上来说
需求: 假如有三个中间件,会在一次请求中匹配到,并且按照顺序执行
- 在middleware1 中,在 req.message 中添加一个字符串 aaa
- 在middleware2 中,在 req.message 中添加一个字符串 bbb
- 在middleware3 中,在 req.message 中添加一个字符串 ccc
当所有内容添加结束后,在 middleware1 中 通过 res 返回最终的结果
const express = require('express')
const app = express()
const middleware1 = (req, res, next) => {
req.message = "aaa"
console.log(1);
next()
res.end(req.message)
console.log(5);
}
const middleware2 = (req, res, next) => {
req.message += "bbb"
console.log(2);
next()
console.log(4);
}
const middleware3 = (req, res, next) => {
req.message += "ccc"
console.log(3);
}
app.use(middleware1, middleware2, middleware3)
app.listen(8000, (() => {
console.log('启动成功');
}))
/*
* 1、先去 middleware1 赋值,然后 遇到 next 他会执行 middleware2 不会先执行 res.end()
* 2、执行完 middleware3 ,middleware3没有 next 就会回去,倒叙执行 middleware2 和 middleware1 剩下的代码
*/
// 输出的结果也是 12345
const middleware3 = (req, res, next) => {
// 假如,要在这个未知, 发起一个异步请求,然后,再拼接值,在返回出去,
// 结果不会是我们想要的
// 所以 express 处理这些异步数据,是有些乏力的
// 也可以再外面定义一个函数,先请求,
req.message += "ccc"
console.log(3);
}
const Koa = require('koa');
const app = new Koa();
const middleware1 = (ctx, next) => {
ctx.message = "aaa";
next();
ctx.body = ctx.message;
}
const middleware2 = (ctx, next) => {
ctx.message += "bbb";
next();
}
const middleware3 = (ctx, next) => {
ctx.message += "ccc";
}
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.listen(8000, () => {
console.log("服务器启动成功~");
})
koa中的next是借助promise,可以做一些异步的处理
const Koa = require('koa');
const axios = require('axios');
const app = new Koa();
const middleware1 = async (ctx, next) => {
ctx.message = "aaa";
await next();
ctx.body = ctx.message;
}
const middleware2 = async (ctx, next) => {
ctx.message += "bbb";
await next();
}
const middleware3 = async (ctx, next) => {
const result = await axios.get('http://123.207.32.32:9001/lyric?id=167876');
ctx.message += result.data.lrc.lyric;
}
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.listen(8000, () => {
console.log("服务器启动成功~");
})