学习nodejs 中的koa2
一. 什么是koa
类似于 express , koa是一个基于nodejs的服务端框架
二. koa的简单使用
-
初始化项目
npm init
-
安装koa
npm install koa --save
在app.js中(自定义项目启动的入口文件)
实例化koa
const Koa = require('koa');
const app = new Koa();
-
设置监听访问页面路径的中间件(koa中一切皆是中间件)
// 为了方便监听, 需要从koa中引入路由中间件 const Router = require('koa-router'); const router = Router() // 设置全局中间件 app.use(async(ctx, next) => { console.log('I am the first middleware') await next() console.log('first middleware end calling') }) app.use(async (ctx, next) => { console.log('I am the second middleware') await next() console.log('second middleware end calling') }) // 定义路由 router.get('/api/test1', async(ctx, next) => { console.log('I am the router middleware => /api/test1') ctx.body = 'hello' }) router.get('/api/testerror', async(ctx, next) => { throw new Error('I am error.') }) // 设置路由中间件 app.use(router.routes())
-
设置服务器启动端口
// 注意, 在nodejs 大多数操作都是异步的, 因此以下代码有可能服务器没有启动就已经输出这句话了 app.listen(3000) console.log('server listening at port 3000'); // 保险的做法, 我认为应该是 app.listen(3000, ()=> { console.log('server listening at port 3000') })
在 koa中 ctx (上下文) , 在初次学习中, 可以简单看成 express框架中的req和res的集合
三. 中间件(middleware)
3.1 什么是中间件
**通俗来讲, ** 中间件就是匹配路由之前或者匹配路由之后所完成的一系列操作就是中间件
3.2 中间件功能
- 执行任何代码
- 修改请求和响应对象
- 终结请求-响应循环
- 调用堆栈中的下一个中间件(next)
3.3 中间件分类
- 应用级中间件 (koa)
- 路由级中间件 (koa-router)
- 错误处理中间件 ( 可以在应用级中间件中 使用ctx.status===404 , 进行全局错误处理 )
- 第三方中间件: 如用于处理post传值的中间件\静态托管的static
3.4 路由中间件多次匹配
多个中间件可以对同一路由进行多次匹配
router.get("/err", async (ctx, next) => {
await next();
console.log("this is error page !");
});
router.get("/err", async (ctx, next) => {
ctx.body = "this is error page";
});
上述代码在访问 http://localhost:3000/err, 会在后端控制台输出 "this is error page", 会在前端页面中输出 "this is error page"
3.5 中间件优先级
与express框架有点不同, 在express中, 中间件的优先级往往取决于写法的先后, 写在前面的往往比写在后面的优先匹配 ( 代码从上往下读 )
但是在koa中, 中间件的先后是符合一定规则的,执行流程类似于洋葱图
四. EJS模板引擎
4.1 ejs 模板引擎的安装
- 安装 koa-views和ejs
npm install --save koa-views / cnpm install --save koa-views
-
引入koa-views配置中间件
const views = require("koa-views"); // 注意, 这只是渲染的其中一种方法, 而且这样子设置之后, render函数只能识别 html的后缀名文件 app.use(views("views", {map: {html: "ejs"}})); // 还有一种设置方法, 推荐这种写法, 这种写法可以识别ejs的文件后缀名, 并且名字明确了模板文件的根路径 // views字符串是指模板文件夹的根路径, 当前设置是指, 将当前项目根目录中的Views文件夹(没有则需要手动创建) 设置成模板文件夹根目录 app.use(views("views", { extension: "ejs" })) // 或者这种写法 app.use(views(__dirname, {extension: "ejs"}))
4.2 EJS模板的使用
4.2.1 使用变量
在koa的路由中设置需要传入EJS的变量
router.get("/me", async (ctx, next) => {
// 使用模板引擎渲染页面
// 注意, 默认是以html结尾的后缀名
// 传变量
let title = "wangermazi";
// 传数组
let arr = [1, 2, 3, 4];
// 这个await一定要加上, 这个类似于 express的 return res.render
await ctx.render("index", {
title,
arr
});
});
在EJS中调用
<h5><%= title%></h5>
4.2.2 数组的循环渲染
在ejs中传入需要循环的数组
router.get("/me", async (ctx, next) => {
// 传数组
let arr = [1, 2, 3, 4];
// 这个await一定要加上, 这个类似于 express的 return res.render
await ctx.render("index", {
arr
});
});
在ejs 在EJS中使用
<%# 第一种循环方式 %>
<ul>
<% for (var i = 0; i < arr.length; i++) { %>
<li>
<%= arr[i]%>
</li>
<% }%>
</ul>
<%# 第二种循环方式%>
<% arr.forEach(function (item, index) { %>
<li>
<%= item%>
</li>
<% }) %>
在ejs中 当前版本好像只支持 for 循环
4.2.3 条件渲染
这个可以参照官方文档进行操作, 没有什么需要特别提出的
4.2.4 模板引用
在VIews文件夹定义一个layout文件夹, 在layout中有一个header.ejs
<h1 class="header">
这是首页
</h1>
在index.ejs中引用 header.ejs内容
<% include layout/header.ejs %>
注意!!!
可能每个版本的ejs 引入都有可能有或多或少的问题, 当发现上述方法引入出现语法报错时, 可以尝试的使用以下语法引入
<% include("layout/header.ejs") %>
4.2.5 模板引用传值
在引用的过程中, 传入即可, 在调用header.ejs模板时传入一个 name = wang的值
在index.ejs中
<% include("layout/header.ejs", {name: "wang"}) %>
在header.ejs中
<h3 class="name">
我的名字是 <%= name %>
</h3>
4.3 session作用域
如果我们想在每一个router中都使用同一组数据, 则可以将该数据置入session中, 然后在ejs中直接调用
而且这种场景一般都需要使用中间件来配置相关的信息
在中间件配置信息
// 设置全局session中间件
app.use(async (ctx, next) => {
ctx.state.userInfo = "wang";
await next() // 表示继续向下匹配路由
});
router.get("/session", async(ctx, next)=>{
await ctx.render("session") // 渲染一个名为session.ejs的模板
})
在session.ejs模板中直接使用
<h2 class="name">
<%= userInfo %>
</h2>
五. 获取post提交数据
5.1 原生NodeJS获取post提交数据
首先先定义获取post传值的方法
在项目跟目录下定义一个 common.js , 内容如下
exports.getPostData = function (ctx) {
return new Promise((resolve, reject)=>{
try {
let str = "";
// 监听数据传输事件, 将每个数据片段进行拼接, 最终返回出去
ctx.req.on("data", function (chunk) {
str += chunk
});
ctx.req.on("end", function (chunk) {
resolve(str)
})
} catch (e) {
reject(e)
}
})
}
在 router中建立监听form表单的提交
const common = require('./common');
router.post("/doAdd", async (ctx, next)=>{
// 获取表单提交的数据
const data = await common.getPostData(ctx);
console.log(data); // 输出 username=15877031526&password=1111, 是类似于键值对
ctx.body = data
});
5.2 koa-bodyparse获取提交post提交数据
5.2.1 安装 koa-bodyparse
npm install koa-bodyparse --save
5.2.2 引入配置中间件
const Koa = require("koa");
const app = new Koa();
const bodyParse = require("koa-bodyparser");
app.use(bodyParse({
// post参数解析错误时触发的函数
onerror: function (err, ctx) {
ctx.throw('body parse error', 422);
}
}));
5.2.3 使用中间件
app.use(async (ctx, next) => {
// the parsed body will store in ctx.request.body
// if nothing was parsed, body will be an empty object {}
// 根据官方文档的表述, 表达提交的数据存放在 ctx.request.body中
// 如果form提交的时候没有传入任何数据, 此时 ctx.request.body 即为一个空对象
ctx.body = ctx.request.body;
await next()
});
六. 静态资源托管
在模型中, 如果想引入静态资源, 则需要引入静态资源托管的中间件, 对文件路径进行重新适配, 在koa中使用的是 koa-static中间件进行托管, 详情可直接了解官方文档: https://www.npmjs.com/package/koa-static
6.1 koa-static 的安装
npm install koa-static --save
6.2 配置中间
const Koa = require('koa');
const app = new Koa();
const static = require("koa-static");
// 注意!!!, 在koa中中间件可以配置多个, 因此静态资源可以配置多个, 当找不到第一个静态资源配置时, 会自动去找下一个
// 使用绝对路径来配置静态资源根目录
app.use(static(__dirname+"/static"); // 表明配置了一个在根目录下的static目录作为根目录
// app.use(require('koa-static')(root, opts));
/*
root root directory string. nothing above this root directory can be served
// 根据官方文档可以得知, 第二个参数options是一个配置对象
opts options object.
*/
七. koa-cookies
-
cookies是存储在用户浏览器中的一个变量, 可以让我们用同一个浏览器访问同一个域名的时候共享数据
(注意是同一个域名的时候)
HTTP是无状态协议, 当你浏览了一个页面, 然后转到同一个网站的其他页面时候, 服务器无法认识到是同一个浏览器在访问同一个网站, 每次访问都与上一次访问没有任何关系
关于koa-cookies的更多使用功能可以详见官方文档, 这里只是讲解简单用法
官方文档: https://www.npmjs.com/package/koa-cookies
7.1 koa-cookies的安装
npm i koa-cookies --save
7.2 koa-cookies使用
// const { clearCookie, setCookie } = require('koa-cookies');
// 这里和官方文档有些出入, 在koa中并不需要单独安装cookies
const app = require("koa")();
const router = require("koa-router");
router.get("/cookies", async(ctx, next)=>{
// 设置cookies值
// await setCookie('bar', 'baz')(ctx)
const options = {}
ctx.cookies.set("userInfo", "wang", options)
ctx.body = "已经设置cookies值";
})
router.get("/clearcookies", async(ctx, next)=>{
const userInfo = ctx.cookies.get("userInfo")
console.log(userInfo);
ctx.body = userInfo;
})
app.use(router.routers())
7.3 配置中间件
app.use(setCookie('foo', 'bar', config))
app.use(clearCookie('baz', config))
7.4 koa-cookies 的配置信息
setCookieConfig = {
domain: ctx.host,
maxAge: one week,
expires: one week from now
}
clearCookieConfig = {
domain: ctx.host,
maxAge: 1 second,
expires: 1970-01-01T00:00:00.001Z
}
7.5 在koa中配置中文cookies
**思路: **可以将中文转成 buffer 64位编码, 进行保存, 在取值的时候, 再将 64位编码转成字符串
-
第一步, 将中文转换成64位编码
// 注意!!! 在最新版本中, 直接调用 Buffer可能会存在问题, 官方建议使用别的方法 // Buffer() is deprecated due to security and usability issues. const name = new Buffer("张三").toString("base64");// 输出 5byg5LiJ
-
第二步, 将64位编码转成字符串
const name = new Buffer("5byg5LiJ", "base64").toSting(); // 输出张三
八. koa与MongoDB
8.1 MongoDB封装
pass
详情了解官方文档: https://docs.mongodb.com/drivers/node/
番外
一. commonJs
1.1 什么是commonJS
只用一句话就可以简单表述出来
CommonJs 就是模块化的标准, NodeJs就是CommonJs的实现
Node应用是由模块组成的, 采用CommonJs模块规范
1.2 CommonJs模块分类
模块又分为两大类:
- 系统模块: 即 NodeJs的内置模块, 无需安装即可使用, 如: path,os, fs等模块
- 文件模块: 由用户编写的第三方文件模块, 使用时需要安装, 如:axios, cheerio, puppeteer等模块
1.3 CommonJs模块规范
-
我们可以把公共的功能抽离成一个单独的js文件, 即一个模块, 在默认的情况下这个模块里面的属性以及方法, 外界都无法访问, 要想暴露出去, 就需要通过export 或者 module.exports暴露属性或者方法
// api.js const axios = { get() { console.log("调用get方法") }, post() { console.log("调用post方法") } } export.axios = axios
-
在需要使用这些文件的模块中, 通过require方法, 引入这些模块
// index.js const axios = require("api"); axios.get();
注意, 在一个叫node_modules 文件夹里面定义的模块, 引入时不需要写完整的路径, 只需要写相对于node_modules文件夹的路径即可
二. art-template模板引擎
适用于koa的模板引擎或者说适用于nodeJS的模板引擎有很多, 比如 jade, ejs, nunjucks(thinkjs默认引擎), art-template
art-template是一个简约, 超快的模板引擎, 它采用作用域声明的技术来优化模板渲染速度, 从而获得接近JS极限的运行速度, 总得来说是挺有意思的一款模板引擎, 之所以不放在正篇说, 是个人认为模板引擎只需掌握一种即可, 了解其渲染原理, 不必过多掌握多种模板引擎
**值得注意的是: **art-template支持ejs 的语法, 也可以用自己类似于angular数据绑定的写法
2. 1 art-template安装
# koa 的模板引擎需要依赖 koa-views的支持
npm install koa-views --save
npm install art-template --save
npm install koa-art-template --save
2.2 art-template 配置
const path = require("path");
const render = require('koa-art-template');
const router = require('koa-router');
render(app, {
root: path.join(__dirname, 'views'), // 表示视图模板引擎的位置
extname: '.html', // 模板文件后缀名
debug: process.env.NODE_ENV !== 'production' // 是否开启调试模式
});
router.get("/art", async(ctx, next)=>{
let name = "wang";
await ctx.render("user", {
name,
}) // 使用的是 .html后缀名
})
2.2 art-template使用
具体详见官方文档, 语法跟ejs 差不多, 同时也支持类似于 angular的语法
2.2.1 报错提示
在开启debug模式之后, art-template语法错误之后会提示报错位置
<!--
// 内容出错在debug模式中会出现错误提示内容
TemplateError: D:\前端\learnkoa\views\user.html:10:15
8| 测试art-template
9| 使用原始语法
>> 10| <h3>我的名字是 : <%= userObj1.name1 %> </h3>
11| 使用标准语法
12| <h3>我的名字是 : {{userObj1.name}} </h3>
-->
三. 对象冒充结合原型链实现继承(ES5)
传统继承模式
function Person (name, age){
this.name=name;
this.run = function(){
alert( this.name+'在运动' )
}
}
Person.prototype.work = function(){
alert( this.name+'在工作’ )
}
// web类 继承person类 原型链+对象冒充的组合继承模式
function Web(name, age){
Person.call( this, name ) // 对象冒充实现继承 (此种方法存在缺陷, 冒充对象之后不能实现原来对象上原型链的方法)
// 使用原型链继承则无法通过子类向父类传参
}
var w = new Web("王", 20)
w.run() // 会执行 对象继承可以继承构造函数里面的方法
w.work() // 不会执行 对象继承可以继承构造函数里面的方法 但是无法继承原型链上面的方法跟属性
// web.protype = new Person()// 原型链继承方法 缺点 实例化子类的时候无法给父类传参
冒充对象结合原型链实现继承
function Person(name,age,hobby){
this.name=name;
this.age = age;
this.hobby = hobby;
this.run = function () {
console.log(this.name+"运动");
}
}
//方法需共享
// Person.prototype.run = function(){
// console.log(this.name+"运动");
// };
function Programmer(name,age,hobby,skill){
// 都是先继承再添加新属性
Person.call(this,name);//对象冒充继承属性
this.skill =skill;//添加新属性
}
Programmer.prototype = new Person();//原型链继承方法
// 注意这步非常重要, 要先在原型链中实现父类, 才能添加方法, 再添加方法才不会覆盖原来定义的方法
Programmer.prototype.work = function(){//添加新方法
console.log(this.name+"在工作");
};
var programer = new Programmer('fox',28,['a','b'],'coding');
programer.run();
programer.work();
四. 单例模式
4.1 ES5 的单例实现
function A(name){
// 如果已存在对应的实例
if(typeof A.instance === 'object'){
return A.instance
}
//否则正常创建实例
this.name = name;
// 缓存
A.instance = this;
return this
}
var a1 = new A();
var a2= new A();
// 从一般情况来说, 每个实例化的对象, 都是不同内存地址,
// 但是单例设计模式, 却是指向同一个内存地址
console.log(a1 === a2); //true
4.2 ES6 的单例实现
// 通过ES6实现单例, 更较为常用
class Person {
constructor() {
console.log("触发构造函数");
}
find() {
console.log("触发find方法")
}
// 设计单例
static getInstance() {
if (!Person.instance) {
Person.instance = new Person()
}
// 不做缓存, 直接返回
return Person.instance
}
}
const p1 = Person.getInstance();
const p2 = Person.getInstance(); // 此时两次实例化只会触发一次构造函数
// p1.find();
// p2.find(); // 此时两个find均能正常触发
console.log(p1 === p2); // 返回的是 true