前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解 ES6 Proxy

深入理解 ES6 Proxy

作者头像
Leophen
发布2021-06-22 22:28:33
5510
发布2021-06-22 22:28:33
举报
文章被收录于专栏:Web前端开发Web前端开发

Proxy

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

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

一、Basic Syntax —— 基本用法

let p = new Proxy(target, handler)

参数

含义

必选

target

用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)

Y

handler

一个对象,其属性是当执行一个操作时定义代理的行为的函数

Y

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

代码语言:javascript
复制
// 设 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

代码语言:javascript
复制
let o = {
  name: 'Faker',
  age: 20
}

console.log(o.name) // Faker
console.log(o.age) // 20
console.log(o.from) // undefined

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

① ES5 的做法

代码语言:javascript
复制
console.log(o.from || '') // ''

② ES6 的做法

代码语言:javascript
复制
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 的做法
代码语言:javascript
复制
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 的做法
代码语言:javascript
复制
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 就不让修改,没有这个属性则返回空字符串

代码语言:javascript
复制
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" ""
① 代码优化——去掉耦合,将验证函数抽离成一个验证函数
代码语言:javascript
复制
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" ""
② 代码优化——整理成一个组件
代码语言:javascript
复制
// 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、监控上报

代码语言:javascript
复制
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 互不相同
① 探索一
代码语言:javascript
复制
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
② 探索二
代码语言:javascript
复制
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
③ 探索三
代码语言:javascript
复制
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 就失效了,就起到了临时代理的作用。

代码语言:javascript
复制
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 能够监视到更多对象的操作,例如删除属性操作

代码语言:javascript
复制
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 对数组进行监视?
代码语言:javascript
复制
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 是以非侵入的方式监管了对象的读写

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-04-06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Proxy
  • 一、Basic Syntax —— 基本用法
    • ① ES5 的做法
      • ② ES6 的做法
      • 二、Schema Validation
        • 1、只读
          • ① ES5 的做法
          • ② ES6 的做法
        • 2、校验
          • ① 代码优化——去掉耦合,将验证函数抽离成一个验证函数
          • ② 代码优化——整理成一个组件
        • 3、监控上报
          • 4、唯一只读 id
            • ① 探索一
            • ② 探索二
            • ③ 探索三
        • 三、Revocable Proxies —— 撤销代理
        • 四、Proxy VS Object.defineProperty()
          • 1、proxy 监视的操作更广
            • 2、Proxy 更好的支持数组对象的监视
              • 如何使用 Proxy 对数组进行监视?
            • 3、Proxy 是以非侵入的方式监管了对象的读写
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档