npm 模块 delegates 源码总结

355 阅读3分钟

该博客是个人学习 delegates 总结篇,如理解有误,请留言或在 GitHub 提交 issue 纠正。
转载请标明出处。

前言

在学习 Koa 源码时,context.js 文件上下文对象将 request.jsresponse.js 文件导出对象的方法和存取描述符属性都伪添加到了 context 对象上,对内部实现很感兴趣,所以学习 delegates 做一篇学习总结。

伪添加:从 context 上访问 request 或 response 对象的属性或方法

delegates 基本使用

用于 Node 方法和访问器属性的委托,将一个对象的方法或 getter 和 setter 属性代理到指定对象上。

基础示例

const Delegates = require('delegates');

const options = {
  url: 'http://localhost:3001',
  method: 'POST',
};

const request = {
  getData() {
    return 'data';
  },
  getList() {
    return [1, 2, 3];
  },
  get url() {
    return options.url;
  },
  set url(newUrl) {
    options.url = newUrl;
  },
  get method() {
    return options.method;
  },
};

const ctx = {
  request,
};

Delegates(ctx, 'request')
  .method('getData')
  .method('getList')
  .access('url')
  .getter('method');

console.log(ctx);

将 ctx 上的 request 对象的方法和属性都伪添加到 ctx 对象上,可以通过 ctx.getData() 或 ctx.url 方便的访问 request 对象上的同名方法。

以下访问是同等的:

ctx.getData === ctx.request.getData
ctx.url === ctx.request.url


Koa 内部也是这样实现的,方便通过 ctx 上下文对象访问 request 或 response 对象上的属性或方法。

Koa context.js 部分源码

 

上面示例 ctx 的输出结果是:

delegates 源码分析

constructor

function Delegator(proto, target) {
  if (!(this instanceof Delegator)) return new Delegator(proto, target);
  this.proto = proto;
  this.target = target;
  this.methods = [];
  this.getters = [];
  this.setters = [];
  this.fluents = [];
}

支持直接调用 Delegator 构造函数, 内部根据 this 指向决定内部是否帮创建出实例对象返回。instanceof 作用在 this 的整条 [[Prototype]] 链中是否有指向 Delegator.prototype 的对象。

proto 可以理解为 ctx,target 理解为 request 对象。要将 request 的属性或方法通过 proto 对象进行代理。

method

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;
};

将 target 对象上指定的方法代理到 proto 对象上。内部在 proto 对象上添加了与 target 同名的方法。当访问 proto 上方法时内部会代理到 target 上指定的方法。因为,name 方法名保存在闭包环境中。

Delegates(ctx, 'request')
  .method('getData')

ctx.getData() === ctx.request.getData()

getter

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;
};


__defineGetter__  方法将一个函数绑定在当前对象的指定的属性上,当那个属性的值被读取时,所绑定的函数就会被调用。getter 方法将 target 上指定的属性添加到 proto 对象上。

setter

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;
};


setter 和 getter 的基本原理是一样的。 __defineSetter__ 方法将一个函数绑定在当前对象指定的属性上,当那个属性被赋值时,你所绑定的函数就会被调用。当使用 ctx.url = xxx 时,函数内部会给真正的 request 对象 url 属性赋值。

access

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


access 是 getter 和 setter 的组合。指定 request 对象上的属性在 ctx 上具有可读写性。

fluent

先看一下 fluent 使用例子:

const Delegates = require('delegates');
const a = {
  b: {
    name: "qqf"
  } 
}

Delegates(a, "b")
  .fluent("name")

a.name() // qqf
a.name("abc")
a.name() // abc

这是 fluent 的使用方法,和 jquery 的 val()  取值和 val(xxx)  设置值有相同的味道。其 fluent 内部实现很简单。

Delegator.prototype.fluent = function (name) {
  var proto = this.proto;
  var target = this.target;
  this.fluents.push(name);

  proto[name] = function(val){
    if ('undefined' != typeof val) {
      this[target][name] = val;
      return this;
    } else {
      return this[target][name];
    }
  };

  return this;
};


将 request 对象的指定属性在 ctx 对象上添加同名函数,当调用时根据参数进行读和写的处理。如果参数有值则为设置,没有值则为获取。

auto 方法的实现暂不说了,感兴趣可以自行查看。