JS 中Proxy代理和 Reflect反射方法示例详解

目录
  • 正文
  • 1.属性描述符
  • 2.Reflect
  • 3.Proxy
    • 3.1 创建空代理
    • 3.2 定义捕获器
    • 3.3 捕获器不变式
    • 3.4 可撤销代理
  • 4.代理捕获器与反射方法
    • 4.1 get()
    • 4.2 set()
    • 4.3 has()
    • 4.4 deleteProperty()
    • 4.5 apply()
    • 4.6 construct()

正文

总所周知,Vue2 => Vue3 时,数据响应式方法从Object.defineProperty()方法变成了Proxy(),所以今天与大家 Proxy(代理)和 Reflect(反射)的知识。

讲解 Proxy 和 Reflect 前,我们需要先了解属性描述符的作用,所以我们线简单解释一下属性描述符的知识。

1.属性描述符

属性描述符(Property Descriptor) 本质上是一个 JavaScript 普通对象,用于描述一个属性的相关信息,共有下面这几种属性。

  • value:属性值
  • configurable:该属性的描述符是否可以修改
  • enumerable:该属性是否可以被枚举
  • writable:该属性是否可以被重新赋值
  • 存取器属性:属性描述符中如果配置了 get 和 set 中的任何一个,则该属性不再是一个普通属性,而变成了存取器属性
    • get()读值函数:如果一个属性是存取器属性,则读取该属性时,会运行 get 方法,并将 get 方法得到的返回值作为属性值
    • set(newVal)存值函数:如果给该属性赋值,则会运行 set 方法,newVal 参数为赋值的值。

存取器属性最大的意义,在于可以控制属性的读取和赋值,在函数里可以进行各种操作。

Vue2 数据响应式就是使用了这一点,在 getter 和 setter 函数中进行了数据绑定与派发更新。

注意点:value 和 writable 属性不能与 get 和 set 属性二者不可共存,二者只能选其一。

查看某个对象的属性描述符,使用以下这两种方法:

Object.getOwnPropertyDescriptor(对象, 属性名); //得到一个对象的某个属性的属性描述符
Object.getOwnPropertyDescriptors(对象); //得到某个对象的所有属性描述符

为某个对象添加属性时 或 修改属性时,配置其属性描述符,使用以下这两种方法:

Object.defineProperty(对象, 属性名, 描述符); //设置一个对象的某个属性
Object.defineProperties(对象, 多个属性的描述符); //设置一个对象的多个属性

2.Reflect

Reflect 是什么? Reflect 是一个内置的 JS 对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些 JS 底层功能。

由于它类似于其他语言的反射,因此取名为 Reflect。

它可以做什么? 使用 Reflect 可以实现诸如:属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在与对象中 等等功能。

这些功能不是已经存在了吗?为什么还需要用 Reflect 实现一次? 有一个重要的理念,在 ES5 就被提出:减少魔法、让代码更加纯粹(语言的方法使用 API 实现,而不使用特殊语法实现),这种理念很大程度上是受到函数式编程的影响。 ES6 进一步贯彻了这种理念,它认为,对属性内存的控制、原型链的修改、函数的调用等等,这些都属于底层实现,属于一种魔法,因此,需要将它们提取出来,形成一个正常的 API,并高度聚合到某个对象中,于是就造就了 Reflect 对象。 因此,你可以看到 Reflect 对象中有很多的 API 都可以使用过去的某种语法或其他 API 实现。

Reflect 里面提供了哪些 API 呢?

