【ES6知识】 Reflect 与 Proxy

2023-09-19 09:02:33

前言

Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。

  • Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。简单来说,就是这个对象所有的操作都交给代理对象来完成。

  • Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个:

    • Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。

    • Reflect 对象对某些方法的返回结果进行了修改,使其更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

原生JavaScipt案例合集
JavaScript +DOM基础
JavaScript 基础到高级
Canvas游戏开发

一、Proxy 代理对象

1.1 基本应用

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。简单来说,就是这个对象所有的操作都交给代理对象来完成。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例

var proxy = new Proxy(target, handler);
  • target 参数。表示所有拦截的目标对象
  • handler 参数。一个对象,用来定制拦截行为
let target = {
    name: 'Tom',
    age: 24
}
let handler = {
    get: function(target, key) {
        console.log('getting '+key);
        return target[key]; // 不是target.key
    },
    set: function(target, key, value) {
        console.log('setting '+key);
        target[key] = value;
    }
}
let proxy = new Proxy(target, handler)
proxy.name     // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set
// getting name
// setting age
// 25
 
// target 可以为空对象
let targetEpt = {}
let proxyEpt = new Proxy(targetEpt, handler)
// 调用 get 方法,此时目标对象为空,没有 name 属性
proxyEpt.name // getting name
// 调用 set 方法,向目标对象中添加了 name 属性
proxyEpt.name = 'Tom'
// setting name
// "Tom"
// 再次调用 get ,此时已经存在 name 属性
proxyEpt.name
// getting name
// "Tom"
 
// 通过构造函数新建实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相
// 影响
targetEpt
// {name: "Tom"}
 
// handler 对象也可以为空,相当于不设置拦截操作,直接访问目标对象
let targetEmpty = {}
let proxyEmpty = new Proxy(targetEmpty,{})
proxyEmpty.name = "Tom"
targetEmpty // {name: "Tom"}

1.2 同一个拦截器函数,可以设置拦截多个操作:

var handler = {
  get: function(target, name) {
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },

  apply: function(target, thisBinding, args) {
    return args[0];
  },

  construct: function(target, args) {
    return {value: args[1]};
  }
};

var fproxy = new Proxy(function(x, y) {
  return x + y;
}, handler);

fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true

1.3 Proxy 支持的拦截操作一览,一共 13 种:

  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.fooproxy['foo']

    target 参数。要拦截的目标对象

    propKey 参数。target 目标对象的属性名

    receiver 参数。表示原始操作行为所在对象,一般是 Proxy 实例本身。

    var person = {
      name: "张三"
    };
    
    var proxy = new Proxy(person, {
      get: function(target, propKey) {
        if (propKey in target) {
          return target[propKey];
        } else {
          throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
        }
      }
    });
    
    proxy.name // "张三"
    proxy.age // 抛出一个错误
    

    上面代码表示,如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回undefined

  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。

    target, propKey, receiver 参数。同 get。

    value 参数。对应属性 propKey 要设置的属性值

    let validator = {
        set: function(obj, prop, value) {
            if (prop === 'age') {
                if (!Number.isInteger(value)) {
                    throw new TypeError('The age is not an integer');
                }
                if (value > 200) {
                    throw new RangeError('The age seems invalid');
                }
            }
            // 对于满足条件的 age 属性以及其他属性,直接保存
            obj[prop] = value;
        }
    };
    let proxy= new Proxy({}, validator)
    proxy.age = 100;
    proxy.age           // 100
    proxy.age = 'oppps' // 报错
    proxy.age = 300     // 报错
    
  • has(target, propKey):用于拦截 HasProperty 操作,即在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截。

    let  handler = {
        has: function(target, propKey){
            console.log("handle has");
            return propKey in target;
        }
    }
    let exam = {name: "Tom"}
    let proxy = new Proxy(exam, handler)
    'name' in proxy
    // handle has
    // true
    
  • deleteProperty(target, propKey):用于拦截 delete 操作,如果这个方法抛出错误或者返回 false ,propKey 属性就无法被 delete 命令删除。

  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。

  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。

  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。

  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。

  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。

  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。

  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。

  • apply(target, ctx args):用于拦截函数的调用、call 和 reply 操作。target 表示目标对象,ctx 表示目标对象上下文,args 表示目标对象的参数数组。

    function sub(a, b){
        return a - b;
    }
    let handler = {
        apply: function(target, ctx, args){
            console.log('handle apply');
            return Reflect.apply(...arguments);
        }
    }
    let proxy = new Proxy(sub, handler)
    proxy(2, 1) 
    // handle apply
    // 1
    
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作(拦截 new 命令,返回值必须是一个对象),比如new proxy(...args)

    let handler = {
        construct: function (target, args, newTarget) {
            console.log('handle construct')
            return Reflect.construct(target, args, newTarget)  
        }
    }
    class Exam { 
        constructor (name) {  
            this.name = name 
        }
    }
    let ExamProxy = new Proxy(Exam, handler)
    let proxyObj = new ExamProxy('Tom')
    console.log(proxyObj)
    // handle construct
    // exam {name: "Tom"}
    

