Vue3源码reactivity响应式篇Reflect和Proxy详解

概览

ProxyReflect是都是在ES6中引入的新对象。它们也是 vue3 实现响应式的基础。这篇文章会深入理解分析这两个重要的角色。

正文

Proxy

Proxy对象用于创建一个对象的代理,从而实现对目标对象的访问拦截和修改(如属性查找、赋值、枚举和函数调用), 即修改这些操作的默认行为。

  • 语法:new Proxy(target, handler)

target: 需要代理的目标对象(可以是任何类型,包括原生对象)

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

handler实例方法

参照 MDN 文档,handler可以如下 13 种方法

  • handler.apply(target,thisArg,args)
    handler.apply用于拦截函数的调用、callapply操作,返回一个值。如果没有定义该方法,那么调用函数时会直接调用目标函数。

  • handler.construct(target,args)

    用于拦截构造函数new操作符的调用

  • handler.defineProperty(target,key,descriptor)
    handler.defineProperty用于拦截对象Object.defineProperty()操作,返回一个布尔值。如果返回false或者报错,则定义属性操作无效。

  • handler.deleteProperty(target,key)
    handler.deleteProperty用于拦截对对象属性的delete操作,返回一个布尔值。如果返回false或者报错,则删除操作无效。

  • handler.get(target,key,receiver)
    handler.get用于拦截对象某个属性的读取操作,返回该属性的值。如果属性不存在,则返回undefined。如果属性部署了getter,那么getterthis是指向第三个参数receiver,若参数receiver省略,则this默认指向target

  • handler.getOwnPropertyDescriptor(target,key)
    handler.getOwnPropertyDescriptor用于拦截Object.getOwnPropertyDescriptor()操作,返回属性描述对象Descriptor或者undefined

  • handler.getPrototypeOf(target)
    handler.getPrototypeOf(target)用于拦截获取对象原型,会拦截如下操作:Object.prototype.__prototype_Object.prototype.isPrototypeOf()Object.getPrototype()Reflect.getPrototypeOf()instanceof等操作。
    handler.getPrototypeOf的返回值必须是对象或者null,否则会报错。若target是不可扩展的,则必须返回目标对象的原型对象。

  • handler.has()

    拦截in操作符

  • handler.isExtensible(target)
    handler.isExtensible(target)用于拦截Object.isExtensible()操作,返回一个布尔值,并且该布尔值与targetisExtensible属性值相同,否则会报错。

  • handler.ownKeys(target)
    handler.ownKeys方法用于拦截对对象自身属性的读取操作返回一个数组;如Object.getOwnPropertyNames()Object.getOwnPropertySymbols(),Object.keys()for...in

  • handler.preventExtensions(target)
    handler.preventExtensions方法用于拦截Object.preventExtensions(),该方法必须返回一个布尔值,否则会被自动转为布尔值。

  • handler.set(target,key,value,receiver)
    handler.set用于拦截对象某个属性的赋值操作,返回一个布尔值。如果赋值成功,则返回true;否则,返回false。如果属性部署了setter,那么setterthis是指向第四个参数receiver,若参数receiver省略,则this默认指向target

  • handler.setPrototypeOf(target,prototype)
    handler.setPrototypeOf方法主要用于拦截Object.setPrototypeOf()方法,返回一个布尔值。如果设置成功,则返回true;否则,返回false

Reflect

Reflect对象是ES6 为了操作对象而提供的新 API,它提供了拦截 JavaScript 操作对象的方法。这些方法与Proxyhandler方法相对应。

Reflect静态方法

Reflect对象一共有 13 个静态方法,大部分与Object对象的同名方法的作用相同,并且与Proxy对象的方法相对应。

  • Reflect.apply(func,thisArg,args)
    Reflect.apply方法用于调用函数,返回函数执行结果。该方法与Function.prototype.apply()方法功能相同,但是Reflect.apply方法的参数是func函数、this对象和参数数组,返回值是函数执行结果。

  • Reflect.construct(target,args)
    Reflect是一个静态类,不能实例化,也不能被继承,因此无法使用new操作符。但是Reflect.construct方法可以用来调用构造函数。

js 复制代码
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const p = Reflect.construct(Person, ["Bob", 18]);
console.log(p); // Person {name: '张三', age: 18}

//等价于
const p = new Person("Bob", 18);
  • Reflect.get(target,name,receiver)
    Reflect.get用于查找target对象的name属性,返回该属性的值。如果name属性不存在,则返回undefined。如果targetname属性部署了getter,那么getterthis是指向第三个参数receiver,若参数receiver省略,则this默认指向target
js 复制代码
const studentA = {
  name: "Bob",
  age: 18,
  get info() {
    return `${this.name} is ${this.age}`;
  },
};

const studentB = {
  name: "Alice",
  age: 19,
};

console.log(Reflect.get(studentA, "info")); // Bob is 18

console.log(Reflect.get(studentA, "info", studentB)); // Alice is 19
  • Reflect.set(target,name,value,receiver)
    Reflect.set用于设置target对象的name属性等于value。如果name属性设置了setter,则setterthis绑定receiver
js 复制代码
const studentA = {
  name: "Bob",
  set setSex(value) {
    this.sex = value;
  },
};

const studentB = {
  name: "Alice",
};