Reflect API 用处 等同于
Reflect.get(target, propertyKey) 读取对象 target 的属性 propertyKey 对象的属性值读取操作
Reflect.set(target, propertyKey, value) 设置对象 target 的属性 propertyKey 的值为 value 对象的属性赋值操作
Reflect.has(target, propertyKey) 判断一个对象是否拥有一个属性 in 操作符
Reflect.defineProperty(target, propertyKey, attributes) 类似于 Object.defineProperty,不同的是如果配置出现问题,返回 false 而不是报错 Object.defineProperty
Reflect.deleteProperty(target, propertyKey) 删除一个对象的属性 delete 操作符
Reflect.apply(target, thisArgument, argumentsList) 调用一个指定的函数,并绑定 this 和参数列表 函数调用操作
Reflect.construct(target, argumentsList) 用构造函数的方式创建一个对象 new 操作符

其他更多的 Reflect API

3.Proxy

ECMAScript 6 新增的代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力

具体地说,可以给目标对象(target)定义一个关联的代理对象,而这个代理对象可当作一个抽象的目标对象来使用

因此在对目标对象的各种操作影响到目标对象之前,我们可以在代理对象中对这些操作加以控制,并且最终也可能会改变操作返回的结果。

所以我的理解是:代理(Proxy)能使我们开发者拥有一种间接修改底层方法的能力,从而控制用户的操作。

3.1 创建空代理

最简单的代理是空代理,即除了作为一个抽象的目标对象,什么也不做。 默认情况下,在代理对象上执行的所有操作都会无障碍地传播到目标对象。因此,在任何可以使用目标对象的地方,都可以通过同样的方式来使用与之关联的代理对象。

代理是使用 Proxy 构造函数创建的,这个构造函数接收两个参数:目标对象和处理程序对象。缺 少其中任何一个参数都会抛出 TypeError。返回一个代理对象 如:new Proxy(target, handler);

要创建空代理,可以传一个简单的对象字面量作为处理程序对象,从而让所有操作畅通无阻地抵达目标对象。

const target = {
 id: 'target'
}; //target:目标对象
const handler = {}; //handler:是一个普通对象,其中可以重写底层实现
//创建空对象
const proxy = new Proxy(target, handler);
// id 属性会访问同一个值
console.log(target.id); // target
console.log(proxy.id); // target
// 给目标属性赋值会反映在两个对象上 因为两个对象访问的是同一个值
target.id = 'foo';
console.log(target.id); // foo
console.log(proxy.id); // foo
// 给代理属性赋值会反映在两个对象上 因为这个赋值会转移到目标对象
proxy.id = 'bar';
console.log(target.id); // bar
console.log(proxy.id); // bar
// hasOwnProperty()方法在两个地方  也都会应用到目标对象
console.log(target.hasOwnProperty('id')); // true
console.log(proxy.hasOwnProperty('id')); // true
// Proxy.prototype 是 undefined   因此不能使用 instanceof 操作符
console.log(target instanceof Proxy); // TypeError: Function has non-object prototype
'undefined' in instanceof check
console.log(proxy instanceof Proxy); // TypeError: Function has non-object prototype
'undefined' in instanceof check
// 严格相等可以用来区分代理和目标
console.log(target === proxy); // false

3.2 定义捕获器

使用代理的主要目的是可以定义捕获器(trap)。捕获器就是在处理程序对象中定义的“基本操作的拦截器”。

每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接或间接在代理对象上调用。

每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。

所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为。比如,get()捕获器会接收到目标对象要查询的属性代理对象三个参数。

const target = {
  foo: "bar",
};
const handler = {
  // 捕获器在处理程序对象中以方法名为键
  get(trapTarget, property, receiver) {
    //trapTarget - 目标对象
    //property   - 要查询的属性
    //receiver   - 代理对象
    return "handler override";
  },
};
const proxy = new Proxy(target, handler);
console.log(target.foo); // bar
console.log(proxy.foo); // handler override

3.3 捕获器不变式

使用捕获器几乎可以改变所有基本方法的行为,但也不是没有限制。

根据 ECMAScript 规范,每个捕获的方法都知道目标对象上下文、捕获函数签名,而捕获处理程序的行为必须遵循“捕获器不变式”(trap invariant)。捕获器不变式因方法不同而异,但通常都会防止捕获器定义出现过于反常的行为。

