Koa源碼閱讀-詳解

Application函數

module.exports = class Application extends Emitter {
    constructor() {
        super();
    
        this.proxy = false;
        this.middleware = [];
        this.subdomainOffset = 2;
        this.env = process.env.NODE_ENV || 'development';
        // context,request,response是空對象,能訪問相應原型對象上的屬性和方法
        this.context = Object.create(context);
        this.request = Object.create(request);
        this.response = Object.create(response);
    }
}
複製代碼

Emitter

Application繼承自Emitterhtml

Emitter主要是觀察訂閱者模式,在index.js能夠這樣寫,會出現結果node

const app = new Koa();

app.on('a',(element)=>{
  // 111
  console.log(element)
})
app.emit('a',111)
複製代碼

學習一下Object.create

const a = {a:1,b:2}
const b = Object.create(a); // {}
console.log(b.__proto__ === a) //true
console.log(b.a) //1
複製代碼

a在b的原型鏈上,b能訪問a的屬性git

單元測試

toJSON() {
    return only(this, [
      'subdomainOffset',
      'proxy',
      'env'
    ]);
}

inspect() {
    return this.toJSON();
}
複製代碼

only函數返回新對象,而且只返回包含key,key的值不爲nullgithub

module.exports = function(obj, keys){
  obj = obj || {};
  if ('string' == typeof keys) keys = keys.split(/ +/);
  return keys.reduce(function(ret, key){
    if (null == obj[key]) return ret;
    ret[key] = obj[key];
    return ret;
  }, {});
};
複製代碼
//執行app.inspect() 
describe('app.inspect()', () => {
  it('should work', () => {
    const app = new Koa();
    const util = require('util');
    const str = util.inspect(app);
    // 相等則經過
    assert.equal("{ subdomainOffset: 2, proxy: false, env: 'test' }", str);
  });
});
複製代碼

這裏有個疑問,爲何在index.js 實例後的對象也只有這3個屬性???json

listen

調用listen執行callbackcookie

listen() {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen.apply(server, arguments);
  }
  callback() {
    const fn = compose(this.middleware);
    if (!this.listeners('error').length) this.on('error', this.onerror);

    const handleRequest = (req, res) => {
      res.statusCode = 404;
      const ctx = this.createContext(req, res);
      const onerror = err => ctx.onerror(err);
      const handleResponse = () => respond(ctx);
      onFinished(res, onerror);
      fn(ctx).then(handleResponse).catch(onerror);
    };

    return handleRequest;
  }
複製代碼

在這裏順便說一下,原生 http建立一個服務是這樣子滴app

const http = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('okay');
});
http.listen(8000)
複製代碼

createContext

createContext(req, res) {
    // 以this.context爲藍本創建對象,此時引入的this.context已經兩次實例了。request和response同理,而且做爲屬性掛在context上
    const context = Object.create(this.context);
    const request = context.request = Object.create(this.request);
    const response = context.response = Object.create(this.response);
    // context request response都能拿到this,req,res
    context.app = request.app = response.app = this;
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    // request response有context;
    request.ctx = response.ctx = context;
    // request和response能互相拿到各自的對象
    request.response = response;
    response.request = request;
    // context 和 request的originalUrl有了req.url
    context.originalUrl = request.originalUrl = req.url;
    // context能拿到cookie
    context.cookies = new Cookies(req, res, {
      keys: this.keys,
      secure: request.secure
    });
    request.ip = request.ips[0] || req.socket.remoteAddress || '';
    context.accept = request.accept = accepts(req);
    context.state = {};
    // 返回 context
    return context;
}
複製代碼

respond

通過中間件的處理,responddom

function respond(ctx) {
  // allow bypassing koa
  if (false === ctx.respond) return;

  const res = ctx.res;
  if (!ctx.writable) return;
  //拿到body
  let body = ctx.body;
  const code = ctx.status;
  // 若是body不存在,那麼返回默認not found
  // status body
  if (null == body) {
    body = ctx.message || String(code);
    if (!res.headersSent) {
      ctx.type = 'text';
      ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
  }
  // 若是body是二進制,是字符串,Stream流,直接返回
  // responses
  if (Buffer.isBuffer(body)) return res.end(body);
  if ('string' == typeof body) return res.end(body);
  if (body instanceof Stream) return body.pipe(res);

  // body: json json的處理
  body = JSON.stringify(body);
  if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
  }
  res.end(body);
}
複製代碼

