了解学习 Proxy 的好朋友 – Reflect

“这是我参与更文挑战的第7天,活动详情查看: 更文挑战

上文一个Javascript 代理 Proxy 的成长之旅,我们简单认识了一下 Proxy 的使用方法,本文我们学习一下 Proxy 的好朋友 Reflect

初识Reflect

Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of proxy handlers. Reflect is not a function object, so it’s not constructible.

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

主要特征:

  • 不可构造,不能使用 new 进行调用
  • 所有方法和 Proxy handlers 相同
  • 所有的方法都是静态方法,类似于 Math
  • 很多方法和 Ojbect 相同,但行为略微有所区别。譬如 Object.defineProperty(obj, name, desc) 在无法定义属性时,会抛出一个错误,而 Reflect.defineProperty(obj, name, desc) 则会返回false

静态方法列表:

Reflect对象一共有 13 个静态方法(匹配Proxy的13种拦截行为)。

  • 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.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.ownKeys(target)

具体参见 MDN 文档-Reflect

示例用法

  • 独立使用 Reflect.get + Reflect.has + Reflect.ownKeys + Reflect.set
const duck = {
    name: 'Maurice',
    color: 'white',
    greeting: function() {
        console.log(`Quaaaack! My name is ${this.name}`);
    }
}
Reflect.get(duck, 'name'); // => "Maurice"

Reflect.has(duck, 'color'); // => true

Reflect.has(duck, 'haircut'); // => false

Reflect.ownKeys(duck); // => [ "name", "color", "greeting" ]

Reflect.set(duck, 'eyes', 'black');

console.log(duck.eyes); // => black

复制代码
  • Proxy 内的 Reflect.get + Reflect.has
   let hero = {
        name: '张三',
    }
    let handler = {
        get(target, name) {
            return Reflect.get(target, name);
        },
        ,
        has(target, name) {
            return Reflect.has(target, name);
        }
    }
    let heroProxy = new Proxy(hero, handler);

    console.log(heroProxy.name); // => 张三
    console.log('name' in heroProxy); // => true
复制代码

简写 Reflect 的调用

在 Proxy handler 内部, 因为所有的参数都一致,所以我们可以用...arguments 简化调用。

...
const handler = {
    get(target, prop, receiver) {
      return Reflect.get(...arguments);
    }
}
...
复制代码

为什么需要 Reflect

正确的上下文引用

在上面的案例中,我们其实换传统方法实现,是完全木有问题的,具体如下:

let hero = {
    name: '张三',
}
let handler = {
    get(target, name) {
        return target[name];
        // return Reflect.get(target, name);
    },
    has(target, name) {
        return name in target;
        // return Reflect.has(target, name);
    }
}
let heroProxy = new Proxy(hero, handler);

console.log(heroProxy.name); // => 张三
console.log('name' in heroProxy); // => true

复制代码

那什么时候需要 Reflect 呢。我们先看一个复杂的例子。

let user = {
    _name: "张三",
    get name() {
        return this._name;
    }
};

let userProxy = new Proxy(user, {
    get(target, prop, receiver) {
        return Reflect.get(target, prop);
        // return target[prop]; // (*) target = user
    }
});

let admin = {
    __proto__: userProxy,
    _name: "李四"
};

// 期待 『李四』,却输出了 『张三』(?!?)
console.log(admin.name); // => 张三

复制代码

上述的情况可以看到无论是使用 Reflect.get(target, prop) 还是 target[prop], 都是张三。很神奇的表现,对吗。那我们该怎么才能按预期的输出李四呢。

如何在这种情况下,正确的传递上下文,是个问题。如果是普通的函数的话,我们还可以通过 call/apply,但在这里我们是 getter,而不是调用。

优秀的你一定看到了 get(target, prop, receiver) 有第三个参数没有使用,而且我们也知道 Reflect 的方法的参数是和 Proxy handler 一致的,那我们试试将receiver传递进入Reflect.get,看修改以后的效果。

let user = {
    _name: "张三",
    get name() {
        return this._name;
    }
};

let userProxy = new Proxy(user, {
    get(target, prop, receiver) { // receiver = admin
        return Reflect.get(target, prop, receiver);
    }
});

let admin = {
    __proto__: userProxy,
    _name: "李四"
};

console.log(admin.name); // => 李四

复制代码

It is OK now。 开心。可以看到第三个参数 receiver 保持了正确的 this 引用,在我们的示例中,指向了 admin

如果没有 Reflect.get 这个 API(配合第三个参数)的话,我们就将束手无策了。

在复杂的使用场景保持正确的上下文,这是 Reflect 一系列 API的一个重要意义所在。

和 Object 的方法略有区别,丰富使用场景

和 Object 的一系列方法略微有所差异,丰富了使用场景,这是第二个意义。

区别具体参见 比较 Reflect 和 Object 方法

正因为有所区别,所以我们可以根据具体的场景使用相应的 API 。

譬如 defineProperty, 因为 Object.defineProperty 可能抛错,那么在不希望 catch 的场景,那就只能用 Reflect.defineProperty

感谢

通过以上,我想我们大致理解学习了 Reflect 的基础并了解了它的使用场景了。

本文参考或引用了以下文章,一并致谢。

参考:

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享