[翻译&摘抄] ES6 中的元编程:代理(Proxies)

2023-11-01

前面几篇博客已经写过了有关 Symbols 和 Reflect 相关的知识,首先来重复看一下:

元编程是什么:元编程(笼统地说)是所有关于一门语言的底层机制,而不是数据建模或者业务逻辑那些高级抽象。如果程序可以被描述为 “制作程序”,元编程就能被描述为 “让程序来制作程序”。例如,反射就是元编程中非常酷的一部分,因为它允许你改变应用程序的内部工作机制。

Symbols 是实现了的反射(Reflection within implementation)—— 你将 Symbols 应用到你已有的类和对象上去改变它们的行为。
Reflect 是通过自省(introspection)实现反射(Reflection through introspection) —— 通常用来探索非常底层的代码信息。
Proxy 是通过调解(intercession)实现反射(Reflection through intercession) —— 包裹对象并通过自陷(trap)来拦截对象行为。

Proxy 是 ES6 中一个新的 API,是一个全局构造函数,你可以传递给它一个对象,以及一些钩子,它将会为你返回一个新的对象:这个对象将会魔法般的被给出的那些钩子包裹。对象因此拥有了代理。

创建代理

Proxy 构造函数接受两个参数,其一是你想要代理的初始对象,其二是一系列处理钩子(handler hooks)。

需要注意,代理维持了一个你创建对象的引用——如果你有了一个原始对象的引用,任何你和原始对象的交互,都会影响到代理,类似地,任何你对代理做的改变,反过来也都会影响到原始对象。换句话说,Proxy 返回了一个包裹了传入对象的新对象,但是任何你对二者的操作,都会影响到它们彼此:

// 代理和原对象的相互影响
var myObject = {};
var proxiedMyObject = new Proxy(myObject, {/*以及一系列处理钩子*/});

assert(myObject !== proxiedMyObject);

myObject.foo = true;
assert(proxiedMyObject.foo === true);

proxiedMyObject.bar = true;
assert(myObject.bar === true);

代理的钩子

钩子本质上就是一系列函数。这些函数控制了你如何和代理对象的交互,因此他们也顺带控制了你如何和原对象的交互。处理钩子都是一些 JS 的内置方法。这些内置方法就是 Reflect 方法的 API。

代理 Proxy 和反射 Reflect,他们两个可以说是一一对应的。每一个代理钩子都对应到一个反射方法,反之亦然,每一个反射方法都有一个代理钩子。完整的反射方法及对应的代理处理钩子如下:

这里写图片描述

通过对这些方法的重写,我们可以改写外界与对象交互的方式。换句话说,代理对象就像是原对象的代理人,对象如何对外界作出反应,必须通过代理人规定的方法。

几个栗子

构建一个可无限链接(chainable)的 API

function urlBuilder(domain) {
  var parts = [];
  var proxy = new Proxy(function () {
    var returnValue = domain + '/' + parts.join('/');
    parts = [];
    return returnValue;
  }, {
    has: function () {
      return true;
    },
    get: function (object, prop) {
      parts.push(prop);
      return proxy;
    },
  });
  return proxy;
}
var google = urlBuilder('http://google.com');
assert(google.search.products.bacon.and.eggs() === 'http://google.com/search/products/bacon/and/eggs')

这个函数的功能通过最后一句话就可以大致看出:将对方法的访问依次转变为网络地址。
在代理中,重写了方法 has 保证所有属性的访问都是有的。对 get 方法的重写则保证了获取属性值的时候依旧返回一个 proxy 完成链式调用,并记录下访问的属性名。

实现一个 “方法缺失” 钩子

许多其他的编程语言都允许你使用一个内置的反射方法去重写一个类的行为,例如,在 PHP 中有 call,在 Ruby 中有 method_missing,在 Python 中则有 getattr。JavaScript 缺乏这个机制,但现在我们有了代理去实现它。

这个机制的作用是,对于任何存在方法,该方法能够按预期被执行。对于不存在方法,在调用时会被 method_missing 替代。另外,method-missing 接受方法名作为第一个参数,这对于判断用户意图非常有用。

