干货 | koa 包教包会系列1 —— 白话 koa

Posted 创宇前端

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了干货 | koa 包教包会系列1 —— 白话 koa相关的知识,希望对你有一定的参考价值。


学习 koa 的必要性

koa 是由 express 原班人马打造的,小巧的,健壮的 web 开发框架,也正因为它这些的优点,eggjs 等很多 web 框架的核心也是由 koa 驱动的,熟悉 koa 代码,不仅对于使用 koa 进行开发有很大帮助,也有助于深入理解像 eggjs 等这样的,功能更加强大的框架。

  • 知识复习

  • 代码结构

知识复习

prototype 可以说是整个 javascript 的核心,是深入理解 javascript 关键知识点之一。
delegate 是一种编程方式,可以简化我们的操作。

1.1 首先声明“父类”(或子类的原型)

 
   
   
 
  1. const Car = {

  2.  get name() {

  3.    return this.engine.name;

  4.  },

  5.  start: function() {

  6.    return `${this.engine.name} start`;

  7.  },

  8. };

1.2 使用用父类创建子类(父类在子类的原型链上)

 
   
   
 
  1. const BMW = Object.create(Car);

1.3 动态的给子类添加属性

 
   
   
 
  1. BMW.engine = {

  2.  name: 'v8',

  3. };

1.5 调用子类的方法

 
   
   
 
  1. BMW.name; // 'v8'

  2. BMW.start(); // 'v8 start'

1.6 定义代理对象

 
   
   
 
  1. const BMWPHONE = {};

1.7 定义代理方法

 
   
   
 
  1. function Delegate(source, target) {

  2.  this.source = source;

  3.  this.target = target;

  4. }

  5. Delegate.prototype.method = function(name) {

  6.  const target = this.target;

  7.  const source = this.source;

  8.  source[name] = function() {

  9.    return this[target][name].apply(this[target], arguments);

  10.  };

  11. };

1.8 在代理对象上执行目标方法

 
   
   
 
  1. const delegate = new Delegate(BMWPHONE, 'BMW');

  2. delegate.method('start');

  3. BMWPHONE.BMW = BMW;

  4. BMWPHONE.start();

定义了 Car 父类,BMW 继承了 Car 方法,并给 BMW 安装了牛逼的引擎,现在可以通过 BMW 手动启动了;
现在 BMW 开发一款手机,用手机操作代理手动启动。

之所以举上面的例子(这里你会体会到 js 和静态语言的巨大差异),是因为 koa 中 4 个文件中,有 3 个都是与原型和代理相关。

代码结构

koa 非常小巧,总共就 4 个文件,每个文件的功能也十分单一,文件名也清楚的反应了文件功能。

koa 的文件结构

 
   
   
 
  1. ├── application.js

  2. ├── context.js

  3. ├── request.js

  4. └── response.js

  • request.js

    主要针对 http 的 request 对象提供了改对象的大量的 get 方法,文件主要是用来获取 request 对象属性,参考 1.1。


  • response.js

    主要针对 http 的 response 对象提供了该对象的大量 set 方法;该文件主要是用来设置 response 对象属性,参考 1.1。


  • context.js

    koa 引入了上下文对象的概念,即 ctx,这里所谓的上下文对象实际上是 request 和 response 两个对象的并集,request 和 response 分别通过代理的形式,参考 1.8,将自己的方法委托给 ctx。那样我们就可以用 ctx 同时操作两个对象,来简化操作。


  • application.js

    该文件是整个 koa 的核心,简单来说主要有两大功能: 挂载真实请求到 ctx 下,封装中间件的执行顺序


 
   
   
 
  1. createContext(req, res) {

  2.  const context = Object.create(this.context);

  3.  const request = context.request = Object.create(this.request);

  4.  const response = context.response = Object.create(this.response);

  5.  context.app = request.app = response.app = this;

  6.  context.req = request.req = response.req = req;

  7.  context.res = request.res = response.res = res;

  8.  request.ctx = response.ctx = context;

  9.  request.response = response;

  10.  response.request = request;

  11. }

这里 createContext 就是上小节 1.3 中的操作--继承原型链上的方法,给原型链上的方法准备数据。
这里做了很多冗余的挂载(冗余是为了兼容 express 部分写法),如果你只需要用 ctx,那么其中部分挂载可以省略。

 
   
   
 
  1. module.exports = compose;

  2. function compose(middleware) {

  3.  return function(context, next) {

  4.    let index = -1;

  5.    return dispatch(0);

  6.    function dispatch(i) {

  7.      if (i <= index)

  8.        return Promise.reject(new Error('next() called multiple times'));

  9.      index = i;

  10.      let fn = middleware[i];

  11.      if (i === middleware.length) fn = next;

  12.      if (!fn) return Promise.resolve();

  13.      try {

  14.        return Promise.resolve(

  15.          fn(context, function next() {

  16.            return dispatch(i + 1);

  17.          })

  18.        );

  19.      } catch (err) {

  20.        return Promise.reject(err);

  21.      }

  22.    }

  23.  };

  24. }

koa 中间件的特点是逆序执行,或者说是洋葱模型。以上 compose 方法就是实现洋葱模型的代码,上面方法简单来说就是: 将下个中间件的方法体,替换上个中间件的 next 参数。 compose 方法应该算是 koa 中最难理解的部分了。

 
   
   
 
  1. app.use(async function1(ctx, next) {

  2.  console.log(1.1);

  3.  next();

  4.  console.log(1.2);

  5. })

  6. app.use(async function2(ctx, next) {

  7.  console.log(2.1);

  8.  next();

  9.  console.log(2.2);

  10. })

经过 compose 函数处理后,实际执行代码可以看做:

 
   
   
 
  1. (async function1(ctx) {

  2.  console.log(1.1);

  3.    // next 部分

  4.    (async function2(ctx) {

  5.        console.log(2.1);

  6.          ...

  7.        console.log(2.2)

  8.      }

  9.    )(ctx)

  10.  conosle.log(1.2);

  11. })(ctx)

那么可以看出洋葱模型的特点: 先注册的中间件,位于模型的最外侧。
实际上 koa 主要做了两件事情:

  1. 封装 ctx

  2. 组装中间件,实现逆序执行的洋葱模型

总结

与 hapi,eggjs 比起来,koa 真的十分小巧,以至于不能称作一种框架,可以看做一种库,但这并不妨碍 koa 生态的发展。

express 当初也是大而全的框架,慢慢的把各种功能已中间件的形式抽离出来,koa 可以看做这种思想的一种实现。大而全的框架主要存在起初的学习成本高,功能冗余等问题,使用 koa 对于初次使用 nodejs 开发 web 的人员非常友好,但也存在一定问题,过于灵活编程,导致编程方式千差万别,没有一个统一的标准,不同工程代码之间学习成本高。

对于初学者来说,建议从 koa 入手,使用不同的中间件来实现不同的功能,对于了解 web 开发有很大帮助。当有一定积累和经验后,可以自己约定 koa 编程规则,当自己编程规则无法满足需求时,就可以入手真正的框架了。



长按指纹

一键关注


以上是关于干货 | koa 包教包会系列1 —— 白话 koa的主要内容,如果未能解决你的问题,请参考以下文章

Koa原理和封装

Java8函数式编程 包教包会系列

Java8函数式编程-包教包会系列

Koa异常处理说明

Koa 系列 — Koa 中间件机制解析

Koa 系列 — 如何编写属于自己的 Koa 中间件