正文

总所周知,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反射的资料请关注Devmax其它相关文章!

JS 中Proxy代理和 Reflect反射方法示例详解的更多相关文章

  1. html5 拖拽及用 js 实现拖拽功能的示例代码

    这篇文章主要介绍了html5 拖拽及用 js 实现拖拽,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. amaze ui 的使用详细教程

    这篇文章主要介绍了amaze ui 的使用详细教程,本文通过多种方法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. swift皮筋弹动发射飞机ios源码

    这是一个款采用swift实现的皮筋弹动发射飞机游戏源码,游戏源码比较详细,大家可以研究学习一下吧。

  4. Swift与Js通过WebView交互

    开发环境:Swfit2.3XCode8.2基础概念jscontext,jscontext是代表JS的执行环境,通过-evaluateScript:方法就可以执行一JS代码JSValue,JSValue封装了JS与ObjC中的对应的类型,以及调用JS的API等JSExport,JSExport是一个协议,遵守此协议,就可以定义我们自己的协议,在协议中声明的API都会在JS中暴露出来,才能调用Swif

  5. JSCore swift

    如果双方相互引用,会造成循环引用,而导致内存泄露。以上是Jscore的基本使用,比较简单

  6. Swift WKWebView的js调用swift

    最近项目需求,需要用到JavaScriptCore和WebKit,但是网上的资源有限,而且比较杂,都是一个博客复制另外一个博客,都没有去实际敲代码验证,下面给大家分享一下我的学习过程。

  7. Swift WKWebView的swift调用js

    不多说,直接上代码:在html里面要添加的的代码,显示swift传过去的参数:这样就实现了swift给js传参数和调用!

  8. 在 Swift 專案中使用 Javascript:編寫一個將 Markdown 轉為 HTML 的編輯器

    你有強烈的好奇心,希望在你的iOS專案中使用JavaScript。jscontext中的所有值都是JSValue對象,JSValue類用於表示任意類型的JavaScript值。因此,我們既需要寫Swift代碼也要寫JavaScript代碼。此外,我們還會在JavaScript中按照這個類的定義來創建一個對象并對其屬性進行賦值。從Swift中呼叫JavaScript就如介紹中所言,JavaScriptCore中最主要的角色就是jscontext類。一個jscontext對象是位於JavaScript環境和本

  9. swift - WKWebView JS 交互

    本文介绍WKWebView怎么与js交互,至于怎么用WKWebView这里就不介绍了HTML代码APP调JS代码结果JS给APP传参数首先注册你需要监听的js方法名2.继承WKScriptMessageHandler并重写userContentController方法,在该方法里接收JS传来的参数3.结果

  10. swift 开发UIWebView跟JS的交互

    前言作为小白的我,才开始入门IOS,选择了swift来进行入门学习,学习做着公司一个简单的小小项目,该项目需要进行跟H5进行交互,然后我就开始研究了UIWebView的使用,其实基本原理跟Android的一样,因为我是Android开发的,所以就顺水推舟了。))//这里设置你需要加载的地址}overridefuncdidReceiveMemoryWarning(){super.didReceiveMemoryWarning()//disposeofanyresourcesthatcanberecreate

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部