通过实现一个 get 方法的代理,可以完成这个功能:

function Foo() {
  return new Proxy(this, {
    get: function (object, property) {
      if (Reflect.has(object, property)) {
        return Reflect.get(object, property);
      } else {
        return function methodMissing() {
          console.log('you called ' + property + ' but it doesn\'t exist!');
        }
      }
    }
  });
}

Foo.prototype.bar = function () {
  console.log('you called bar. Good job!');
}

foo = new Foo();
foo.bar();
// you called bar. Good job!
foo.this_method_does_not_exist();
// you called this_method_does_not_exist but it doesn't exist!

隐藏属性,不被 getOwnPropertyNames、Object.keys、in 等迭代方法找到

var example = new Proxy({ foo: 1, bar: 2 }, {
  has: function () { return false; },
  ownKeys: function () { return []; },
  getOwnPropertyDescriptor: function () { return false; },
});
assert(example.foo === 1);
assert(example.bar === 2);
assert('foo' in example === false);
assert('bar' in example === false);
assert(example.hasOwnProperty('foo') === false);
assert(example.hasOwnProperty('bar') === false);
assert.deepEqual(Object.keys(example), [ ]);
assert.deepEqual(Object.getOwnPropertyNames(example), [ ]);

完美隐藏!

可撤销代理

代理还有最后一个大招:一些代理可以被撤销。为了创建一个可撤销的代理,你需要使用 Proxy.revocable(target, handler) (而不是 new Proxy(target, handler)),并且,最终返回一个结构为 {proxy, revoke()} 的对象来替代直接返回一个代理对象。

function youOnlyGetOneSafetyNet(object) {
  var revocable = Proxy.revocable(object, {
    get(target, property) {
      if (Reflect.has(target, property)) {
        return Reflect.get(target, property);
      } else {
        // 如果属性不存在,代理就会被撤销
        revocable.revoke();
        return 'You only get one safety net';
      }
    }
  });
  return revocable.proxy;
}

var myObject = youOnlyGetOneSafetyNet({ foo: true });

assert(myObject.foo === true);
assert(myObject.foo === true);
assert(myObject.foo === true);

assert(myObject.bar === 'You only get one safety net'); // 代理被撤销
myObject.bar // TypeError
myObject.bar // TypeError
Reflect.has(myObject, 'bar') // TypeError

你可以看到例子中最后一行的右侧,如果代理已经被撤销,任何在代理对象上的操作都会抛出 TypeError —— 即便这些操作句柄还没有被代理。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[翻译&摘抄] ES6 中的元编程:代理(Proxies) 的相关文章

