# 每天阅读一个 npm 模块(7)- delegates

栏目: Node.js · 发布时间: 6年前

内容简介:系列文章:因为准备深入地探究 koa 及相关的生态,所以接下来一段时间阅读的 npm 模块都会和 koa 密切相关 ^_^今天阅读的模块是delegates,它由大名鼎鼎的

系列文章:

  1. 每天阅读一个 npm 模块(1)- username
  2. 每天阅读一个 npm 模块(2)- mem
  3. 每天阅读一个 npm 模块(3)- mimic-fn
  4. 每天阅读一个 npm 模块(4)- throttle-debounce
  5. 每天阅读一个 npm 模块(5)- ee-first
  6. 每天阅读一个 npm 模块(6)- pify

因为准备深入地探究 koa 及相关的生态,所以接下来一段时间阅读的 npm 模块都会和 koa 密切相关 ^_^

一句话介绍

今天阅读的模块是delegates,它由大名鼎鼎的 TJ 所写,可以帮我们方便快捷地使用 设计模式 当中的 委托模式(Delegation Pattern) ,即外层暴露的对象将请求委托给内部的其他对象进行处理,当前版本是 1.0.0,周下载量约为 364 万。

用法

delegates 基本用法就是将内部对象的变量或者函数绑定在暴露在外层的变量上,直接通过 delegates 方法进行如下委托,基本的委托方式包含:

  • getter:外部对象可以直接访问内部对象的值
  • setter:外部对象可以直接修改内部对象的值
  • access:包含 getter 与 setter 的功能
  • method:外部对象可以直接调用内部对象的函数
const delegates = require('./index');

const petShop = {
  dog: {
    name: '旺财',
    age: 1,
    sex: '猛汉',
    bar() {
      console.log('bar!');
    }
  },
}

// 将内部对象 dog 的属性、函数
// 委托至暴露在外的 petShop 上
delegates(petShop, 'dog')
  .getter('name')
  .setter('age')
  .access('sex')
  .method('bar');

// 访问内部对象属性
console.log(petShop.name)
// => '旺财'

// 修改内部对象属性
petShop.age = 2;
console.log(petShop.dog.age)
// => 2

// 同时访问和修改内部对象属性
console.log(petShop.sex)
// => '猛汉'
petShop.sex = '公主';
console.log(petShop.sex);
// => '公主'

// 调用内部对象函数
petShop.bar();
// 'bar!'
复制代码

除了上面这种方式之外,还可以在外部对象上添加类似 jQuery 风格的函数,即:

  • 函数不传参数的时候,获取对应的值
  • 函数传参数的时候,修改对应的值
const delegates = require('./index');

const petShop = {
  dog: {
    name: '旺财',
  },
}

delegates(petShop, 'dog')
  .fluent('name');

// 不传参数,获取内部属性
console.log(petShop.name());

// 传参数,修改内部属性
// 还可以链式调用
console.log(
    petShop.name('二哈')
    	.name('蠢二哈')
    	.name();
);
复制代码

源码学习

初始化

// 源码 7 - 1
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 = [];
}
复制代码

this 对象中 methods | getters | setters | flaunts 均为数组,用于记录委托了哪些属性和函数。

上述初始化函数的第一行值得引起注意: 如果 this 不是 Delegator 的实例的话,则调用 new Delegator(proto, target) 。通过这种方式,可以避免在调用初始化函数时忘记写 new 造成的问题,因为此时下面两种写法是等价的:

let x = new Delegator(petShop, 'dog')
let x = Delegator(petShop, 'dog')

另外讲一讲在调用 new 时主要做了以下事情:

  1. 将构造函数内的 this 指向新创建的空对象 {}
  2. 执行构造函数体
  3. 如果构造函数没有显示返回值的话,则返回 this
  4. 如果构造函数有显示返回值的话,则返回对应的值

getter

// 源码 7-2
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__ 的使用,它可以在已存在的对象上添加可读属性,其中第一个参数为属性名,第二个参数为函数,返回值为对应的属性值:

const obj = {};
obj.__defineGetter__('name', () => 'elvin');

console.log(obj.name);
// => 'elvin'

obj.name = '旺财';
console.log(obj.name);
// => 'elvin'
// 我怎么能被改名叫旺财呢!
复制代码

需要注意的是尽管 __defineGetter__ 曾被广泛使用,但是已不被推荐,建议通过 Object.defineProperty 实现同样功能,或者通过 get 操作符实现类似功能:

const obj = {};
Object.defineProperty(obj, 'name', {
  value: 'elvin',
});

Object.defineProperty(obj, 'sex', {
  get() {
    return 'male';
  }
});

const dog = {
  get name() {
    return '旺财';
  }
};
复制代码

Github 上已有人提出相应的 PR#20 ,不过因为 TJ 已经离开了 Node.js 社区,所以估计也不会更新这个仓库了。

setter

// 源码 7-3
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;
};
复制代码

上述代码与 getter 几乎一模一样,不过使用的是 __defineSetter__ ,它可以在已存在的对象上添加可读属性,其中第一个参数为属性名,第二个参数为函数,参数为传入的值:

const obj = {};
obj.__defineSetter__('name', function(value) {
  this._name = value;
});

obj.name = 'elvin';
console.log(obj.name, obj._name);
// undefined 'elvin'
复制代码

同样地,虽然 __defineSetter__ 曾被广泛使用,但是已不被推荐,建议通过 Object.defineProperty 实现同样功能,或者通过 set 操作符实现类似功能:

const obj = {};
Object.defineProperty(obj, 'name', {
  set(value) {
    this._name = value;
  }
});

const dog = {
  set(value) {
    this._name = value;
  }
};
复制代码

method

// 源码 7-4
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;
};
复制代码

method 的实现也十分简单,只需要注意这里 apply 函数的第一个参数是内部对象 this[target] ,从而确保了在执行函数 this[target][name] 时,函数体内的 this 是指向对应的内部对象。

其它delegates 提供的函数如 fluent | access 都是类似的,就不重复说明了。

koa 中的使用

在 koa 中,其核心就在于 context 对象,许多读写操作都是基于它进行,例如:

  • ctx.header 获取请求头

  • ctx.method 获取请求方法

  • ctx.url 获取请求 URL

  • ...

这些对请求参数的获取都得益于 koa 中 context.request 的许多属性都被委托在了 context 上:

// Koa 源码 lib/context.js
delegate(proto, 'request')
  .method('acceptsLanguages')
  .method('acceptsEncodings')
  .access('path')
  .access('url')
  .getter('headers')
  .getter('ip');
  // ...
复制代码

又例如:

  • ctx.body 设置响应体
  • ctx.status 设置响应状态码
  • ctx.redirect() 请求重定向
  • ...

这些对响应参数的设置都得益于 koa 中 context.response 的许多属性和方法都被委托在了 context 上:

// Koa 源码 lib/context.js
delegate(proto, 'response')
  .method('redirect')
  .method('vary')
  .access('status')
  .access('body')
  .getter('headerSent')
  .getter('writable');
  // ...
复制代码

关于我:毕业于华科,工作在腾讯,elvin 的博客 欢迎来访 ^_^


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

自制搜索引擎

自制搜索引擎

[日]山田浩之、[日]末永匡 / 胡屹 / 人民邮电出版社 / 2016-1 / 39.00元

《自制搜索引擎》聚焦于Google和Yahoo!等Web搜索服务幕后的搜索引擎系统,首先讲解了搜索引擎的基础知识和原理,接着以现实中的开源搜索引擎Senna/Groonga为示例,使用该引擎的源代码引导读者亲自体验搜索引擎的开发过程。这部分讲解涉及了倒排索引的制作和压缩、检索的处理流程以及搜索引擎的优化等内容。又简单介绍了一些更加专业的搜索引擎的知识和要点,为读者今后进一步学习打下了基础。本书适合......一起来看看 《自制搜索引擎》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试