重学JavaScript[5] Proxy

6/19/2022 JavaScriptProxy

在2022已经过了一半的时候重新捡起来这个系列...

# 代理

说到Proxy,联想到它的中文意思则是”代理“

image-20220619193522970

何为代理?

  1. 小伙伴叫你出去玩,但你妈却告诉小伙伴”你在学习,不想出去玩“
  2. 大过年的,王阿姨给了你压岁钱¥500,但递交给你时被你妈装进了口袋

这两个例子中,”你妈“就充当了代理的作用,干涉你与外界的沟通

# Proxy

简单来说,Proxy只是挂在 globalThis 上的一个普通对象(也是个构造函数)

const you = { said: '我想出去玩' };
const yourMother = new Proxy(you, { get() { return '小明在学习,他不想出去玩' } });

console.log(you.said); // 我想出去玩
console.log(yourMother.said); // 小明在学习,他不想出去玩

当你在浏览器输入上述代码后,再查看 youyourMother 这两个变量,发现 yourMother 包含了[[Handler]]和[[Target]],这是一个被Proxy包装过的对象

# 用法

看看API

const p = new Proxy(target, handler);

其中

  • target 是需要使用Proxy包装的目标对象(包括数组、函数)

  • handler 也是一个对象,是Proxy操作的重头戏

hander可以包含以下属性

get, set, has, apply, construct, ownKeys, defineProperty ... (这里就不全列出来了,感兴趣可以去MDN (opens new window)上瞅瞅全部的13种拦截器)

这些属性分别可以对对象的属性读属性写in运算符函数调用new调用等操作进行拦截

在上面的”你妈不让你出去玩“惨剧中,你妈只是实现了”控制’,实际上,她也可以不改变你的行为,仅对你实施“监听”

const you = {}; // 你在感情方面是一张白纸
const yourMother = new Proxy(you, {
  set(_, property, value) {
    console.log(`有人给我儿子的${property}说:${value}`);
  },
});
const ruhua = { name: '如花', love: p => (p.love = '我爱你') };

ruhua.love(yourMother); // 有人给我儿子的love说:我爱你

如花给你表白时,你妈将得知

当然,上面的代码还是有问题的,因为在配置 set 拦截器中,你妈拦截了如花对你的表白

// 运行了上面的代码后...
console.log(you); // {}
// 你的感情仍然是一张白纸

因此,如果想要不干涉你的感情,你妈应该在控制台悄悄记录后 return Reflect.set(...arguments);

扯远了...

如果想了解关于Reflect的事情,参考这篇 Reflect (还没写)

# 应用

Proxy 的应用绝非只是实现一个控制欲极强的妈妈这么简单

对代理模式、观察者模式和实现响应式与数据绑定来说,Proxy都是极佳的选择

  • Vue3.x 里大量使用了Proxy来进行依赖收集、实现 computedwatch

  • 一段无聊的代码: 来源 (opens new window)

    const www = new Proxy(new URL('https://www'), {
        get: function get(target, prop) {
            let o = Reflect.get(target, prop);
            if (typeof o === 'function') {
                return o.bind(target)
            }
            if (typeof prop !== 'string') {
                return o;
            }
            if (prop === 'then') {
                return Promise.prototype.then.bind(fetch(target));
            }
            target = new URL(target);
            target.hostname += `.${prop}`;
            return new Proxy(target, { get });
        }
    });
    
    www.baidu.com.then(console.log) // 试试看