随机推荐

  • OSPF实验及配置---超详细

    什么是OSPF 开放式最短路径优先OSPF Open Shortest Path First 是IETF组织开发的一个基于链路状态的内部网关协议 Interior Gateway Protocol 目前针对IPv4协议使用的是OSPF Ve
  • 【改进算法】混合鲸鱼WOA和BAT算法(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码及文献 1 概述 文献来源 鲸鱼优化算法 whale op
  • 接口测试开发之:Python3,订单并发性能实战

    小屌丝 鱼哥 我想写一个接口订单并发性能 能不能给我讲一下 小鱼 接口订单并发 我前篇文章不是写过常见并发框架 然后你在追加一个创建订单和生成订单不就可以了 小屌丝 鱼哥 你说的可轻松 那你能不能来一个 小鱼 好吧 那我就以我某个项目为例
  • open cvBrisk特征检测与匹配

    什么是BRISK算法 BRISK算法是2011年ICCV上 BRISK Binary Robust Invariant Scalable Keypoints 文章中 提出来的一种特征提取算法 也是一种二进制的特征描述算子 它具有较好的旋转不
  • CentOS 7下安装pptp服务

    一 检查是否支持PPTP 1 在安装之前查看系统是否支持PPTP modprobe ppp compress 18 echo success 应该输出 success 2 是否开启TUN TAP cat dev net tun 应该输出 c
  • Js中读取、移除属性及隐藏组件方法研究

    添加 移除组件属性方法 class名 attr 属性名 属性值 设置指定属性 class名 attr 属性名 读取指定属性值 or document getElementById id值 getAttribute 属性名 class名 re
  • 迪杰斯特拉算法+链式前向星+堆优化

    目录 一 基础 二 使用链式前向星 每次遍历的第一次优化 前向星 链式前向星 1 结构 2 存储边 3 遍历 第一次优化代码 三 堆优化 主要思想 数据类型 四 完整代码 一 基础 直接用邻接矩阵 每次遍历查找来进行操作 void dijk
  • Exchange Online Kiosk产品详细介绍

    目录 前言 电子邮件功能 日历和会议功能 联系人和任务管理功能 移动设备支持
  • pythonz字符串去重并排序

    项目场景 python练习题 问题描述 输入一个非空字符串 去除重复的字符后 从小到大排序输出为一个新字符串 原因分析 去重可以运用python中set数据类型的特性 然后将去重的set转为列表 再调用sort 函数进行排序即可 解决方案
  • 二维已经 OUT 了?3DPose 实现三维人体姿态识别真香

    作者 李秋键 出品 AI科技大本营 ID rgznai100 引言 人体姿态估计是计算机视觉领域很多研究工作的基础 也是研究的热点问题 在行为识别 人机交互 姿态跟踪等领域有着广泛的应用前景 按照人体姿态维度的差异 可以将人体姿态估计任务分
  • Windows FFmpeg 多张图片合并视频

    执行指令 ffmpeg f image2 i images d jpg vcodec libx264 r 25 b 200k test mp4 图片资源截图 重点 图片资源名称的命名规则
  • JDK8 新特性-----对象::new

    public class demo public static void main String args 第一种方式 ICar iCar1 new ICar Override public Car getCar String name I
  • pycharm 安装第三方库方法

    pycharm 使用anaconda库并添加其他库 andconda 中可以添加多个虚拟环境 在安装路径下envs 文件下可以查看 安装的第三方库在D Anaconda3 Lib site packages 文件夹下 在运行程序时 在缺少安
  • 【C#学习笔记】读access2007

    using System using System Data OleDb namespace ConsoleApplication class Program static void Main string args string strC
  • A1103 Integer Factorization (30 分)(将N表示为K个整数的P次方的和)(DFS)(难)

    The K P factorization of a positive integer N is to write N as the sum of the P th power of K positive integers You are
  • STM32——DAC数模转换实验

    一 数模转换原理 STM32的DAC模块是十二位数字输入 电压输出型的DAC DAC可以配置为8位或12位模式 也可以与DMA控制器配合使用 DAC工作在12位模式时 数据可以设置成左对齐或者右对齐 DAC模块有2个输出通道 每个通道都有单
  • backgroundImage加载图片报403解决方法

    加载图片时报403错误 可能是链接防盗链导致 解决方法 在html中加入
  • join方法介绍

    首先给出结论 t join 方法只会使调用该方法的线程进入t对象的等待池 并等待t线程执行完毕后才会被唤醒 并不影响同一时刻处在运行状态的其他线程 一 使用方式 join是Thread类的一个方法 启动线程后直接调用 例如 Thread t
  • 数组的遍历方式(齐全)

    数组 遍历数组 Array 案例 arr1 1 2 3 arr2 3 4 5 arr3 arr1 arr2 log arr3 0 2 输出为 arr1 的 3 一 数组的概念 之前 变量只能存一个值 如果我们想存多个值呢 这就涉及到数组了
  • [翻译&摘抄] ES6 中的元编程:代理(Proxies)

    前面几篇博客已经写过了有关 Symbols 和 Reflect 相关的知识 首先来重复看一下 元编程是什么 元编程 笼统地说 是所有关于一门语言的底层机制 而不是数据建模或者业务逻辑那些高级抽象 如果程序可以被描述为 制作程序 元编程就能被