二、Reflect 对象

2.1 基本使用

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个:

  • Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。

  • Reflect 对象对某些方法的返回结果进行了修改,使其更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

    // 老写法
    try {
      Object.defineProperty(target, property, attributes);
      // success
    } catch (e) {
      // failure
    }
    
    // 新写法
    if (Reflect.defineProperty(target, property, attributes)) {
      // success
    } else {
      // failure
    }
    
  • Reflect 对象使用函数的方式实现了 Object 的命令式操作。比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

    // 老写法
    'assign' in Object // true
    
    // 新写法
    Reflect.has(Object, 'assign') // true
    
  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

    Proxy(target, {
      set: function(target, name, value, receiver) {
        var success = Reflect.set(target, name, value, receiver);
        if (success) {
          console.log('property ' + name + ' on ' + target + ' set to ' + value);
        }
        return success;
      }
    });
    

    上面代码中,Proxy方法拦截target对象的属性赋值行为。它采用Reflect.set方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。

    var loggedObj = new Proxy(obj, {
      get(target, name) {
        console.log('get', target, name);
        return Reflect.get(target, name);
      },
      deleteProperty(target, name) {
        console.log('delete' + name);
        return Reflect.deleteProperty(target, name);
      },
      has(target, name) {
        console.log('has' + name);
        return Reflect.has(target, name);
      }
    })
    

    上面代码中,每一个Proxy对象的拦截操作(getdeletehas),内部都调用对应的Reflect方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。

2.2 Reflect对象一共有 13 个静态方法

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

上面方法大部分与Object对象的同名方法作用相同。

Reflect 对象的方法与 Proxy 对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。

let exam = {
    name: "Tom",
    age: 24
}
let handler = {
    get: function(target, key){
        console.log("getting "+key);
        return Reflect.get(target,key);
    },
    set: function(target, key, value){
        console.log("setting "+key+" to "+value)
        Reflect.set(target, key, value);
    }
}
let proxy = new Proxy(exam, handler)
proxy.name = "Jerry"
proxy.name
// setting name to Jerry
// getting name
// "Jerry"

三、使用 Proxy 实现观察者模式

观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。

// 定义 Set 集合
const queuedObservers = new Set();
// 把观察者函数都放入 Set 集合中
const observe = fn => queuedObservers.add(fn);
// observable 返回原始对象的代理,拦截赋值操作
const observable = obj => new Proxy(obj, {set});
// 拦截函数set
function set(target, key, value, receiver) {
  // 获取对象的赋值操作
  const result = Reflect.set(target, key, value, receiver);
  // 执行所有观察者
  queuedObservers.forEach(observer => observer());
  // 执行赋值操作
  return result;
}

// 使用
const person = observable({
  name: '张三',
  age: 20
});

function print() {
  console.log(`${person.name}, ${person.age}`)
}

observe(print);
person.name = '李四';
// 输出
// 李四, 20

更多推荐

SpringMVC初级

文章目录一、SpringMVC概述二、springMVC步骤1、新建maven的web项目2、导入maven依赖3、创建controller4、创建spring-mvc.xml配置文件(本质就是spring的配置件)5、web.xml中配置前端控制器6、新建a.jsp文件7、配置tomcat8、启动测试三、工作流程分析

