Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

koa源码解读 #23

Open
SunShinewyf opened this issue Jul 16, 2017 · 0 comments
Open

koa源码解读 #23

SunShinewyf opened this issue Jul 16, 2017 · 0 comments

Comments

@SunShinewyf
Copy link
Owner

SunShinewyf commented Jul 16, 2017

继上次对express进行简单地了解和深入之后,又开始倒腾koa了。对于koa的印象是极好的,简洁而有表现力。和express相比它有几个比较明显的特征:

  • 比较新潮,koa1中使用了generator,拥抱es6语法,使用同步语法来避免callback hell的层层嵌套。在koa2中又拥抱了es7async-await语法,语法表现形式更为简洁。
  • 变得更轻量化,相比于expresskoa抽离了原先内置的中间件,一些比较重要的中间件都使用单独的中间件插件,使得开发者可以根据自己的实际需要使用中间件,更加灵活。就比如给开发者建造了一个简单的地基,之后的装修设计都由开发者自己决定,精简灵活。
    关于更多更详细的两者以及和hapi的比较,读者可以移步这里

在源码方面,koa变得更加轻量化,但是还是很有特点的。目录结构如下:

- lib/
    - application.js
    - context.js
    - request.js
    - response.js

从目录结构来看,只有四个文件,摒弃了express中的路由模块,显得简单而有表现力。四个文件中分别定义了四个对象,分别是app对象,context,request以及response。深入源码查看,你会发现更加简单,每个文件的代码行数也是很少,而且逻辑嵌套并不复杂。

中间件原理

首先定义了一个构造函数,源码如下:

function Application() {
  if (!(this instanceof Application)) return new Application;
  this.env = process.env.NODE_ENV || 'development';
  this.subdomainOffset = 2;
  this.middleware = [];
  this.proxy = false;
  this.context = Object.create(context);  //koa的上下文对象
  this.request = Object.create(request);  //koa.request
  this.response = Object.create(response);  //koa.request
}

在这段里面只是单纯地定义了一个实例化app的一些属性。如上下文,中间件数组等。
然后是注册了一些中间件,中间件的use源码很简单,就是将当前的中间件函数push到该应用实例的middleware数组中,在此不再赘述。
最后定义了开启服务的lisen函数,在这个函数里面没有什么特殊的,只是有一点需要注意:它将自身原型的一个callback作为参数传入:

  var server = http.createServer(this.callback());

这一句很关键,它表示每次在开启koa服务的时候,就会执行传入的callback,而callback都干了些啥,具体看源码:

app.callback = function(){
  if (this.experimental) {
    console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.')
  }
  var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));
  var self = this;

  if (!this.listeners('error').length) this.on('error', this.onerror);

  return function handleRequest(req, res){
    res.statusCode = 404;
    var ctx = self.createContext(req, res);
    onFinished(res, ctx.onerror);
    fn.call(ctx).then(function handleResponse() {
      respond.call(ctx);
    }).catch(ctx.onerror);
  }
};

在这个函数里面,通过experimental参数作为是否使用es7async的标准,当然了,这种折中处理的方式是koa1中的,在koa2中,由于完全摒弃了generator,转而拥抱async-await,所以直接使用的const fn = compose(this.middleware);就简单进行处理了。对于使用es7语法的情况,使用的compose_es7app中的中间件数组进行处理:

function compose(middleware) {
  return function (next) {
    next = next || new Wrap(noop);
    var i = middleware.length;
    while (i--) next = new Wrap(middleware[i], this, next);
    return next
  }
}

也就是将中间件进行遍历,compose函数的作用如下:

compose([f1,f2,...,fn])(args)  =====>  f1(f2(f3(..(fn(args)))));  

也就是将数组里面的函数依次执行,通过一个next中间值不断将执行权进行传递。如果传入的中间件数组不是generator函数,那么应该是依次执行,但是generator有暂停执行的功能,所以一旦执行yield next的时候,就会去执行下一个函数。等下一个中间件执行完成时,再在原来中断的地方继续执行。这种执行方式导致形成了koa中著名的洋葱模型。
输入图片说明

举例子如下:

first step before
second step before
second step after
first step after

当使用es7语法时,处理也是一样的。

上下文context对象

相比在express中,koa多了一个上下文对象,创建上下文的源码如下:

app.createContext = function(req, res){
  var context = Object.create(this.context);
  var request = context.request = Object.create(this.request);
  var response = context.response = Object.create(this.response);
  context.app = request.app = response.app = this;
  context.req = request.req = response.req = req;
  context.res = request.res = response.res = res;
  request.ctx = response.ctx = context;
  request.response = response;
  response.request = request;
  context.onerror = context.onerror.bind(context);
  context.originalUrl = request.originalUrl = req.url;
  context.cookies = new Cookies(req, res, {
    keys: this.keys,
    secure: request.secure
  });
  context.accept = request.accept = accepts(req);
  context.state = {};
  return context;
};

从中可以看出,ctx.reqctx.res代表的是noderequestresponse对象,而ctx.requestctx.response则代表的是koa的对应对象。在express中,获取一些参数是通过访问传入的res或者req的对应参数。但是在koa中,则是访问的ctx上下文里面的相应参数。只是两者的封装不一样而已。

以上是我的一些分析,不对的地方希望大神交流和指出。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant