import{_ as s,o as n,c as a,a as l}from"./app.94d5b31a.js";const i=JSON.parse('{"title":"深入理解Proxy与Reflect","description":"","frontmatter":{},"headers":[{"level":3,"title":"监听对象的操作","slug":"监听对象的操作","link":"#监听对象的操作","children":[]},{"level":3,"title":"Proxy类基本使用","slug":"proxy类基本使用","link":"#proxy类基本使用","children":[]},{"level":3,"title":"捕获器","slug":"捕获器","link":"#捕获器","children":[]},{"level":3,"title":"Reflect","slug":"reflect","link":"#reflect","children":[]},{"level":3,"title":"与Object的区别","slug":"与object的区别","link":"#与object的区别","children":[]},{"level":3,"title":"Reflect常见方法","slug":"reflect常见方法","link":"#reflect常见方法","children":[]},{"level":3,"title":"Reflect.construct方法","slug":"reflect-construct方法","link":"#reflect-construct方法","children":[]}],"relativePath":"article/深入理解Proxy与Reflect.md","lastUpdated":1676029882000}'),p={name:"article/深入理解Proxy与Reflect.md"},o=l(`

深入理解Proxy与Reflect

监听对象的操作

可以使用Proxy对象将原对象包裹,此后的操作都对proxy进行,每次getset被触发时都会自动执行相应代码

js
const obj = {
  name: 'ziu',
  age: 18,
  height: 1.88
}
const proxy = new Proxy(obj, {
  get(target, key) {
    console.log('get', key)
    return target[key]
  },
  set(target, key, value) {
    console.log('set', key, value)
    target[key] = value
  }
})
js
const tmp = proxy.height // getter被触发
proxy.name = 'Ziu' // setter被触发

除此之外,在之前的版本中可以通过Object.defineProperty为对象中某个属性设置gettersetter函数,可以达到类似的效果

js
for (const key of Object.keys(obj)) {
  let value = obj[key]
  Object.defineProperty(obj, key, {
    get() {
      console.log('get', value)
      return value
    },
    set(newVal) {
      console.log('set', key, newVal)
      value = newVal
    }
  })
}

但是通过Object.defineProperty实现的监听存在问题:

Proxy类基本使用

JS
const proxy = new Proxy(target, handler)

即使不传入handler,默认也会进行基本的代理操作

js
const obj = {
  name: 'ziu',
  age: 18
}
const proxy = new Proxy(obj, {})
proxy.height = 1.88 // 添加新属性
proxy.name = 'Ziu' // 修改原属性

console.log(obj) // { name: 'Ziu', age: 18, height: 1.88 }

捕获器

常用的捕获器有setget函数

js
const proxy = new Proxy(obj, {
  set: function (target, key, newVal) {
    console.log(\`监听: \${key} 设置 \${newVal}\`)
    target[key] = newVal
  },
  get: function (target, key) {
    console.log(\`监听: \${key} 获取\`)
    return target[key]
  }
})

另外介绍两个捕获器:hasdeleteProperty

js
const proxy = new Proxy(obj, {
  ...
  has: function (target, key) {
    console.log(\`监听: \${key} 判断\`)
    return key in target
  },
  deleteProperty: function (target, key) {
    console.log(\`监听: \${key} 删除 \`)
    return true
  }
})

delete proxy.name // 监听: name 删除
console.log('age' in proxy) // 监听: age 判断

Reflect

Reflect是ES6新增的一个API,它本身是一个对象

如果我们又Object对象可以完成这些操作,为什么还需要Reflect呢?

与Object的区别

删除对象上的某个属性

js
const obj = {
  name: 'ziu',
  age: 18
}
// 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变
// 同时该属性也能从对应的对象上被删除。 默认为 false。
Object.defineProperty(obj, 'name', {
  configurable: false
})

// 1. 旧方法 检查\`delete obj.name\`是否执行成功
// 结果: 需要额外编写检查代码且存在问题(严格模式下删除configurable为false的属性将报错)
delete obj.name
if (obj.name) {
  console.log(false)
} else {
  console.log(true)
}

// 2. Reflect
// 结果: 根据是否删除成功返回结果
if (Reflect.deleteProperty(obj, 'name')) {
  console.log(true)
} else {
  console.log(false)
}

Reflect常见方法

其中的方法与Proxy的方法是一一对应的,一共13个。其中的一些方法是Object对象中没有的:

代理对象的目的:不再直接操作原始对象,一切读写操作由代理完成。我们先前在编写Proxy的代理代码时,仍然有操作原对象的行为:

js
const proxy = new Proxy(obj, {
  set: function (target, key, newVal) {
    console.log(\`监听: \${key} 设置 \${newVal}\`)
    target[key] = newVal // 直接操作原对象
  },
})

这时我们可以让Reflect登场,代替我们对原对象进行操作,之前的代码可以修改:

js
const proxy = new Proxy(obj, {
  set: function (target, key, newVal) {
    console.log(\`监听: \${key} 设置 \${newVal}\`)
    Reflect.set(target, key, newVal)
  },
  get: function (target, key) {
    console.log(\`监听: \${key} 获取\`)
    return Reflect.get(target, key)
  },
  has: function (target, key) {
    console.log(\`监听: \${key} 判断\`)
    return Reflect.has(target, key)
  }
})

使用Reflect替代之前的对象操作有以下好处:

针对好处三,做出如下解释。以下述代码为例,set name(){}函数中的this指向的是obj

js
const obj = {
  _name: 'ziu',
  set name(newVal) {
    console.log(\`set name \${newVal}\`)
    console.log(this)
    this._name = newVal
  },
  get name() {
    console.log(\`get name\`)
    console.log(this)
    return this._name
  }
}

console.log(obj.name)
obj.name = 'Ziu'
js
const proxy = new Proxy(obj, {
  set: function (target, key, newVal, receiver) {
    console.log(\`监听: \${key} 设置 \${newVal}\`)
    Reflect.set(target, key, newVal, receiver)
  },
  get: function (target, key, receiver) {
    console.log(\`监听: \${key} 获取\`)
    return Reflect.get(target, key, receiver)
  }
})

我们使用Proxy代理,并且使用Reflect操作对象时,输出的this仍然为obj,需要注意的是,此处的this指向是默认指向原始对象obj,而如果业务需要改变this指向,此时可以为Reflect.set()的最后一个参数传入receiver

Reflect.construct方法

以下两段代码的实现结果是一样的:

js
function Person(name, age) {
  this.name = name
  this.age = age
}

function Student(name, age) {
  Person.call(this, name, age) // 借用
}

const stu = new Student('ziu', 18)
console.log(stu)
js
function Person(name, age) {
  this.name = name
  this.age = age
}

function Student(name, age) {
  // Person.call(this, name, age) // 借用
}

const stu = new Reflect.construct(Person, ['ziu', 18], Student)
console.log(stu)
`,44),e=[o];function c(t,r,y,D,F,A){return n(),a("div",null,e)}const b=s(p,[["render",c]]);export{i as __pageData,b as default};