比如,如果目标对象有一个不可配置且不可写的数据属性,那么在捕获器返回一个与该属性不同的值时,会抛出 TypeError:

const target = {};
Object.defineProperty(target, "foo", {
  configurable: false,
  writable: false,
  value: "bar",
});
const handler = {
  get() {
    return "qux";
  },
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // TypeError

3.4 可撤销代理

有时候可能需要中断代理对象与目标对象之间的联系。

Proxy 也暴露了 revocable()方法,这个方法支持撤销代理对象与目标对象的关联。

后续可直接调用撤销函数 revoke() 来撤销代理。

撤销代理之后再调用代理会抛出 TypeError,撤销函数和代理对象是在实例化时同时生成的:

const target = {
  foo: "bar",
};
const handler = {
  get() {
    return "intercepted";
  },
};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.foo); // intercepted
console.log(target.foo); // bar
revoke();
console.log(proxy.foo); // TypeError

4.代理捕获器与反射方法

4.1 get()

get()捕获器会在获取属性值的操作中被调用。对应的反射 API 方法为 Reflect.get()

const myTarget = {};
const proxy = new Proxy(myTarget, {
  get(target, property, receiver) {
    console.log("get()");
    return Reflect.get(...arguments);
  },
});
proxy.foo; // 触发get()捕获器

返回值 返回值无限制。

拦截的操作

  • proxy.property
  • proxy[property]
  • Object.create(proxy)[property]
  • Reflect.get(proxy, property, receiver)

捕获器处理程序参数

  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。① - receiver:代理对象或继承代理对象的对象。

捕获器不变式 如果 target.property 不可写且不可配置,则处理程序返回的值必须与 target.property 匹配。 如果 target.property 不可配置且[[Get]]特性为 undefined,处理程序的返回值也必须是 undefined。

4.2 set()

set()捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
  set(target, property, value, receiver) {
    console.log("set()");
    return Reflect.set(...arguments);
  },
});
proxy.foo = "bar"; // 触发set()捕获器

返回值 返回 true 表示成功;返回 false 表示失败,严格模式下会抛出 TypeError。

拦截的操作

  • proxy.property = value
  • proxy[property] = value
  • Object.create(proxy)[property] = value
  • Reflect.set(proxy, property, value, receiver)

捕获器处理程序参数

  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
  • value:要赋给属性的值。
  • receiver:接收最初赋值的对象。

捕获器不变式 如果 target.property 不可写且不可配置,则不能修改目标属性的值。 如果 target.property 不可配置且[[Set]]特性为 undefined,则不能修改目标属性的值。 在严格模式下,处理程序中返回 false 会抛出 TypeError。

4.3 has()

has()捕获器会在 in 操作符中被调用。对应的反射 API 方法为 Reflect.has()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
  has(target, property) {
    console.log("has()");
    return Reflect.has(...arguments);
  },
});
"foo" in proxy; //触发 has()捕获器

返回值 has()必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。

拦截的操作

  • property in proxy
  • property in Object.create(proxy)
  • with(proxy) {(property);}
  • Reflect.has(proxy, property)

捕获器处理程序参数

  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。

捕获器不变式 如果 target.property 存在且不可配置,则处理程序必须返回 true。 如果 target.property 存在且目标对象不可扩展,则处理程序必须返回 true。

4.4 deleteProperty()

deleteProperty()捕获器会在 delete 操作符中被调用。对应的反射 API 方法为 Reflect.deleteProperty()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
  deleteProperty(target, property) {
    console.log("deleteProperty()");
    return Reflect.deleteProperty(...arguments);
  },
});
delete proxy.foo; // 触发deleteProperty()捕获器
  • 返回值 deleteProperty()必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。
  • 拦截的操作
    • delete proxy.property
    • delete proxy[property]
    • Reflect.deleteProperty(proxy, property)
  • 捕获器处理程序参数
    • target:目标对象。
    • property:引用的目标对象上的字符串键属性。
  • 捕获器不变式 如果自有的 target.property 存在且不可配置,则处理程序不能删除这个属性。