保密资质申报条件

一、保密资格等级及认定机关:保密资格分两个级别;一级:可承担一级(绝密级、机密级、秘密级)科研生产任务;二级:可承担二级(机密级、秘密级)科研生产任务;一级保密认定:国家保密委员会(国家保密局、国防科工局、装备发展部)、国防科工局。二级保密资格认定:省保密委员会(省保密局、省国防科工办受理)。注:2021年6月3日,国

Unity中的简单数据存储办法

这段代码演示了Unity中的简单数据存储办法当涉及到不同类型的存储时,下面是一些示例代码来演示在Unity中如何使用不同的存储方法:1.临时存储示例代码(内存变量):```csharp//定义一个静态变量来存储临时计分publicstaticintscore=0;//在某个事件触发时更新计分publicvoidUpda

腾讯Behaviac Designer 和Unity连调行为树

1.克隆源码https://github.com/Tencent/behaviac/2.编译生成BehaviacDesigner.exe3.找到并打开BehaviacDesigner.exe(先不急着填弹出的路径workspace设置框)4.新建一个Unity空工程,并在此处下载behaviacunitypackage

论文解读 | 基于视觉的水果采摘机器人识别与定位方法研究进展

原创|文BFT机器人01背景在复杂的农业环境中,利用机器视觉及其相关算法可以提高收割机器人的效率、功能性、智能化和远程互动性。对于水果采摘机器人系统来说,主要的挑战包括免提导航和水果定位,以及大多数果园中常见的崎岖地形和大型障碍物。这些挑战会在移动采摘机器人穿越地形时在其视觉系统中引起严重的振动,因此需要采用动态目标跟

信息安全性测试的流程

安全测试一、信息安全性测试的定义软件安全是一个广泛而复杂的主题,每一个新软件都可能存在安全的缺陷,甚至这个缺陷是前所未见的。信息安全性测试的目的在于通过系统的测试,对所测软件提出安全改进建议,帮助用户将风险控制/转移/降低在国家安全标准允许或公众接受的许可范围内。二、信息安全性测试的作用(1)为信息系统安全验收出具报告

让Pegasus天马座开发板实现超声波测距

在完成《让Pegasus天马座开发板用上OLED屏》后,我觉得可以把超声波测距功能也在Pegasus天马座开发板上实现。于是在箱子里找到了,Grove-UltrasonicRanger这一超声波测传感器。官方地址:https://wiki.seeedstudio.com/Grove-Ultrasonic_Ranger超

zabbix介绍及部署(五十一)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档目录一、zabbix的基本概述二、zabbix的构成1、Server2、web页面3、数据库4、proxy5、Agent三、zabbix的监控对象四、zabbix的常用术语五、zabbix的工作流程六、zabbix进程详解七、zabbix的监控框架7.1三

Blender骨骼动画简明教程

Blender是首选的开源3D动画软件之一。令人惊讶的是,开始创建简单的角色动画并不需要太多时间。一旦获得最终的3D角色模型,你就可以使用该软件的众多动画功能和工具将其变为现实。推荐:用NSDT编辑器快速搭建可编程3D场景例如,Blender的绑定工具将帮助你实现角色所需的动作。还可以使用软件的姿势编辑功能添加和操作姿

【Vue】el 和 data短小精湛的细节!

hello,我是小索奇,精心制作的Vue教程持续更新哈,花费了大量的时间和精力,总结拓展了很多疑难点,想要学习&巩固&避坑就一起学习叭~el与data的两种写法el共有2种写法el表达式主要用来在模板中展示数据,它可以输出简单的变量值,也可以调用方法。语法是${表达式}创建Vue实例对象的时候配置el属性先创建Vue实

python学习--定义python中的类

创建类的语法类的组成类属性实例方法静态方法类方法classStudent:#Student为类的名称(类名)由一个或多个单词组成,每个单词首字母大写,其余小写native_place='吉林'#直接写在类的变量,称为类属性def_init_(self,name,age):self.name=name#self.name

热文推荐