JavaScript 代理的超能力

Vue.js

今天我们将会学习ECMAScript 6 代理。我们将会在本篇文章中涵盖以下所有内容:

  • 什么是代理
  • 代理在运转
  • 谁用代理
  • 用例和样例
  • 资源

我们开始吧 :)

什么是代理

如同MDN 网站陈述的那样:

代理对象能让你为另一个对象创建一个代理,该代理可以拦截和重新定义那个对象的基本操作。

他们通过说它创建了一个代理来解释什么是代理,这有点可笑。当然,他们并没有说错,但是我们可以简化这种说法,使得它可以更友好:

通过这个代理对象,你可以包装目标对象,并通过这样做,我们可以拦截和重新定义那个对象的基本操作。

基本上,这意味着我们将要获取一个对象,用一个 代理 包装它,这将允许我们创建一个”隐藏” 的后门,并控制对所需对象的所有访问。

小提示: 代理 也是一种软件设计模式,你绝对应该了解它(Wikipedia Link).

创建 代理 需要两个参数:

  • target: 你想包装的原始对象 (代理)
  • handler: 一个对象,该对象定义了哪些操作将会被拦截以及如何重新定义也可以称为“陷阱”的拦截操作。
const target = {
  message1: "hello",
  message2: "everyone"
};

const handler = {};

const proxy = new Proxy(target, handler);

大多数浏览器都支持代理,但是很少有不支持代理的旧浏览器(当然是IE),您可以查看完整列表 这里 。Google有一个 polyfill 用于代理,但是它不支持所有代理功能。

现在我们已经知道什么是 代理 了,接下来看下我们可以用它做什么。


数据操作代理

假想自己是银行或者一个多疑的老伴,我们想以通知的形式知晓每次访问银行账户时的余额情况。这里将使用最最简单的 handler 实现操作/捕获数据,详见:get

const bankAccount = {
  balance: 2020,
  name: 'Georgy Glezer'
};

const handler = {
   get: function(target, prop, receiver) {
    if (prop === 'balance') {
    console.log(`Current Balance Of: ${target.name} Is: ${target.balance}`);
    }

    return target[prop];
  }
};

const wrappedBankAcount = new Proxy(bankAccount, handler);

wrappedBankAcount.balance; // 获取余额

// 输出:
// Current Balance Of: Georgy Glezer Is: 2020
// 2020

在上面的示例中,我们定义了一个银行账户对象,其中包含了我的名字和 2020 年的余额。

handler 对象在这里实现了 get 操作的捕获,它被定义为一个函数,这个函数具有三个参数和 get 操作的返回值。

  • target: 正在访问的对象(我们包装的那个)
  • prop:在示例中被访问的属性 - 这里是 “balance”
  • receiver: 其他代理或从代理继承的对象

我们在函数中加了一个条件判断,如果属性名称是 “balance”,将会发一个通知(console.log),通知包含余额以及当前用户名,并返回访问属性 “balance”

正如输出所示,每当 “balance” 属性被访问,我们就可以通过设置了 get 方法的 Proxy 方便的收到访问通知。


接下来继续实现我们的银行构想,要求是每次有人从银行账户取钱时都能被通知到。另外还有一个限制是银行不允许负余额的存在。这次我们使用 set 来实现。

const bankAccount = {
    balance: 2020,
    name: 'Georgy Glezer'
};

const handler = {
    set: function (obj, prop, value) {
        console.log(`Current Balance: ${obj.balance}, New Balance: ${value}`);

        if (value < 0) {
            console.log(`We don't allow Negative Balance!`);
            return false;
        }
        obj[prop] = value;

        return true;
    }
};

const wrappedBankAcount = new Proxy(bankAccount, handler);

wrappedBankAcount.balance -= 2000; // 取钱
console.log(wrappedBankAcount.balance);

wrappedBankAcount.balance -= 50; // 取钱
console.log(wrappedBankAcount.balance);

// 输出:
// Current Balance: 2020, New Balance: 20
// 20
// Current Balance: 20, New Balance: -30
// We don't allow Negative Balance!
// 20