4.5 apply()

apply()捕获器会在调用函数时中被调用。对应的反射 API 方法为 Reflect.apply()。

const myTarget = () => {};
const proxy = new Proxy(myTarget, {
  apply(target, thisArg, ...argumentsList) {
    console.log("apply()");
    return Reflect.apply(...arguments);
  },
});
proxy(); // 触发apply()捕获器
  • 返回值 返回值无限制。
  • 拦截的操作
    • proxy(...argumentsList)
    • Function.prototype.apply(thisArg, argumentsList)
    • Function.prototype.call(thisArg, ...argumentsList)
    • Reflect.apply(target, thisArgument, argumentsList)
  • 捕获器处理程序参数
    • target:目标对象。
    • thisArg:调用函数时的 this 参数。
    • argumentsList:调用函数时的参数列表
  • 捕获器不变式 target 必须是一个函数对象。

4.6 construct()

construct()捕获器会在 new 操作符中被调用。对应的反射 API 方法为 Reflect.construct()。

const myTarget = function () {};
const proxy = new Proxy(myTarget, {
  construct(target, argumentsList, newTarget) {
    console.log("construct()");
    return Reflect.construct(...arguments);
  },
});
new proxy(); // 触发construct()捕获器
  • 返回值 construct()必须返回一个对象。
  • 拦截的操作
    • new proxy(...argumentsList)
    • Reflect.construct(target, argumentsList, newTarget)
  • 捕获器处理程序参数
    • target:目标构造函数
    • argumentsList:传给目标构造函数的参数列表。
    • newTarget:最初被调用的构造函数。
  • 捕获器不变式 target 必须可以用作构造函数。

还有另外七种捕获器:

  • defineProperty()捕获器会在 Object.defineProperty()中被调用。
  • getOwnPropertyDescriptor()捕获器会在 Object.getOwnPropertyDescriptor()中被调 用。
  • ownKeys()捕获器会在 Object.keys()及类似方法中被调用。
  • getPrototypeOf()捕获器会在 Object.getPrototypeOf()中被调用。
  • setPrototypeOf()捕获器会在 Object.setPrototypeOf()中被调用。
  • isExtensible()捕获器会在 Object.isExtensible()中被调用。
  • preventExtensions()捕获器会在 Object.preventExtensions()中被调用。

这七种捕获器详细介绍可参考MDN - Proxy

参考博客

《Javascript 高级程序设计(第 4 版)》(JS 红宝书)

MDN - Proxy

MDN - Reflect

以上就是JS 中Proxy代理和 Reflect反射方法示例详解的详细内容,更多关于JS Proxy代理Reflect反射的资料请关注我们其它相关文章!

(0)

