zoukankan      html  css  js  c++  java
  • 深入理解 ES6 Proxy

    Proxy

    ES6 标准中新增——Proxy(代理),只要有 “代理” 的诉求都可以考虑使用 Proxy 来实现,例如自定义一些常用行为如查找、赋值、枚举、函数调用等。

    代理类似租房找中介,而中介可以屏蔽原始信息。

    一、Basic Syntax —— 基本用法

    let p = new Proxy(target, handler)

    参数 含义 必选
    target 用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理) Y
    handler 一个对象,其属性是当执行一个操作时定义代理的行为的函数 Y

    第一个参数 target 就是用来代理的 “对象”,被代理之后它是不能直接被访问的,而 handler 就是实现代理的过程,举个例子:

    // 设 o 为房东,即被代理的对象
    let o = {
      name: 'Faker',
      price: 200
    }
    
    // 设 d 为中介,即代理
    let d = new Proxy(o, {})
    
    console.log(d.price, d.name) // 200 'Faker'
    
    // 因为传的是空对象,所以是透传
    
    let d = new Proxy(o, {
      get(target, key) { // target 是指被代理的对象 o,key 指的是 o 的属性
        // 如果 key 为价格时进行加 10 的操作,否则返回 key 的值本身
        if (key === 'price') {
          return target[key] + 10
        } else {
          return target[key]
        }
      }
    })
    
    console.log(d.price, d.name) // 210 "Faker"
    

    上述对代理后的 d 中 price 进行相应处理(中介报价在房东报价之上)

    再举个例子,当我们要读取一个对象中不存在的属性时,由于对象没有这个属性,所以会返回 undefined

    let o = {
      name: 'Faker',
      age: 20
    }
    
    console.log(o.name) // Faker
    console.log(o.age) // 20
    console.log(o.from) // undefined
    

    如果我们不想在调用的时候出现 undefined,可以这么处理:

    ① ES5 的做法

    console.log(o.from || '') // ''
    

    ② ES6 的做法

    let o = {
      name: 'Faker',
      age: 20
    }
    
    // 代理处理器
    let handler = {
      get(obj, key) {
        return Reflect.has(obj, key) ? obj[key] : ''
      }
    }
    
    let p = new Proxy(o, handler)
    
    console.log(p.from) // ''
    

    二、Schema Validation

    1、只读

    拿走备份,不影响原始数据

    ① ES5 的做法

    let o = {
      name: 'Faker',
      price: 200
    }
    
    for (let [key] of Object.entries(o)) {
      Object.defineProperty(o, key, {
        writable: false
      })
    }
    
    console.log(o.name, o.price)// Faker 200
    
    o.price = 300
    console.log(o.name, o.price)// Faker 200
    

    ② ES6 的做法

    let o = {
      name: 'Faker',
      price: 200
    }
    
    let d = new Proxy(o, {
      get(target, key) {
        return target[key]
      },
      set(target, key, value) {
        return false
      }
    })
    
    d.price = 300
    console.log(d.price, d.name) // 200 "Faker"
    

    ES5 做法和 ES6 代理的区别,在于 ES5 的全部锁死,而 ES6 中用户只读,但是代理可以做操作

    2、校验

    实现:如果价格 >300 就不让修改,没有这个属性则返回空字符串

    let o = {
      name: 'Faker',
      price: 200
    }
    
    let d = new Proxy(o, {
      get(target, key) {
        return target[key] || ''
      },
      set(target, key, value) {
        if (Reflect.has(target, key)) {
          if (key === 'price') {
            if (value > 300) {
              return false
            } else {
              target[key] = value
            }
          } else {
            target[key] = value
          }
        } else {
          return false
        }
      }
    })
    
    d.price = 240
    console.log(d.price, d.name)// 240 "Faker"
    
    d.price = 301 // 没有生效,因为校验没有通过
    d.name = 'Bang'
    console.log(d.price, d.name)// 240 "Bang"
    
    d.age = 23 // 没有这个属性,set 时候返回,get 的时候赋值为空字符串
    console.log(d.price, d.name, d.age)// 240 "Bang" ""
    

    ① 代码优化——去掉耦合,将验证函数抽离成一个验证函数

    let o = {
      name: 'Faker',
      price: 200
    }
    
    let validator = (target, key, value) => {
      if (Reflect.has(target, key)) {
        if (key === 'price') {
          if (value > 300) {
            return false
          } else {
            target[key] = value
          }
        } else {
          target[key] = value
        }
      } else {
        return false
      }
    }
    
    let d = new Proxy(o, {
      get(target, key) {
        return target[key] || ''
      },
      set: validator
    })
    
    d.price = 240
    console.log(d.price, d.name)// 240 "Faker"
    
    d.price = 301
    d.name = 'Bang'
    console.log(d.price, d.name)// 240 "Bang"
    
    d.age = 23
    console.log(d.price, d.name, d.age)// 240 "Bang" ""
    

    ② 代码优化——整理成一个组件

    // Validator.js
    export default (obj, key, value) => {
      if (Reflect.has(key) && value > 20) {
        obj[key] = value
      }
    }
    
    import Validator from './Validator'
    let data = new Proxy(response.data, {
      set: Validator
    })
    

    3、监控上报

    window.addEventListener('error', (e) => {
      console.log(e.message)
      // 上报
      // report('...')
    }, true) //捕获
    
    let o = {
      name: 'Faker',
      price: 200
    }
    
    let validator = (target, key, value) => {
      if (Reflect.has(target, key)) {
        if (key === 'price') {
          if (value > 300) {
            // 不满足要触发错误
            throw new TypeError('price exceed 300')
          } else {
            target[key] = value
          }
        } else {
          target[key] = value
        }
      } else {
        return false
      }
    }
    
    let d = new Proxy(o, {
      get(target, key) {
        return target[key] || ''
      },
      set: validator
    })
    
    d.price = 240
    console.log(d.price, d.name)// 240 "Faker"
    
    d.price = 301
    d.name = 'Bang'
    console.log(d.price, d.name)// 240 "Bang"
    
    d.age = 23
    console.log(d.price, d.name, d.age)// 240 "Bang" ""
    

    4、唯一只读 id

    实现:

    1. 每次生成一个 id
    2. 不可修改
    3. 每个实例的 id 互不相同

    ① 探索一

    class Component {
      constructor() {
        this.id = Math.random().toString(36).slice(-8)
      }
    }
    
    let com = new Component()
    let com2 = new Component()
    
    for (let i = 0; i < 10; i++) {
      console.log(com.id) // (10) 030nc7is
    }
    for (let i = 0; i < 10; i++) {
      console.log(com2.id) // (10) 772fqaup
    }
    
    
    // 这种方式可以每次生成一个id,但是可以修改,不符合要求
    com.id = 'abc'
    console.log(com.id, com2.id) // abc 93ukz26i
    

    ② 探索二

    class Component {
      get id() {
        return Math.random().toString(36).slice(-8)
      }
    }
    
    let com = new Component()
    let com2 = new Component()
    
    for (let i = 0; i < 10; i++) {
      console.log(com.id)
    }
    // nqwlamib
    // l9ojsjiq
    // gad3vm2a
    // i1jew3bd
    // owquntob
    // rcpce268
    // va6mry5v
    // lvqxv0m4
    // a900358x
    // jahi7079
    for (let i = 0; i < 10; i++) {
      console.log(com2.id)
    }
    // vukusf5k
    // rg8hyzf3
    // 50vxv0hk
    // tjeyes1v
    // 4g8zwsxz
    // 5r1cbx1k
    // v9k2v7hd
    // 0mgn3heb
    // n0zc9v66
    // rdjevl2i
    
    // 这种方式不可以修改,但是每此都生成了一个新的,不符合要求
    com.id = 'abc'
    console.log(com.id, com2.id) // 9rjmwrd9 kxdxtywe
    

    ③ 探索三

    class Component {
      constructor() {
        this.proxy = new Proxy({
          id: Math.random().toString(36).slice(-8)
        }, {})
      }
      get id() {
        return this.proxy.id
      }
    }
    
    let com = new Component()
    let com2 = new Component()
    
    for (let i = 0; i < 10; i++) {
      console.log(com.id) // (10)e9e8jsks
    }
    for (let i = 0; i < 10; i++) {
      console.log(com2.id) // (10)tfs2rrvg
    }
    
    // 满足要求
    com.id = 'abc'
    console.log(com.id, com2.id) // e9e8jsks tfs2rrvg
    

    三、Revocable Proxies —— 撤销代理

    除了常规代理,还可以创建临时代理,临时代理可以撤销。
    一旦 revoke 被调用,proxy 就失效了,就起到了临时代理的作用。

    let o = {
      name: 'Faker',
      price: 200
    }
    
    // 这里不能使用 new,只能使用 Proxy.revocable 去声明代理
    let d = Proxy.revocable(o, {
      get(target, key) {
        if (key === 'price') {
          return target[key] + 10
        } else {
          return target[key]
        }
      }
    })
    // d 里面包含了代理数据和撤销操作
    console.log(d.proxy.price) // 210
    console.log(d) // {proxy: Proxy, revoke: ƒ}
    
    setTimeout(function () {
      // 对代理进行撤销操作
      d.revoke()
      setTimeout(function () {
        console.log(d.proxy.price)
        // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
      }, 100)
    }, 1000)
    

    四、Proxy VS Object.defineProperty()

    如果想监听对象属性的改变,可以使用 Object.defineProperty 这个方法去添加属性,捕捉对象中属性的读写过程, Vue3之前的版本就是通过这个实现的数据双向绑定。

    Vue3 开始就使用 proxy 来实现内部响应了。

    proxy 是专门为对象设置代理器的,可以轻松监视到对象的读写过程。
    相比较 definePropertyproxy 的功能更强大,使用起来也更为方便,具体表现如下:

    1、proxy 监视的操作更广

    defineProperty 只能监视属性的读写,proxy 能够监视到更多对象的操作,例如删除属性操作

    const person = {
      name: 'Faker',
      age: 23
    }
    
    const personProxy = new Proxy(person, {
      deleteProperty(target, property) {
        console.log('delete ' + property) // delete age
        delete target[property]
      }
    })
    
    delete personProxy.age
    
    console.log(person) // { name: 'Faker' }
    
    handler ⽅法 触发⽅式
    get 读取某个属性
    set 写⼊某个属性
    has in 操作符
    deleteProperty delete 操作符
    getProperty Object.getPropertypeOf()
    setProperty Object.setPrototypeOf()
    isExtensible Object.isExtensible()
    preventExtensions Object.preventExtensions()
    getOwnPropertyDescriptor Object.getOwnPropertyDescriptor()
    defineProperty Object.defineProperty()
    ownKeys Object.keys() 、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()
    apply 调⽤⼀个函数
    construct ⽤ new 调⽤⼀个函数

    2、Proxy 更好的支持数组对象的监视

    Object.defineProperty() 使用的是重写数组的操作方法

    如何使用 Proxy 对数组进行监视?

    const list = []
    
    const listProxy = new Proxy(list, {
      set(target, property, value) {
        console.log('set', property, value)
        target[property] = value
        return true // 表示设置成功
      }
    })
    
    listProxy.push(100)
    // set 0 100
    // set length 1
    
    listProxy.push(200)
    // set 1 200
    // set length 2
    

    3、Proxy 是以非侵入的方式监管了对象的读写

  • 相关阅读:
    [leetcode]133. Clone Graph 克隆图
    [leetcode]366. Find Leaves of Binary Tree捡树叶
    [leetcode]311. Sparse Matrix Multiplication 稀疏矩阵相乘
    [leetcode]151. Reverse Words in a String翻转给定字符串中的单词
    [leetcode]150. Evaluate Reverse Polish Notation逆波兰表示法
    Union and Intersection of two sorted lists 并集和交集
    [leetcode]205. Isomorphic Strings 同构字符串
    [leetcode]428. Serialize and Deserialize N-ary Tree序列化与反序列化N叉树
    [leetcode]364. Nested List Weight Sum II嵌套列表加权和II
    属性 元素的内容 创建,插入和删除节点 虚拟节点
  • 原文地址:https://www.cnblogs.com/Leophen/p/14897044.html
Copyright © 2011-2022 走看看