从源码学习使用 node-delegates
node-delegates 是 TJ 大神所写的一个简单的小工具,源码只有 157 行,作用在于将外部对象接受到的操作委托到内部属性进行处理,也可以理解为讲对象的内部属性暴露到外部,简化我们所需要书写的代码。
安装和使用的代码在源码仓库都可以找到,这里主要先讲一下 API。
API
Delegate(proto, prop)
用于创建一个 delegator 实例,用于把 proto 接收到的一些操作委托给它的 prop 属性进行处理。
Delegate.auto(proto, targetProto, targetProp)
根据 targetProp 所包含的键,自动判断类型,把 targetProto 上的对应属性代理到 proto。可以是 getter、setter、value 或者 method。
Delegate.prototype.method(name)
在 proto 对象上新增一个名为 name
的函数,调用该函数相当于调用 proto 的 prop 属性上的 name 函数。
Delegate.prototype.getter(name)
新增一个 getter 到 proto 对象,访问该 getter 即可访问 proto 的 prop 的对应 getter。
Delegate.prototype.setter(name)
同 getter。
Delegate.prototype.access(name)
在 proto 上同时新增一个 getter 和一个 setter,指向 proto.prop 的对应属性。
Delegate.prototype.fluent(name)
access
的特殊形式。
delegate(proto, 'request')
.fluent('query')
// getter
var q = request.query();
// setter (chainable)
request
.query({ a: 1 })
.query({ b: 2 });
源码阅读
/**
* Expose `Delegator`.
*/
// 暴露 Delegator 构造函数
module.exports = Delegator;
/**
* Initialize a delegator.
* 构造一个 delegator 实例
* @param {Object} proto 外部对象,供外部调用
* @param {String} target 外部对象的某个属性,包含具体处理逻辑
* @api public
*/
function Delegator(proto, target) {
// 如果没有使用 new 操作符调用构造函数,则使用 new 构造
if (!(this instanceof Delegator)) return new Delegator(proto, target);
// 构造实例属性
this.proto = proto;
this.target = target;
this.methods = [];
this.getters = [];
this.setters = [];
this.fluents = [];
}
/**
* Automatically delegate properties
* from a target prototype
* 根据 targetProp 自动委托,绑定一个属性到 Delegator 构造函数
* @param {Object} proto 接受请求的外部对象
* @param {object} targetProto 处理具体逻辑的内部对象
* @param {String} targetProp 包含要委托的属性的对象
* @api public
*/
Delegator.auto = function(proto, targetProto, targetProp){
var delegator = Delegator(proto, targetProp);
// 根据 targetProp 获取要委托的属性
var properties = Object.getOwnPropertyNames(targetProto);
// 遍历所有要委托的属性
for (var i = 0; i < properties.length; i++) {
var property = properties[i];
// 获取 targetProto 上对应属性的 descriptor
var descriptor = Object.getOwnPropertyDescriptor(targetProto, property);
// 如果当前属性的 get 被重写过,就作为 getter 委托(使用 __defineGetter__ 或者 Object.defineProperty 指定 getter 都会重写 descriptor 的 get 属性)
if (descriptor.get) {
delegator.getter(property);
}
// 同 get,如果 set 被重写过,那就作为 setter 委托
if (descriptor.set) {
delegator.setter(property);
}
// 如果当前 property 具有 value,那么判断是函数还是普通值
if (descriptor.hasOwnProperty('value')) { // could be undefined but writable
var value = descriptor.value;
if (value instanceof Function) {
// 是函数就进行函数委托
delegator.method(property);
} else {
// 是普通值就作为 getter 委托
delegator.getter(property);
}
// 如果这个值可以重写,那么继续进行 setter 委托
if (descriptor.writable) {
delegator.setter(property);
}
}
}
};
/**
* Delegate method `name`.
*
* @param {String} name
* @return {Delegator} self
* @api public
*/
Delegator.prototype.method = function(name){
var proto = this.proto;
var target = this.target;
this.methods.push(name);
// 在 proto 上定义一个 name 的方法
proto[name] = function(){
// 实际还是调用的 proto[target][name],内部的 this 还是指向 proto[target]
return this[target][name].apply(this[target], arguments);
};
return this;
};
/**
* Delegator accessor `name`.
*
* @param {String} name
* @return {Delegator} self
* @api public
*/
Delegator.prototype.access = function(name){
// 同时定义 getter 和 setter
return this.getter(name).setter(name);
};
/**
* Delegator getter `name`.
* 委托 name getter
* @param {String} name
* @return {Delegator} self
* @api public
*/
Delegator.prototype.getter = function(name){
var proto = this.proto;
var target = this.target;
this.getters.push(name);
// 使用 __defineGetter__ 绑定 name getter 到 proto
proto.__defineGetter__(name, function(){
// 注意 this 指向 proto 本身,所以 proto[name] 最终访问的还是 proto[target][name]
return this[target][name];
});
// 此处 this 指向 delegator 实例,构造链式调用
return this;
};
/**
* Delegator setter `name`.
* 在 proto 上委托一个 name setter
* @param {String} name
* @return {Delegator} self
* @api public
*/
Delegator.prototype.setter = function(name){
var proto = this.proto;
var target = this.target;
this.setters.push(name);
// 通过 __defineSetter__ 方法指定一个 setter 到 proto
proto.__defineSetter__(name, function(val){
// 注意 this 指向 proto 本身,所以对 proto[name] 设置值即为为 proto[target][name] 设置值
return this[target][name] = val;
});
// 返回自身实现链式调用
return this;
};
/**
* Delegator fluent accessor
*
* @param {String} name
* @return {Delegator} self
* @api public
*/
Delegator.prototype.fluent = function (name) {
var proto = this.proto;
var target = this.target;
this.fluents.push(name);
proto[name] = function(val){
// 如果 val 不为空,那么就作为 setter 使用
if ('undefined' != typeof val) {
this[target][name] = val;
// 完事后返回 proto 自身,实现链式调用
return this;
} else {
// 如果 val 未定义,那么作为 getter 使用,返回具体的值
return this[target][name];
}
};
return this;
};
具体案例
之所以会研究一下这个库是因为在看 koa
源码的时候看到使用了这个库,在 koa
中通过使用 node-delegates
把 context.request
和 context.response
上的属性都委托到了 context
自身。所以我们可以直接使用 context.query
、context.status
来进行操作,简化了我们所写的代码。
koa 源码位置链接:https://github.com/koajs/koa/blob/b7fc526ea49894f366153bd32997e02568c0b8a6/lib/context.js#L191
总结
- 通过
__defineGetter__
和__defineSetter__
可以设置 getter 和 setter,但是 MDN 显示这两个 API 已被 deprecated,github 也已经有人提了 issue 和 pr。另外,通过这两个 API 设置 getter 和 setter 时,传递的函数的内部 this 指向原来的属性,比如:let a = { nickName: 'HotDog' }
a.__defineGetter__('name', function() {
return this.nickName // 此处 this 仍然指向 a
})
- 学习了委托模式,可以把外部对象接收到的操作委托给内部属性(或其他对象)进行具体的处理。
从源码学习使用 node-delegates的更多相关文章
- Spring5.0源码学习系列之浅谈BeanFactory创建
Spring5.0源码学习系列之浅谈BeanFactory创建过程 系列文章目录 提示:Spring源码学习专栏链接 @ 目录 系列文章目录 博客前言介绍 一.获取BeanFactory主流程 二.r ...
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- Java并发包源码学习之AQS框架(四)AbstractQueuedSynchronizer源码分析
经过前面几篇文章的铺垫,今天我们终于要看看AQS的庐山真面目了,建议第一次看AbstractQueuedSynchronizer 类源码的朋友可以先看下我前面几篇文章: <Java并发包源码学习 ...
- 【 js 基础 】【 源码学习 】源码设计 (持续更新)
学习源码,除了学习对一些方法的更加聪明的代码实现,同时也要学习源码的设计,把握整体的架构.(推荐对源码有一定熟悉了之后,再看这篇文章) 目录结构:第一部分:zepto 设计分析第二部分:undersc ...
- hashMap源码学习记录
hashMap作为java开发面试最常考的一个题目之一,有必要花时间去阅读源码,了解底层实现原理. 首先,让我们看看hashMap这个类有哪些属性 // hashMap初始数组容量 static fi ...
- Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md
写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...
- Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签
写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...
- Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作
写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...
- Underscore.js 源码学习笔记(上)
版本 Underscore.js 1.9.1 一共 1693 行.注释我就删了,太长了… 整体是一个 (function() {...}()); 这样的东西,我们应该知道这是一个 IIFE(立即执行 ...
- 基于jdk1.8的HashMap源码学习笔记
作为一种最为常用的容器,同时也是效率比较高的容器,HashMap当之无愧.所以自己这次jdk源码学习,就从HashMap开始吧,当然水平有限,有不正确的地方,欢迎指正,促进共同学习进步,就是喜欢程序员 ...
随机推荐
- C#DataGrid列值出现E形式的小数,将DataGrid表格上的数据保存至数据库表时会因格式转换不正确导致报错
问题描述:在DataGridView中调整金额一列,当输入小数0.000001后会显示1E-6,此时进行保存操作时报错,提示无法将string类型转换成Decimal 原因分析:由于列调整金额为1E- ...
- java.io.IOException: Broken pipe
最近项目虽然已经在正常运行,但是偶尔会有一些不知名的错误冒出来,比如时不时报一个数据库主键重复或者某些时候会有null的异常报出来.看看代码写完能跑起来还只是开始而已,需要不断精进重构,才能让代码运行 ...
- Derby 数据库 客户端 ij使用
Derby是开源的.嵌入式的Java数据库程序,ij是Derby提供的客户端工具,相当于其他数据库提供的sqlplus工具. ij是纯Java的程序,不用安装,使用起来就像运行普通的Java应用程序一 ...
- golang docker kubernetes
不断共建Golang生态.其中比较有代表性的Golang编写软件作品是Docker和Kubernetes.从目前Golang的发展时间和社区活跃度来看,Golang无疑是一门成功的编程语言.
- 好不容易当上技术管理者,却时常担心被下属diss技术水平,怎么办?
作者 | 刘建国出处 | 极客时间<技术管理实战 36 讲>专栏编辑 | Natalie 转型做管理后,你可以用在技术上的时间会越来越少,尤其是写代码的机会越来越少,手越来越生,但是要做的 ...
- golang打包和部署到centos7
一.环境说明:VS code 二.编译: set GOOS=linux set GOARCH=amd64 go build -o "packageName" 三.发布 上传到服 ...
- NotePad++ 使用 DBGp 调试php
1.使用phpinfo();查看php版本信息 2.配置xdebug 2.1 根据php版本信息,下载对应的xdebug . 2.2将下载的php_xdebug-2.1.2-5.3-vc6.dll文件 ...
- socket 在Unix domain的使用
server.c #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #includ ...
- Educational Codeforces Round 33 (Rated for Div. 2) B题
B. Beautiful Divisors Recently Luba learned about a special kind of numbers that she calls beautiful ...
- ThinkPHP,page,paginate后台分页翻页时保留检索条件的方法
paginate(20,false,['query'=>request()->param()]); 20是每页显示行数 示例代码: $list = Db::name('article ...