相关推荐

  • JS代理对象Proxy初体验简单的数据驱动视图

    目录 引言 Proxy对象是什么 使用Proxy写一个简单的数据驱动视图 引言 上大学的时候,最流行的框架是JQuery,它是事件驱动类型的,也就是说,当一个数据与DOM的某个内容相关联的时候,我需要在这个数据改变之后,去操作DOM来进行同步: <div id="data">数据</div> var data = "数据" // 通过某种事件数据发生了变化 data = "新数据" $("#data")

  • JavaScript Object.defineProperty与proxy代理模式的使用详细分析

    目录 1. Object.defineProperty 2. Object.defineProperties 3. proxy 代理模式 总结 1. Object.defineProperty const obj = {}; Object.defineProperty(obj,prop,descript); 参数 obj 要定义属性的对象 prop 要定义的属性 descript 属性描述符 返回值:被传递给函数的对象 descript接收一个对象作为配置: writable 属性是否可写 默认

  • node.js使用 http-proxy 创建代理服务器操作示例

    本文实例讲述了node.js使用 http-proxy 创建代理服务器操作.分享给大家供大家参考,具体如下: 代理,也称网络代理,是一种特殊网络服务,允许一个终端通过代理服务与另一个终端进行非直接的连接,这样利于安全和防止被攻击. 代理服务器,就是代理网络用户去获取网络信息,就是信息的中转,负责转发. 代理又分 正向代理 和 反向代理: 正向代理:帮助局域网内的用户访问外面的服务. 反向代理:帮助外面的用户访问局域网内部的服务. 一.安装 http-proxy npm install http-

  • 详解nodejs通过代理(proxy)发送http请求(request)

    有可能有这样的需求,需要node作为web服务器通过另外一台http/https代理服务器发http或者https请求,废话不多说直接上代码大家都懂的: var http = require('http') var opt = { host:'这里放代理服务器的ip或者域名', port:'这里放代理服务器的端口号', method:'POST',//这里是发送的方法 path:' https://www.google.com', //这里是访问的路径 headers:{ //这里放期望发送出去

  • nuxt.js服务端渲染中axios和proxy代理的配置操作

    需要npm axios? 刚开始,我以为需要像普通的vue SPA开发那样,需要npm axios,这种方式的确可以生效.但在使用时并不方便.尤其是设置代理比较麻烦,而且在asyncData里与在普通methods里使用方式不一样. 后来在nuxt的github上发现了nuxt是默认集成了axios的,所以不需要npm axios,但是需要进行适当的配置. 以上是百度到的,发现老是报错,现在网上的教程完全是在扯淡,npm axios 是不需要安装了,但是 @nuxtjs/axios 要安装啊 第

  • 使用nodejs中httpProxy代理时候出现404异常的解决方法

    在公司中使用nodejs构建代理服务器实现前后台分离,代码不能拿出来,然后出现httpProxy代理资源的时候老是出现404.明明被代理的接口是存在的.代码大概如下: var http = require('http'), httpProxy = require('http-proxy'); var proxy = httpProxy.createProxyServer({}); var server = http.createServer(function(req, res) { proxy.

  • .NET 中配置从xml转向json方法示例详解

    目录 一.配置概述 二.配置初识 三.选项模式 四.选项依赖注入 五.其它配置 六.托管模式 一.配置概述 在.net framework平台中我们常见的也是最熟悉的就是.config文件作为配置,控制台桌面程序是App.config,Web就是web.config,里面的配置格式为xml格式. 在xml里面有系统生成的配置项,也有我们自己添加的一些配置,最常用的就是appSettings节点,用来配置数据库连接和参数. 使用的话就引用包System.Configuration.Configur

  • node.js中fs文件系统模块的使用方法实例详解

    本文实例讲述了node.js中fs文件系统模块的使用方法.分享给大家供大家参考,具体如下: node.js中为我们提供了fs文件系统模块,实现对文件或目录的创建,修改和删除等操作. fs模块中,所有的方法分为同步和异步两种实现. 有 sync 后缀的方法为同步方法,没有 sync 后缀的方法为异步方法. 一.文件的整个读取 const fs = require('fs'); //参数一表示读取的文件 //参数二表示读取的配置,{encoding:'null', flag:'r'} //encod

  • JS中异常抛出和处理方法图文详解

    目录 抛出异常 抛出的表达式类型 基本数据类型 对象 类的实例对象 Error 类的实例对象 Error 的子类 处理异常 js中常见的系统异常: 总结 抛出异常 在 js 中,有时候我们需要处理一些异常或错误.比如编写的某个函数所接收的参数要求是 Number 类型的,如果在该函数被调用时传入的是字符串,就需要发出提醒.此时我们可以使用 throw 语句来抛出个异常: // 例 1 function fn(num) { if (typeof num !== 'number') throw '需

  • vue.js中methods watch和computed的区别示例详解

    目录 前言 介绍 一.作用机制上 二.从性质上 三.watch和computed的对比 四.methods不处理数据逻辑关系,只提供可调用的函数 五.从功能的互补上看待methods,watch和computed的关系 六.利用computed处理watch在特定情况下代码冗余的现象,简化代码 总结 computed watch 前言 这篇文章主要简述vue中的watch和computer区别,还有methods 首先,先说一下这几个不同在哪里,那当然是长得不一样啦~~~, 哈哈哈哈哈不开玩笑了

  • PHP中替换键名的简易方法示例详解

    YII框架中封装好了的数据库操作函数,默认输出的时候,将数据库字段名作为数组的键名进行输出,但是有些时候带有键名的数据不能够满足未知情况下的操作,譬如:数据库数据导出为EXCEL等比较非正常的操作. 所以这边需要对数据库结果集进行解析,下面就是针对这种特殊情况的一个简单方法: 复制代码 代码如下: /** * @todo 针对YII 查询输出带有数据库表字段名键名进行优化EXCEL表格输出 * @todo 替换键名为0.1.2... * @param array $data * @return

  • Java中的反射机制示例详解

    目录 反射 什么是Class类 获取Class实例的三种方式 通过反射创建类对象 通过反射获取类属性.方法.构造器 更改访问权限和实例赋值 运用场景 反射 反射就是把Java类中的各个成分映射成一个个的Java对象.即在运行状态中,对于任意一个类,都能够知道这个类的所以属性和方法:对于任意一个对象,都能调用它的任意一个方法和属性.这种动态获取信息及动态调用对象方法的功能叫Java的反射机制 每一个Java程序执行必须通过编译.加载.链接和初始化四个阶段 1.编译:将.java.文件编译成字节码.

  • JS中的Replace()传入函数时的用法详解

    replace方法的语法是:stringObj.replace(rgExp, replaceText) 其中stringObj是字符串(string),reExp可以是正则表达式对象(RegExp)也可以是字符串(string),replaceText是替代查找到的字符串.. 废话不多说了,直接给大家贴代码了,具体代码如下所示: <script> var str = "a1ba2b"; var reg = /a.b/g; str = str.replace(reg,func

  • JS中获取 DOM 元素的绝对位置实例详解

    在操作页面滚动和动画时经常会获取 DOM 元素的绝对位置,例如 本文 左侧的悬浮导航,当页面滚动到它以前会正常地渲染到文档流中,当页面滚动超过了它的位置,就会始终悬浮在左侧. 本文会详述各种获取 DOM 元素绝对位置 的方法以及对应的兼容性.关于如何获取 DOM 元素高度和滚动高度,请参考视口的宽高与滚动高度 一文. 概述 这些是本文涉及的 API 对应的文档和标准,供查阅: API 用途 文档 标准 offsetTop 相对定位容器的位置 MDN CSSOM View Module clien

  • Go语言中的字符串处理方法示例详解

    1 概述 字符串,string,一串固定长度的字符连接起来的字符集合.Go语言的字符串是使用UTF-8编码的.UTF-8是Unicode的实现方式之一. Go语言原生支持字符串.使用双引号("")或反引号(``)定义. 双引号:"", 用于单行字符串. 反引号:``,用于定义多行字符串,内部会原样解析. 示例: // 单行 "心有猛虎,细嗅蔷薇" // 多行 ` 大风歌 大风起兮云飞扬. 威加海内兮归故乡. 安得猛士兮守四方! ` 字符串支持转义

  • vue 中动态绑定class 和 style的方法代码详解

    先列举一些例子 class="['content',{'radioModel':checkType}]" class="['siteAppListDirNode',{open:appitem.open==true}]" class="['portalCenterMenu',{showNav:!showHideNav,hideNav:showHideNav}]" class="{shortcutMenuShow:!showHideNav,

随机推荐