Node.js学习笔记【三】

Koa简介

一句话简介

基于Node.js的下一代Web框架

  • 基于Node.js:node.js模块
  • 下一代:蚕食第一代Web框架Express的市场
  • Web框架:不是命令行工具、不是算法

官网简介

image-20210804202605346

  • 由Express幕后的原班人马打造
  • Web应用和API开发领域
  • 更小、更富有表现力、更健壮
  • 利用async函数,丢弃回调函数(koa2利用ES7的async/await特性,极大的解决了我们在做nodejs开发的时候异步给我们带来的烦恼。 )
  • 增强错误处理:try catch
  • 没有捆绑任何中间件:开发者按照自己的意愿选择自己需要的中间件
  • 快速而愉快地编写程序

相关知识

在学习koa2前还要了解ES6的相关语法。

作用域

ES5 中作用域有:全局作用域、函数作用域。没有块作用域的概念。

ES6 中新增了块级作用域。块作用域由 { } 包括,if语句和 for语句里面的{ }也属于块作用域。

let、const和var

varlet---定义变量(值可以改变)

const ---定义常量(必须给初始值,且值不能修改)

letconst有块级作用域,var没有

//实例1
    for(let i=1;i<5;i++){
      // i只能在这里使用
    }
    console.log(i);//报错
//实例2
    //使用 let 依次打印1、2、3、4、5。因为每一次循环都形成一个块(局部作用域),不同的i
    for(let i=1;i<=5;i++){
      setTimeout(()=>{
        console.log(i);
      },1000*i)
    }
//实例3
// 使用 var 只会打印5次6。因为内存中就只有一个i,for很快执行完了,定时器属于异步任务后执行,1秒钟之后才执行1个定时器。
    for(var i=1;i<=5;i++){
      setTimeout(()=>{
        console.log(i);
      },1000*i)
    }

除此之外,还需要注意:

  • let/const定义的变量不会出现变量提升,而var定义的变量会提升
  • 相同作用域中,letconst不能出现重复声明。而var就可以重复声明

箭头函数

语法

()=>{}

- ()=>之间不能换行

=>是由一个=>组合而成的,中间不能有空格

=>和{}之间能换行


写法特点

形参如果只有一个,可以省略小括号

函数体只有一行代码,可以省略大括号,并且自带 return 效果

    let fn = function(x){
      return x*x;
    }
    let fn = (x)=>{
      return x*x;
    }
    let fn = x=>x*x;
    console.log(fn(5));//25

其他特点

1.函数内没有argument对象,可以使用剩余参数获取全部的实参(ES6有个剩余参数,在普通函数也能用)

    let fn = (...c)=>{
      // console.log(arguments);//报错
      console.log(c);// [3, 2, 1, 9, 7]
    }
    fn(3,2,1,9,7);

2.箭头函数内部没有自己的this;使用this需要向查找普通变量那样去查找

箭头函数this是在定义的时候绑定的,内部的this就算外层代码的this。

setTimeout的外层是fn2,fn2的this就是obj

    let obj = {
      fn1:function(){
        setTimeout(function(){
          console.log(this);//window
        },1000)
      },
      fn2:function(){
        // 这里的this指向谁? ===> obj对象
        setTimeout(()=>{
          console.log(this);//obj对象
        },1000)
      }
    }
    obj.fn1();//window
    obj.fn2();//obj

以前的this指向:

普通函数中的this ==> window

定时器中的this ===> window

自调用函数中的this ===> window

obj.fn()里面的this ===> obj对象

事件处理函数中this ===> 事件源

构造函数中的this ===> 实例对象

3.箭头函数不能当做构造函数

//构造函数
    function Person(){
      this.age = 20
    }

Promise

  • promise只有三种状态,未完成,完成(fulfilled)和失败(rejected)。
  • promise的状态可以由未完成转换成完成,或者未完成转换成失败。
  • promise的状态转换只发生一次

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