Reflect.set(studentA, "setSex", "male");
console.log(studentA.sex); // male
Reflect.set(studentA, "setSex", "female", studentB);
console.log(studentA.sex, studentB.sex); // male,female
  • Reflect.defineProperty(target,name,desc)
    Reflect.defineProperty方法基本等同于Object.defineProperty,返回一个布尔值。如果操作失败,会抛出一个错误。Object.defineProperty会逐渐被废弃。
    另外Reflect.defineProperty的第一个参数必须是对象,否则会抛出错误。
js 复制代码
Reflect.defineProperty({}, "name", {
  value: "Bob", // 值
  writable: true, // 是否可写
  enumerable: true, // 是否可枚举
  configurable: true, // 是否可配置
});
  • Reflect.deleteProperty(target,name)
    Reflect.deleteProperty方法用于删除对象的属性,返回一个布尔值。如果删除成功,返回true;如果删除失败,返回false

    同理,若Reflect.deleteProperty的第一个参数不是对象,也会抛出错误。

  • Reflect.has(target,name)
    Reflect.has方法对应name in target的操作。如果target对象继承了name属性,返回true;否则返回false。相比name in obj写法,Reflect.has是函数的形式,且心智负担很小。

  • Reflect.ownKeys(target)
    Reflect.ownKeys方法用于返回对象的所有属性,包括不可枚举属性。它的返回值是一个数组,数组的成员是字符串,每个字符串对应一个属性名。
    Reflect.ownKeys基本等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target)),但它是一个静态方法,不需要实例化。

    同理,若Reflect.ownKeys的第一个参数不是对象,也会抛出错误。

js 复制代码
Reflect.ownKeys({
  name: "Bob",
  age: 18,
  [Symbol("symbol")]: "symbol",
});

// ['name', 'age', Symbol(symbol)]
  • Reflect.isExtensible(target)
    Reflect.isExtensible方法对应Object.isExtensible方法,返回一个布尔值,表示当前对象是否可扩展。
    同理,若Reflect.isExtensible的第一个参数不是对象,也会抛出错误。
    在某些情况下,Reflect.isExtensible(obj)会返回false,如对象objObject.preventExtensions(obj)Object.seal(obj)Object.freeze(obj)等处理后,会返回false。另外,JS 某些内置对象也是不可扩展对象,如MathJSON

总结如下:

方法/状态 isExtensible 能否添加属性 能否删除属性 能否修改值
默认对象 ✅ true ✅ 是 ✅ 是 ✅ 是
Object.preventExtensions() ❌ false ❌ 否 ✅ 是 ✅ 是
Object.seal() ❌ false ❌ 否 ❌ 否 ✅ 是
Object.freeze() ❌ false ❌ 否 ❌ 否 ❌ 否
  • Reflect.preventExtensions(target)
    Reflect.preventExtensions(target)对应Object.preventExtension方法,用于让一个对象变为不可扩展,返回一个布尔值,表示是否操作成功。

  • Reflect.getOwnPropertyDescriptor(target,name)
    Reflect.getOwnPropertyDescriptor(target,name)对应Object.getOwnPropertyDescriptor方法,用于获取对象的属性描述符,返回一个PropertyDescriptor对象。

js 复制代码
let p={}
Reflect.defineProperty(p,'name',{
    value:"Jinus",
    enumerable:false,
    writable:true,
    configurable:true,
})

console.log(Reflect.getOwnPropertyDescriptor(p,'name')) 
// {value:"Jinus",enumerable:false,writable:true,configurable:true,}

console.log(Reflect.getOwnPropertyDescriptor(p,'age')) 
// undefined  
  • Reflect.getPrototypeOf(target)
    Reflect.getPrototypeOf方法对应Object.getPrototypeOf,用于读取对象的__proto__属性,返回对象的原型。若参数target不是对象,Reflect.getPrototypeOf会报错,而Object.getPrototypeOf会将target转为对象,再获取该对象的__prototype

  • Reflect.setPrototypeOf(target,prototype)
    Reflect.setPrototypeOf方法对应Object.setPrototypeOf,用于设置对象的原型prototype,返回一个布尔值,表示是否操作成功。

    若参数target不是对象,Reflect.setPrototypeOf会报错,而Object.setPrototypeOf会返回target自身。特殊情况下,targetnullundefinedReflect.setPrototypeOf会何Object.setPrototypeOf都会报错。

相关推荐
anyup1 分钟前
🔥 🔥 为什么我建议你使用 uView Pro 来开发 uni-app 项目?
前端·vue.js·uni-app
Skelanimals1 分钟前
Elpis全栈框架开发总结
前端
蓝胖子的小叮当5 分钟前
JavaScript基础(十三)函数柯里化curry
前端·javascript
孪创启航营9 分钟前
数字孪生二维热力图制作,看这篇文章就够了!
前端·three.js·cesium
宫水三叶的刷题日记12 分钟前
真的会玩,钉钉前脚辟谣高管凌晨巡查工位,小编随后深夜发文
前端·后端·面试
zzywxc78721 分钟前
AI 行业应用:金融、医疗、教育、制造业领域的落地案例与技术实现
android·前端·人工智能·chrome·金融·rxjava
Juchecar28 分钟前
Vue 3 + Naive UI 调用useMessage的方法(在Naive UI 2.42.0实测有效)
前端
前端Hardy43 分钟前
HTML&CSS:超酷炫的3D动态卡片
前端·javascript·css
RaidenLiu1 小时前
从 Provider 迈向 Riverpod 3:核心架构与迁移指南
前端·flutter
前端进阶者1 小时前
electron-vite_18Less和Sass共用样式指定
前端