在上述示例中,我们在取钱后获知了当前余额和新余额,并且在新余额为负时终止取钱操作。

我们使用了 set 函数捕获写操作,如果更新操作成功返回 true,否则返回 false。此函数接受如下参数:

  • target: 正在访问的对象(我们包装的那个)
  • prop: 在示例中被访问的属性 - 这里是 “balance”
  • value: 将被更新的新值
  • receiver: 最初指向的对象,通常是 Proxy 对象本身。不过 set 处理函数除也能通过原型链或者其他方式间接调用,所以此参数也可能是别的对象。

可以看到这与 get 非常相似,不过只有一个额外的参数,用于告诉你新设置的值。

这两个处理函数是最常见的,如果想了解全部的操作,可以看下 这个

哪些项目使用代理

很多流行的库都会使用这项技术,比如:

除此之外还有更多的项目……其大多数都利用了强大的代理技术为我们提供了很棒的库。

范例和用法

我们已知代理可用于:

  • 实时记录(通知到银行)
  • 做验证(阻止负余额的更新)

缓存

这里将再次使用 get 操作符并将 “dollars” 属性分配给对象。一旦访问 “dollars” 属性,将会自动计算我们的余额对应多少美元。由于计算可能是一项非常耗时的操作,所以我们希望能尽可能命中缓存进而加速响应。

const bankAccount = {
    balance: 10,
    name: 'Georgy Glezer',
    get dollars() {
        console.log('Calculating Dollars');
        return this.balance *3.43008459;
    }
};

let cache = {
    currentBalance: null,
    currentValue: null
};

const handler = {
    get: function (obj, prop) {
        if (prop === 'dollars') {
            let value = cache.currentBalance !== obj.balance ? obj[prop] : cache.currentValue;

            cache.currentValue = value;
            cache.currentBalance = obj.balance;

            return value;
        }

        return obj[prop];
    }
};

const wrappedBankAcount = new Proxy(bankAccount, handler);

console.log(wrappedBankAcount.dollars);
console.log(wrappedBankAcount.dollars);
console.log(wrappedBankAcount.dollars);
console.log(wrappedBankAcount.dollars);

// 输出:
// Calculating Dollars
// 34.3008459
// 34.3008459
// 34.3008459
// 34.3008459

如示例中所见,我们创建了一个缓存对象,其中包含了当前的银行余额和同价值的美金余额。每当有人访问 “dollars” 属性时,我们将只进行第一次计算,然后将其缓存。

DOM 操作

如果希望每当余额发生变化时,都能实时更新屏幕上的信息。可以为此属性使用 set 操作符,然后每当该值发生变化时,都同步更新屏幕中的 DOM 元素。


const bankAccount = {
  balance: 2020,
  name: "Georgy Glezer",
  get text() {
    return `${this.name} Balance Is: ${this.balance}`;
  }
};

const objectWithDom = (object, domId) => {
  const handler = {
    set: function (obj, prop, value) {
      obj[prop] = value;

      document.getElementById(domId).innerHTML = obj.text;

      return true;
    }
  };

  return new Proxy(object, handler);
};

// 创建 dom 元素,其 id 为 bank-account
const wrappedBankAccount = objectWithDom(bankAccount, "bank-account");

wrappedBankAccount.balance = 26;
wrappedBankAccount.balance = 100000;

在这里我们创建了一个辅助函数以便存储 DOM 元素的 ID ,还在 set 操作符中添加了一行简单的代码更新 DOM 元素。超简单是吧?下面我们一起看下最终效果 :)

Vue.js

总结

总而言之,我们了解了 ECMAScript 6 的代理,包括如何使用它,以及使用它可以实现什么需求。我觉得代理是一个很强大的工具,我们可将其用于多种场景,并只选择适合业务的方法即可。:)

如果你喜欢这篇文章,欢迎一键三连哟 👏

参考资料

编码技能提升

欢迎加入我们社区!订阅我们的 YouTube 频道 或加入 Skilled.dev 编码面试小课

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://levelup.gitconnected.com/the-ama...

译文地址:https://learnku.com/vuejs/t/52142

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 1
cnguu

vue3的核心之一

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!