const promise = new Promise(function(resolve, reject) {
  // ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;

reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数或者返回处理进度信息

promise.then(function(fulfilled){
        //当promise状态变成fulfilled时,调用此函数
    },function(rejected){
        //当promise状态变成rejected时,调用此函数
    },function(progress){
        //当返回进度信息时,调用此函数
    });

使用Promise包装了一个读取文件的异步操作

const fs = require('fs');
function readFileAsync(path){
  return new Promise((resolve,reject)=>{
    fs.readFile(path,(err,data)=>{
      if(err) reject(err)
      else resolve(data)
    })
  })
}

readFileAsync('./package.json').then(data=>{
data=JSON.parse(data)
console.log(data.name);
}).catch(err=>{
console.log(err);
})

参考文章:ES6 Promise用法讲解

处理异步方式

处理异步场景有以下几种方式:(例子为读取文件的异步操作)

  • 通过回调函数实现异步操作
const fs = require('fs')
function readFile(cb){
  fs.readFile('../package.json',(err,data)=>{
    if(err) return cb(err)
    cb(null,data)
  })
}

readFile((err,data)=>{
if(!err){
data = JSON.parse(data)
console.log(data.name);
}else{
console.log("读取文件失败");
}
})

  • 使用Promise
const fs = require('fs')
function readFileAsync(path){
  return new Promise((resolve,reject)=>{
    fs.readFile(path,(err,data)=>{
      if(err) reject(err)
      else resolve(data)
    })
  })
}

readFileAsync('./package.json').then(data=>{
data=JSON.parse(data)
console.log(data.name);
}).catch(err=>{
console.log(err);
})

  • 利用co库+生成器函数(generator function)
const fs = require('fs')
const co = require('co')
const util= require('util')

co(function *(){
let data = yield util.promisify(fs.readFile)('../package.json')
data = JSON.parse(data)
console.log(data.name);
})

  • Async
const fs = require('fs')
const util= require('util')
const readAsync = util.promisify(fs.readFile)
async function init(){
  let data = await readAsync('../package.json')
  data = JSON.parse(data)
  console.log(data.name);
}
init()

安装koa2

使用npm install koa命令安装koa2

然后就可以使用koa2啦😊

//引入Koa
const Koa = require('koa')
const app = new Koa()
//配置中间件(可以先当做路由)
app.use(async(ctx,next)=>{
  ctx.body = 'Hello World'
})
//监听端口
app.listen(8080)

上下文(Context)

Koa 提供一个Context对象,表示一次对话的上下文(包括 HTTP 请求和 HTTP 回复)。通过加工这个对象,就可以控制返回给用户的内容。ctxcontext的缩写中文一般叫成上下文,这个在所有语言里都有的名词,可以理解为上(request)下(response)沟通的环境,所以koa中把它们两都封装进了ctx对象

这个ctx对象下有4个主要的属性,它们分别是

  • ctx.req:原生的req对象
  • ctx.res:原生的res对象
  • ctx.request:koa自己封装的request对象
  • ctx.response:koa自己封装的response对象

其中koa自己封装的和原生的最大的区别在于,koa自己封装的请求和响应对象的内容不仅囊括原生的还有一些其独有的东西。

Koa Context 将 node 的 requestresponse 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。 这些操作在 HTTP 服务器开发中频繁使用,它们被添加到此级别而不是更高级别的框架,这将强制中间件重新实现此通用功能。

koa中的next

配置中间件时通常会将参数定义为(ctx,next),而这个next函数主要负责将控制权交给下一个中间件,如果当前中间件没有终结请求,并且next没有被调用,那么当前中间件的请求将被挂起,等到next()后的中间件执行完再返回继续执行。

总结来说就是:从第一个中间件开始执行,遇到next进入下一个中间件,一直执行到最后一个中间件,在逆序,执行上一个中间件next之后的代码,一直到第一个中间件执行结束才发出响应。

实例代码:

const one = (ctx, next) => {
  console.log('>> one');
  next();
  console.log('<< one');
}

const two = (ctx, next) => {
console.log('>> two');
next();
console.log('<< two');
}

const three = (ctx, next) => {
console.log('>> three');
next();
console.log('<< three');
}

app.use(one);
app.use(two);
app.use(three);

输出结果:

>> one
>> two
>> three
<< three
<< two
<< one

中间件

Koa 的最大特色,就是中间件(middleware)。在中间件系统的实现上,Koa中间件通过async/await来在不同中间件之间交换控制权,工作机制和结构非常相似。Koa把很多async函数组成一个处理链,每个async函数都可以做一些自己的事情,然后用await next()来调用下一个async函数。我们把每个async函数称为middleware,这些middleware可以组合起来,完成很多有用的功能。

Koa中使用app.use()用来加载中间件,基本上Koa 所有的功能都是通过中间件实现的。

在Koa中,中间件是指连贯整个 Koa 应用程序,并共享资源的独立插件”,注意两个词,“连贯”与“共享资源”,与上面的代码一一对应,“连贯”对应“next”,“共享资源对应context”。

每个中间件默认接受两个参数,第一个参数是 Context对象,第二个参数是next函数。只要调用next函数,就可以把执行权转交给下一个中间件。每一个中间件就相当于洋葱的一层,请求从最外层进去,然后从最里层出来,每个中间件都会执行两次。

Koa 中通过 next贯穿整个应用程序:

const Koa = require('koa');
const app = new Koa();

const mid1 = async(ctx,next)=>{
ctx.type = 'text/html;charset=utf-8;'
await next()
ctx.body = ctx.body + ' !'
};

const mid2 = async(ctx,next)=>{
ctx.body = 'Hello'
await next()
};

const mid3 = async(ctx,next)=>{
ctx.body = ctx.body+' World'
};

app.use(mid1);
app.use(mid2);
app.use(mid3);

app.listen(8080);

image-20210802215202405

Koa中的koa-compose

在了解koa-compose前先了解下纯函数尾递归概念

纯函数

一个函数的返回结果只依赖其参数,并且执行过程中没有副作用。在状态数据多变的环境中,可以带来强的可控性。

function pure(x){
	return x+1;
}
console.log(pure(1));
console.log(pure(1));
console.log(pure(1));
console.log(pure(1));

尾递归

若一个函数在尾位置调用本身(或是一个尾调用本身的其他函数等),则称这种情况为尾递归,是递归的一种特殊情形。而形式上只要是最后一个return语句返回的是一个完整函数,它就是尾递归。

尾递归的特点是最后返回的值是一个递归的函数调用,这样执行完就会在调用栈中销毁,不会占据调用栈。

基础递归

function tail(i){
  if(i>3) return
  console.log('修改前 ',i);
  tail(i+1)
  console.log('修改后 ',i);
}

tail(0);

修改前  0
修改前  1
修改前  2
修改前  3
修改后  3
修改后  2

缺点:在这个过程,函数记录了太多的堆栈深度和状态,比较浪费性能。

使用尾递归改进:

function tail(i){
  if(i>3) return
  console.log('修改前 ',i);
  return tail(i+1)
}

tail(0);

修改前  0
修改前  1
修改前  2
修改前  3

很容易看出, 普通的线性递归尾递归更加消耗资源,,在实现上说, 每次重复的过程调用都使得调用链条不断加长。系统不得不使用栈进行数据保存和恢复,而尾递归就不存在这样的问题。

koa-compose

koa 的 koa-compose利用递归实现了 Promise 的链式执行,不管中间件中是同步还是异步都通过 Promise 转成异步链式执行

compose核心代码

function compose (middleware) {
  return function (context, next) {
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

可以看到

  • compose其实就是一个尾递归函数 ; 递归相对低效且消耗系统资源
  • 返回的是一个Promise.resolve包装之后的调用,而不是同步的调用,所以这是一个异步递归,异步递归比同步递归的好处是可以被打断,如果中间有一些优先级更高的微任务,那么可以先执行别的微任务
  • compose函数复合,把n个middleware复合成一个,参数依然是context和next,这种复合之后依然是一个middleware,还可以继续进行复合。

compose 是一个工具函数,Koa.js 的中间件通过这个工具函数组合后,按app.use()的顺序同步执行,也就是形成了 洋葱圈式的调用。

img

img

koa-session

session同之前介绍的一样,是一种记录客户状态的机制。

使用npm install koa-session安装体验koa-session

const Koa = require('koa');
const session = require('koa-session');
const app = new Koa()
//设置cookie key
app.keys = ['Hello']

app.use(session(app))
app.use(ctx=>{
if (ctx.path === '/favicon.ico') return
let n =ctx.session.views || 0
ctx.session.views = ++n
ctx.body = n+' views'
})
app.listen(8888)

 

 

 

 

posted @ 2021-08-02 23:34  小风车吱呀转  阅读(100)  评论(0编辑  收藏  举报