一个前端的自我救赎--简易版的koa实现

项目这段时间在使用koa,对立面的ctx以及异步中间件的执行很感兴趣,所以就索性实现了一下

  1. 需要对了解js里面this的指向
  2. 需要对promsie有一个清晰的认知

对koa ctx上面的属性做一个系统的人知

ctx.req.request.url    // ctx.req = req
ctx.request.req.url   // ctx.request.req = req;

ctx.request.url      //ctx.request koa自己封装的
ctx.url              // ctx代理了ctx.request

复制代码

对appliction文件的封装

const http = require("http");
const context = require("./context");
const request = require("./request");
const response = require("./response");

class Mkoa {
  constructor() {
    this.middlewares = [];
    this.context = context;
    this.request = request;
    this.response = response;
  }
  use(cb) {
    this.middlewares.push(cb); // 保存所有的中间件
  }
  createContext(req, res) {
    let ctx = Object.create(this.context); // ctx可以拿到全局的context上的属性,并且修改ctx的时候不会影响到context

    // 上下文上挂载请求 ctx.request 指向外部的request文件 方便扩展
    ctx.request = Object.create(this.request);
    ctx.req = ctx.request.req = req;

    // ctx上挂载响应
    ctx.response = Object.create(this.response);
    ctx.res = ctx.response.res = res;

    return ctx;
  }
  
  // 返回一个promise 保证是在中间件执行完毕
  componse(ctx, middlewares) {
    function dispatch(index) {
      if (index === middlewares.length) {
        return Promise.resolve();
      }
      const middleware = middlewares[index];
      return Promise.resolve(
        middleware(ctx, () => {
          return dispatch(index + 1);
        })
      );
    }
    return dispatch(0);
  }
  
  hanleRequest(req, res) {
    let ctx = this.createContext(req, res); // 基于req res 创造ctx
    let composeMiddleware = this.componse(ctx, this.middlewares); // componse 组合执行内部的中间件,返回一个promise 保证是所有的中间件都是按照顺序执行的
    
    // 所有的中间件都执行完毕过后,判断body上是否有值,再根据情况进行返回
    // 判断body的类型 可以设置不同的请求头 文件 字符串 流
    composeMiddleware.then(() => {
      let body = ctx.body;
      if (typeof body === "undefined") {
        res.end(`not found`);
      } else if (typeof body === "string") {
        res.end(body);
      }
    });
  }
  
  listen(...arg) {
    let app = http.createServer(this.hanleRequest.bind(this));
    app.listen(...arg);
  }
}

module.exports = Mkoa;



复制代码

对request文件的封装

let url = require('url');
const request = {
  // 这个方法的调用是采用的 ctx.request.url => ctx.request.url.call(ctx.request)
  // this指的就是 ctx.request
  // ctx.req = ctx.request.req = req
  get url(){
    return this.req.url
  },

  get path(){
    return url.parse(this.req.url).pathname
  }
}
module.exports = request


/*
函数执行的时候 fn(a,b) 会转换为标准的执行方式 fn.call(context,a,b)
obj.child.method(p1, p2) 等价于 obj.child.method.call(obj.child, p1, p2);
 var obj = {
  foo: function(){
    console.log(this)
  }
}

var bar = obj.foo
obj.foo() // 转换为 obj.foo.call(obj),this 就是 obj
bar() 
转换为 bar.call()
由于没有传 context
所以 this 就是 undefined
最后浏览器给你一个默认的 this —— window 对象    


function fn (){ console.log(this) }
var arr = [fn, fn2]
arr[0]() // 这里面的 this 又是什么呢? 
 */
复制代码

对response文件的封装

const response = {
  set body(value){
    this.res.statusCode = 200;
    this._body = value // 保存value值
  },
  get body(){
    this.res.statusCode = 404;
    return this._body
  }
}
module.exports = response
复制代码

context封装

// 直接访问ctx上的属性
const context = {
  
}

// koa 源码里面的 delegate
function defineGetter(property,name){
  // context[name] 返回的是 context[property][name]
  context.__defineGetter__(name,function(){
    return this[property][name]
  })
}
// ctx.body => ctx.body.call(ctx)
function defineSetter(property,name){
  // context[name] 返回的是 context[property][name]
  context.__defineSetter__(name,function(value){
    return this[property][name] = value
  })
}
// koa 内部有一个delegate的方法 我们只做简易实现
defineGetter('request','url'); //完整的访问链 ctx.url => ctx.request.url => ctx.request.req.url
defineGetter('request','path');

defineGetter('response','body')
defineSetter('response','body')
module.exports = context
复制代码

编写一个运行实例

let koa = require("./ckoa/application");

let app = new koa();
function logger() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("已异步顺序的问题");
      resolve();
    }, 2000);
  });
}
/**
 * 如果一个中间件里面存在异步的情况 我们需要使用await next() 保证下一个中间件执行完毕,才执行当前中间件后续的代码
 * koa 中间件在没有异步的情况下是和express是一样的执行顺序 没有什么区别
 */
app.use(async (ctx, next) => {
  console.log(1);
  await next();
  console.log(2);
});
app.use(async (ctx, next) => {
  console.log(3);
  await logger()
  next();
  console.log(4);
});
app.use((ctx, next) => {
  console.log(5);
  next();
  console.log(6);
});

app.listen(3001);

复制代码

文件分析

/*
context request response 之间的关系

ctx.request = Object.create(我们自定义扩展的request)
ctx.request 就可以通过原型链访问到request上定义的属性,
可以看koa源码里面的request文件,
基本上都是 this.req.**** this.req.url this.req.header
都是表示的原生的req

ctx.request.url    首先ctx.request 上没有url这个属性,
需要通过原型链去进行查找 原型上的this指向的就是实例属性。
或者ctx.request.url() =>  ctx.request.url.call(ctx.request) => this指向的就是 ctx.request

ctx.request.req.url  其实就是 req.url 因为 ctx.req = ctx.request.req = req;

ctx.url 首先ctx上并没有 url 一样是原型链进行查找,
会找到context 文件里面去,因为ctx的原型指向的是 context这个文件,
但是context这个文件里面又进一步做了一次代理。
ctx.url通过delegate过后访问的其实就是 ctx.request.url 就回到了最初的时候 ctx.request.req.url 

ctx.req = ctx.request.req = req 所以会有 ctx.req.*** ctx.request.req.** 就是访问的原生的req上的属性. 
ctx.req.path ctx.request.req.path 是访问不了的

但是 ctx.request.path ctx.path 访问的path其实是用户自己封装的path,
在request文件里面,
ctx.request.path返回的是 url.parse(this.req.url).pathname,
里面的this就是ctx.request。
同理,ctx.path 访问的就是context里面的getter 其实还是 ctx.request.path


ctx.response = Object.create(response)
ctx.res =  ctx.response.res = res

ctx.body => 访问的是context里面代理过后的,
其实设置的其实是 ctx.response.body  => 这样访问的就是response里面的body,
这个时候里面的this 其实指的就是 ctx.response.body,
并且里面我们设置了 this.res.statusCode = 200; 
其实设置的 ctx.response.res.statusCode = 200
*/
复制代码

componse串行中间件的时候遇到的问题

没有理解到为什么一定要return dispatch(index+1) 涉及到promise的嵌套问题。先去实现一个promise过后再来补充

Promise.resolve(middleware(ctx,()=>{
    return dispatch(index+1)
}))


复制代码

转载于:https://juejin.im/post/5d084eccf265da1baf7ced66

猜你喜欢

转载自blog.csdn.net/weixin_33991727/article/details/93167092