系列文章:

  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. 如果构造函数有显示返回值,且该值为对象的话,则返回对应的值
  4. 如果构造函数没有显示返回值或者显示返回值不是对象(例如显示返回值为 1, 'haha' 等)的话,则返回 this

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 的博客 欢迎来访 ^_^

# 每天阅读一个 npm 模块(7)- delegates相关推荐

  1. # 每天阅读一个 npm 模块(8)- koa-route

    系列文章: 每天阅读一个 npm 模块(1)- username 每天阅读一个 npm 模块(2)- mem 每天阅读一个 npm 模块(3)- mimic-fn 每天阅读一个 npm 模块(4)- ...

  2. 每天阅读一个 npm 模块(4)- throttle-debounce

    系列文章: 每天阅读一个 npm 模块(1)- username 每天阅读一个 npm 模块(2)- mem 每天阅读一个 npm 模块(3)- mimic-fn 上一篇文章中介绍的属性描述符的知识太 ...

  3. 每天阅读一个 npm 模块(5)- ee-first

    系列文章: 每天阅读一个 npm 模块(1)- username 每天阅读一个 npm 模块(2)- mem 每天阅读一个 npm 模块(3)- mimic-fn 每天阅读一个 npm 模块(4)- ...

  4. 每天阅读一个 npm 模块(1)- username

    最近工作比较繁忙,每天能用于学习知识的时间越来越少,深感这样不利于自己的技术提升.恰好想起 狼叔 所说的 "迷茫时学习 Node.js 最好的方法 - 每天看十个 npm 模块", ...

  5. 每天看 10 个 NPM 模块?

    最近看到阿里前端技术专家狼叔在 17 年的这篇<迷茫时学习 Node.js 最好的方法>[1]提到: 今天小弟过来找我,说迷茫,我告诉他一个密法:一天看 10 个 npm 模块,坚持一年就 ...

  6. [译] 在 Google Apps 脚本中使用 ES6 和 npm 模块

    原文地址:Using ES6 and npm modules in Google Apps Script 原文作者:Prasanth Janardanan 译文出自:掘金翻译计划 本文永久链接:git ...

  7. nodejs安装及npm模块插件安装路径配置

    在学习完js后,我们就要进入nodejs的学习,因此就必须配置nodejs和npm的属性了. 我相信,个别人在安装时会遇到这样那样的问题,看着同学都已装好,难免会焦虑起来.于是就开始上网查找解决方案, ...

  8. 如何在React Native中写一个自定义模块

    前言 在 React Native 项目中可以看到 node_modules 文件夹,这是存放 node 模块的地方,Node.js 的包管理器 npm 是全球最大的开源库生态系统.提到npm,一般指 ...

  9. 如何在Node JS中卸载NPM模块?

    本文翻译自:How to uninstall npm modules in node js? As commonly known, any npm module can be installed by ...

  10. 手撸一个npm包,安利一下duiba-sprite

    背景 我所在组负责我司线上H5互动小游戏的开发,其中一部分开发者负责皮肤的开发.大致流程为:视觉出psd,开发者切图,开发者开发,开发者上传皮肤代码,运营验收.这里边有个奇葩的动作:开发者切图,为什么 ...

最新文章

  1. IBM公司新推一个基于云计算的Web分析工具
  2. explode 无分隔符_使用PHP explode()函数时出现“空分隔符”警告
  3. DL之Keras: Keras深度学习框架的注意事项(默认下载存放路径等)、使用方法之详细攻略
  4. 进程线程007 进程挂靠与跨进程读写内存
  5. android--service之aidl传递复杂对象,Android--Service之AIDL传递复杂对象
  6. 计算机本科毕业设计:毕业设计、论文要点及我们面对毕业答辩应持有的态度
  7. 《大数据》2015年第2期“专题”——我国大数据交易的主要问题及建议
  8. java arraylist 初始化_Java面试整理-基础篇8.集合1
  9. 线程同步--关键代码段(三)
  10. linux 内核模块发送udp,在内核模块级缓冲UDP的问题(当然不是SOCKET编程了。)
  11. Kotlin入门(21)活动页面的跳转处理
  12. 正确配置Linux系统ulimit/nproc值的方法
  13. 28.Linux/Unix 系统编程手册(上) -- 详述进程创建和程序执行
  14. 计算机网络与信息安全公务员,计算机网络技术专业考公务员有些职位?
  15. 三维地图下载,3D地图下载,谷歌地球三维地形图查看
  16. leetcode296. 最佳的碰头地点
  17. 中位数技巧(推理+证明)
  18. 关于计算机的教学论文,关于计算机教学论文
  19. android8 avb检验,android avb(Android Verified Boot)验证
  20. 计算机内存和外存的作用,内存和外存的主要区别之处竟是在这里!

热门文章

  1. SRE之道:创造软件系统来维护系统运行
  2. 系统架构与软件架构是一层含义吗
  3. 视图状态机制下的IStateManager接口
  4. 剑指 Offer II 083. 没有重复元素集合的全排列
  5. Python文件输入输出
  6. Oracle 拼接结果过长,ora-01489 字符串连接的结果过长 解决方案
  7. 蓝桥杯2017年第八届C/C++省赛B组第四题-方格分割
  8. Ubuntu安装好后,没有网络怎么办?
  9. 【安装包】gcc编译器
  10. Eclipse — 如何导入MySQL驱动