context函數

前面主要作了onerror和暴露屬性的處理 來看最重要的koa

/** * context可以使用response下attachment redirect的方法, * 可以設置respons下status message的屬性, * 能讀headerSent和writable的值 */
delegate(proto, 'response')
  .method('attachment')
  .method('redirect')
  .method('remove')
  .method('vary')
  .method('set')
  .method('append')
  .method('flushHeaders')
  .access('status')
  .access('message')
  .access('body')
  .access('length')
  .access('type')
  .access('lastModified')
  .access('etag')
  .getter('headerSent')
  .getter('writable');
複製代碼
function Delegator(proto, target) {
  // 調用函數即返回實例後的本身,每調用一次都是不一樣的this,好處可以調用原型對象上的方法
  if (!(this instanceof Delegator)) return new Delegator(proto, target);
  this.proto = proto;
  this.target = target;
  this.methods = [];
  this.getters = [];
  this.setters = [];
  this.fluents = [];
}
複製代碼
// 把target的上的方法掛載在proto上,而執行的this倒是target的
Delegator.prototype.method = function(name){
  var proto = this.proto;
  var target = this.target;
  this.methods.push(name);

  proto[name] = function(){
    return this[target][name].apply(this[target], arguments);
  };

  return this;
};
複製代碼

proto[name]能讀取target[name]socket

Delegator.prototype.getter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.getters.push(name);

  proto.__defineGetter__(name, function(){
    return this[target][name];
  });

  return this;
};
複製代碼

proto[name]能設置target[name]

Delegator.prototype.setter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.setters.push(name);

  proto.__defineSetter__(name, function(val){
    return this[target][name] = val;
  });

  return this;
};
複製代碼

可讀可寫

Delegator.prototype.access = function(name){
  return this.getter(name).setter(name);
};
複製代碼

request對象

一句話,request對象主要作了拿到請求的信息 咱們看幾個例子

// 返回this.req.headers信息
get header() {
    return this.req.headers;
},

get(field) {
    const req = this.req;
    switch (field = field.toLowerCase()) {
      case 'referer':
      case 'referrer':
        return req.headers.referrer || req.headers.referer || '';
      default:
        return req.headers[field] || '';
}
},
// 獲取請求頭的內容鏟毒
get length() {
    const len = this.get('Content-Length');
    if (len == '') return;
    return ~~len;
},
//得到query信息
get querystring() {
    if (!this.req) return '';
    return parse(this.req).query || '';
}
複製代碼

response對象

response對象主要作了相應頭的的信息

set(field, val) {
    if (2 == arguments.length) {
      if (Array.isArray(val)) val = val.map(String);
      else val = String(val);
      this.res.setHeader(field, val);
    } else {
      for (const key in field) {
        this.set(key, field[key]);
      }
    }
},
set lastModified(val) {
    if ('string' == typeof val) val = new Date(val);
    this.set('Last-Modified', val.toUTCString());
    },
    set etag(val) {
    if (!/^(W\/)?"/.test(val)) val = `"${val}"`;
    this.set('ETag', val);
}

redirect(url, alt) {
    // location
    if ('back' == url) url = this.ctx.get('Referrer') || alt || '/';
    this.set('Location', url);
    
    // status
    if (!statuses.redirect[this.status]) this.status = 302;
    
    // html
    if (this.ctx.accepts('html')) {
      url = escape(url);
      this.type = 'text/html; charset=utf-8';
      this.body = `Redirecting to <a href="${url}">${url}</a>.`;
      return;
    }
    
    // text
    this.type = 'text/plain; charset=utf-8';
    this.body = `Redirecting to ${url}.`;
}
複製代碼

結合官網看更好

以上,🌹謝謝你的閱讀,你的點贊是我寫文章的動力。 git博客地址

相關文章
相關標籤/搜索