【ES6知识】 Reflect 与 Proxy

文章目录

    • 前言
    • [一、Proxy 代理对象](#一、Proxy 代理对象)
      • [1.1 基本应用](#1.1 基本应用)
      • [1.2 同一个拦截器函数,可以设置拦截多个操作:](#1.2 同一个拦截器函数,可以设置拦截多个操作:)
      • [1.3 Proxy 支持的拦截操作一览,一共 13 种:](#1.3 Proxy 支持的拦截操作一览,一共 13 种:)
    • [二、Reflect 对象](#二、Reflect 对象)
      • [2.1 基本使用](#2.1 基本使用)
      • [2.2 `Reflect`对象一共有 13 个静态方法](#2.2 Reflect对象一共有 13 个静态方法)
    • [三、使用 Proxy 实现观察者模式](#三、使用 Proxy 实现观察者模式)

前言

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
相关推荐
时光の尘7 分钟前
C语言菜鸟入门·关键字·float以及double的用法
运维·服务器·c语言·开发语言·stm32·单片机·c
以后不吃煲仔饭20 分钟前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师21 分钟前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
前端拾光者25 分钟前
利用D3.js实现数据可视化的简单示例
开发语言·javascript·信息可视化
程序猿阿伟27 分钟前
《C++ 实现区块链:区块时间戳的存储与验证机制解析》
开发语言·c++·区块链
傻啦嘿哟1 小时前
如何使用 Python 开发一个简单的文本数据转换为 Excel 工具
开发语言·python·excel
大数据编程之光1 小时前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink
初九之潜龙勿用1 小时前
C#校验画布签名图片是否为空白
开发语言·ui·c#·.net
Dola_Pan1 小时前
C语言:数组转换指针的时机
c语言·开发语言·算法
ExiFengs1 小时前
实际项目Java1.8流处理, Optional常见用法
java·开发语言·spring