RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1161902
Accepted
Ivy
Ivy
Asked:2020-08-04 17:15:58 +0000 UTC2020-08-04 17:15:58 +0000 UTC 2020-08-04 17:15:58 +0000 UTC

为什么在 JavaScript 中使用 Proxy 和 Reflect,有什么区别以及何时使用以及使用什么?

  • 772

代理有什么用,它们在实际开发中使用的频率如何?

反射也不清楚,事实证明它几乎是一样的。目前尚不清楚为什么两者同时存在。

javascript
  • 2 2 个回答
  • 10 Views

2 个回答

  • Voted
  1. Best Answer
    Vasily
    2020-08-08T00:43:16Z2020-08-08T00:43:16Z

    代理对象是“陷阱”,我们可以通过它从对象、类、函数等中拦截我们需要的“事件”。

    如果您完全不熟悉它们,那么我会说这与Browser API中的eventListners非常相似,因为在Proxy的帮助下,我们可以绑定并在必要时从上面列出的那些实体中拦截我们需要的事件.

    这样的陷阱是什么样子的:

    const address = {
      country: "Russia",
      city: "Moscow"
    }
    
    const addressProxy = new Proxy(address, {
      // здесь мы определяем какое именно действие
      // по объекту address мы хотим перехватить
    
      // например, мы можем перехватить тот момент
      // когда что-то пытается получить доступ
      // до одного из значений объекта по его ключу
    
      // для этого мы поставим ловушку/handler
      get: (target, prop) => {
        return target[prop]
      }
    })
    
    console.log(addressProxy.country)
    // Russia
    
    console.log(addressProxy.city)
    // Moscow
    
    console.log(addressProxy.street)
    // undefined
    

    现在我们得到了对象的值,但是我们是通过代理来完成的,所以我们有机会在放弃之前做任何有价值的事情:

    const addressProxy = new Proxy(address, {
      get: (target, prop) => {
          if (prop === "country") {
              return target[prop].slice(0, 2).toUpperCase()
          }
          return target[prop]
      }
    })
    
    console.log(addressProxy.country)
    // RU
    
    console.log(addressProxy.city)
    // Moscow
    
    console.log(addressProxy.street)
    // undefined
    

    也就是说,现在我们可以在“事件” get()上放置一个 eventListener,在它工作之后,我们拦截了对特定键的请求并将其值更改为我们需要的格式。

    现在我们可以安全地向我们的对象添加新键:

    addressProxy.street = "Arbat"
    
    console.log(addressProxy)
    // { country: "Russia", city: "Moscow", street: "Arbat" }
    

    但很容易通过绑定到“事件” set()来禁用它:

    const addressProxy = new Proxy(address, {
      set: (target, prop, value) => {
          return false
      }
    })
    
    addressProxy.street = "Arbat"
    
    console.log(city in addressProxy)
    // { country: "Russia", city: "Moscow" }
    

    我们还可以隐藏某些字段:

    addressProxy.metadata = 12345
    
    console.log(addressProxy)
    // { country: "Russia", city: "Moscow", metadata: 12345 }
    
    const addressProxy = new Proxy(address, {
      has: (target, prop) => {
          return prop in target && prop !== "metadata"
      }
    })
    

    如果现在我们“询问”是否存在这样的字段,我们会得到:

    console.log("country" in addressProxy)
    // true
    
    console.log("city" in addressProxy)
    // true
    
    console.log("metadata" in addressProxy)
    // false
    

    函数也可以被代理,例如,我们可以跟踪它被调用的时刻:

    const print = text => console.log(text)
    
    const printProxy = new Proxy(print, {
      apply: (target, thisArg, argArray) => {
          return target.apply(thisArg, argArray)
      }
    })
    
    printProxy("это тест, мы успешно перехватили вызов функции")
    // это тест, мы успешно перехватили вызов функции
    

    这将使我们无需任何额外的努力就可以实现以下逻辑:

    // давайте отфильтруем плохие слова
    // запретив нашей функции их вывод
    // и оставим только приятный слуху язык
    
    const print = text => console.log(text)
    
    const printProxy = new Proxy(print, {
      apply: (target, thisArg, argArray) => {
          // для простоты примера представим что 
          // в массиве запрещенных слов сейчас 
          // только одно слово
          const badWords = ["ругательство"]
          if (badWords.includes(argArray[0])) {
            return target("***")
          }
          return target(argArray[0])
      }
    })
    
    printProxy("спасибо")
    // спасибо
    
    printProxy("ругательство")
    // ***
    

    我想现在代理的主要思想已经变得更加清晰了。另外,我想指出,对于每个实体,都有特定于它们的处理程序,例如,对于函数,它是apply(),对于类(以及以new operator 开头的所有内容)construct()。

    可以在此处找到处理程序的完整列表。

    它们在实际开发中的使用频率如何?

    在大型现代项目中,经常使用代理,常见的应用领域之一是各种“优化”。

    假设我们有一组用户,我们需要通过 ID 找到我们需要的用户:

    const users = [
      { id: 1, name: "Иван" },
      { id: 2, name: "Мария" },
      { id: 3, name: "Антон" }
    ]
    
    // что бы найти пользователя с ID равным 3
    // нам нужно пройтись по всему массиву
    // и сверить ID у каждого пользователя
    // пока мы не найдем нужный
    
    const targetUser = users.find(user => user.id === 3)
    
    console.log(targetUser)
    // { id: 3, name: "Антон" }
    

    如果只有三个用户,原则上没有问题,但如果有 100,000 个用户,我们该怎么办?

    (这种体积的持续隔板将非常昂贵)

    const users = [
      { id: 1, name: "Иван" },
      { id: 2, name: "Мария" },
      { id: 3, name: "Антон" }
      // ...
      // и еще более чем
      // 100.000 записей
    ]
    

    我们可以代理Array类,并为其添加一个处理程序construct(),这将允许我们“附加”到每个新实例的初始化时刻。

    在其中,我们迭代我们的数组并为每个条目分配一个等于用户 ID 的索引:

    const IndexedArray = new Proxy(Array, {
      construct: () => {
        const index = {}
          users.forEach(item => index[item.id] = item)
          return index
      }
    })
    
    const indexedUsers = new IndexedArray(users)
    

    在创建新的 indexedArray 实例时,迭代将只执行一次:

    console.log(indexedUsers)
    // {
    //   "1": { id: 1, name: "Иван" },
    //   "2": { id: 2, name: "Мария" },
    //   "3": { id: 3, name: "Антон" }
    //   ... остальные 100.000
    // }
    

    之后,我们就可以尽可能简单地获取我们需要的用户:

    console.log(indexedUsers[3])
    // { id: 3, name: "Антон" }
    

    我特意简化了上面的例子,以便于理解主要思想,但是,为了保持完整的功能,包括添加/删除/更改字段等,它需要最终确定。


    代理和反射是标准的内置对象,但如果前者旨在“拦截”和“重写”被代理对象的基本操作,那么后者提供了处理“拦截”操作的方法。

    Reflect的所有方法和属性都是静态的,而对象本身是非函数的,也就是说它是不可构造的,我们不能和new操作符一起使用,也不能作为函数调用。

    Reflect 对象的函数名称与 Proxy 中的处理程序名称相同,其中一些重复了Object类的方法的功能,尽管有一些区别。

    为什么需要这个?

    这就是标准化。

    例如,apply() 方法存在于所有构造函数(许多实现)中,但在 Reflect 中删除(一种实现)。

    ESLint的默认设置已经表明,而不是:

    testFunction.apply(thisArg, argsArr)
    

    值得使用:

    Reflect.apply(testFunction, thisArg, argsArr)
    

    因此,例如,而不是写:

    const chinaAddress = new Address(argsArr)
    // sidenote
    // такой подход приведет к созданию 
    // и использованию итератора
    

    可以这样写:

    const chinaAddress = Reflect.construct(Address, argsArr)
    // sidenote
    // такой подход не потребует задействования итератора
    // поскольку construct() использует
    // length и прямой доступ
    // что в целом положительно повлияет на оптимизацию
    

    也就是说,通过选择 Reflect 而不是标准方法,我们只是在朝着现代 JavaScipt 前进的方向前进。

    • 14
  2. Grundy
    2020-08-04T18:13:34Z2020-08-04T18:13:34Z

    Proxy是一个构造函数,允许您对对象进行包装,您可以在其中覆盖标准行为,例如,访问对象属性

    var target = {
      a: 1
    };
    
    var handler = {
      get(target, propertyName, proxy) {
        return target.hasOwnProperty(propertyName) ? target[propertyName] : 42;
      }
    }
    
    
    var proxy = new Proxy(target, handler);
    
    console.log(proxy.a);
    console.log(proxy.b);
    console.log(target.b);

    正如您在示例中看到的,访问目标上不存在的属性将返回预定义的值,而不是undefined.


    Reflect是一个提供处理对象的方法的对象,其中一些方法是重复的Object,例如

    Object.defineProperty-Reflect.defineProperty

    以及复制某些运算符功能的方法,

    • new-Reflect.construct
    • delete-Reflect.deleteProperty 和其他人。

    因此,在这个对象中,我们决定收集处理对象内部的方法。


    Proxy积极使用Vuejs

    • 7

相关问题

  • 第二个 Instagram 按钮的 CSS 属性

  • 由于模糊,内容不可见

  • 弹出队列。消息显示不正确

  • 是否可以在 for 循环中插入提示?

  • 如何将 JSON 请求中的信息输出到数据表 Vuetify vue.js?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何从列表中打印最大元素(str 类型)的长度?

    • 2 个回答
  • Marko Smith

    如何在 PyQT5 中清除 QFrame 的内容

    • 1 个回答
  • Marko Smith

    如何将具有特定字符的字符串拆分为两个不同的列表?

    • 2 个回答
  • Marko Smith

    导航栏活动元素

    • 1 个回答
  • Marko Smith

    是否可以将文本放入数组中?[关闭]

    • 1 个回答
  • Marko Smith

    如何一次用多个分隔符拆分字符串?

    • 1 个回答
  • Marko Smith

    如何通过 ClassPath 创建 InputStream?

    • 2 个回答
  • Marko Smith

    在一个查询中连接多个表

    • 1 个回答
  • Marko Smith

    对列表列表中的所有值求和

    • 3 个回答
  • Marko Smith

    如何对齐 string.Format 中的列?

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5