Front-End

浅析 Proxy 和 Reflect

2020-06-22  本文已影响0人  sphenginx

Proxy是ES6中提供的新的API,可以用来定义对象各种基本操作的自定义行为 (在文档中被称为traps,我觉得可以理解为一个针对对象各种行为的钩子),拿它可以做很多有意思的事情,在我们需要对一些对象的行为进行控制时可以使用 Reflect 将变得非常有效。

定义

ProxyReflect 都属于 JavaScript 标准内置对象 的 反射 分类,不同的是:

Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。

Proxy 是 一个 function

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

Reflect 是 一个对象

术语

包含捕捉器(trap)的占位符对象,可译为处理器对象。

提供属性访问的方法。这类似于操作系统中捕获器的概念。

被 Proxy 代理虚拟化的对象。它常被作为代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。

注:

被代理的对象, 都是浅拷贝(及传址)。

语法

const p = new Proxy(target, handler)

参数

适用场景

const handler = {
    get(obj, prop) {
        return prop in obj ? Reflect.get(obj, prop) : 37;  
    }
};

const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b);      // 1, undefined
console.log('c' in p, p.c); // false, 37

通过 反射对象 来获取属性,如果没有则返回 37

let target = {};
let p = new Proxy(target, {});

p.a = 37;   // 操作转发到目标

console.log(target.a);    // 37. 操作已经被正确地转发

通过代理,你可以轻松地验证向一个对象的传值。下面的代码借此展示了 set handler 的作用。

let validator = {
  set(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');
      }
    }
    // 利用反射类来设置属性
    Reflect.set(obj, prop, value);
    // 表示成功
    return true;
  }
};

let person = new Proxy({}, validator);

person.age = 100;

console.log(person.age); 
// 100

person.age = 'young'; 
// 抛出异常: Uncaught TypeError: The age is not an integer

person.age = 300; 
// 抛出异常: Uncaught RangeError: The age seems invalid

方法代理可以轻松地通过一个新构造函数来扩展一个已有的构造函数。这个例子使用了constructapply

function extend(sup, base) {
  var descriptor = Object.getOwnPropertyDescriptor(
    base.prototype, "constructor"
  );
  base.prototype = Object.create(sup.prototype);
  var handler = {
    construct(target, args) {
      var obj = Object.create(base.prototype);
      this.apply(target, obj, args);
      return obj;
    },
    apply(target, that, args) {
      sup.apply(that, args);
      base.apply(that, args);
    }
  };
  var proxy = new Proxy(base, handler);
  descriptor.value = proxy;
  Object.defineProperty(base.prototype, "constructor", descriptor);
  return proxy;
}

var Person = function (name) {
  this.name = name
};

var Boy = extend(Person, function (name, age) {
  this.age = age;
});

Boy.prototype.sex = "M";

var Peter = new Boy("Peter", 13);
console.log(Peter.sex);  // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age);  // 13

解决对象属性为undefined的问题

在实际应用中,我们调用对象的多级属性时,当属性的父级不存在时经常会报 VM241:2 Uncaught TypeError: Cannot read property 'c' of undefined 的错误,如下:

VM241:2 Uncaught TypeError: Cannot read property 'c' of undefined

那么,我们可以使用 递归的Proxy 来代理 traps 的 get 方法, 来解决这个问题:

let handlers = {
    get (target, property) {
        if (Reflect.has(target, property)) {
            return Reflect.get(target, property);
        } else {
            if (target === undefined) {
                target = {};
            }
            if (target[property] === undefined) {
                target[property] = {};
            }
            return new Proxy(target[property], handlers);
        }
    }
}
let a = {};
let proxy = new Proxy(a, handlers)
console.log(proxy.a.b.c);

结果如下:

proxy.a.b.c

可以看到,并未报错,而是返回了一个 Proxy 的实例对象。

他山之石

上一篇下一篇

猜你喜欢

热点阅读