likes
comments
collection
share

走进 Reflect,Proxy,系统掌握 Vue3 底层响应式 API

作者站长头像
站长
· 阅读数 16

Reflect反射

  • 囊括了 JS 内部一些专有方法,例如 Object.keysObject.getownPropertyNameObjecct.delete
const obj = {
  name: "云牧"
};

// 直接操作
obj.name;

// Reflect
Reflect.get(obj, "name");

// Object 静态方法操作
Object.defineProperty(obj, "age", {
  value: 100
});

// Reflect
Reflect.defineProperty(obj, "age", {
  value: 100
});

console.log(obj.age); // 100

Proxy对象

  • 创建一个对象的代理,从而实现基本操作的拦截和自定义
const p = new Proxy(target, handler);
// target 可以是对象、数组、函数、代理
// handler 是一个对象,内部每个方法定义了执行各种操作时代理 p 的行为

走进 Reflect,Proxy,系统掌握 Vue3 底层响应式 API

const obj = {};

const proxyObj = new Proxy(obj, {
  get(target, property, receiver) {
    console.log("get:", target, property, receiver);
    // { name: '云牧' }, name, { name: '云牧' }

    console.log("get:", target === obj, receiver === proxyObj);
    // get: true, true

    // 相当于 target[property]
    return Reflect.get(target, property);

    // 相当于 receiver[property]
    return Reflect.get(target, property, receiver);
  },
  set(target, property, value, receiver) {
    console.log("set:", target, property, value, receiver);
    // set: {}, name, 云牧, {}

    console.log("set:", target === obj, receiver === proxyObj);
    // set: true, true

    return Reflect.set(target, property, value, receiver);
  }
});

proxyObj.name = "云牧";

console.log(proxyObj, obj);
// { name: '云牧' }, { name: '云牧' }

receiver 不是代理对象的情况

情况一:

  • 某个对象的原型是一个代理对象
  • 设置对象某个属性,本身不存在,但是原型(代理对象)有
  • 触发代理对象 set 捕获器,此时 receiver === 某个对象
const proto = {
  proto_name: "云牧"
};
let testObj;

const proxyProto = new Proxy(proto, {
  set(target, property, value, receiver) {
    console.log(receiver === proxyProto); // false
    // 当设置 proto_name 时为 true
    console.log(testObj === receiver);

    return Reflect.set(target, property, value, receiver);
  }
});

function TestObject(message) {
  this.message = message;
}

TestObject.prototype = proxyProto;

testObj = new TestObject("hello");
testObj.message = "world";
testObj.proto_name = "黛玉";

情况二:

  • 某个对象原型是代理对象
  • 此对象和原型都有相同属性如 name
  • 此对象访问原型(代理对象)的 getter 方法,此方法访问 this.name
const proto = {
  name: "proto_name",
  get nameValue() {
    return this.name;
  }
};

const proxyObj = new Proxy(proto, {
  get(target, property, receiver) {
    return Reflect.get(target, property, receiver);
  }
});

const obj = {
  name: "obj_name"
};

// 设置原型
Object.setPrototypeOf(obj, proxyObj);
// 此时 receiver === proxyObj
console.log("proxyObj.nameValue:", proxyObj.nameValue); // proto_name
// 此时触发原型上的捕获器 receiver === obj
console.log("obj.nameValue:", obj.nameValue); // obj_name

apply 函数调用捕获器

拦截范围:

  1. proxy(..args)
  2. Function.prototype.apply()
  3. Reflect.apply()
  4. Function.prototype.call()
function sum(n1, n2) {
  return n1 + n2;
}

const proxySum = new Proxy(sum, {
  // 目标对象, 上下文, 参数数组
  apply(target, thisArg, argumentsList) {
    console.log(target, thisArg, argumentsList);
    return Reflect.apply(target, thisArg, argumentsList);
  }
});

proxySum(1, 2, 3); // [Function: sum], undefined, [ 1, 2, 3 ]
proxySum.call(null, 1, 2, 3); // [Function: sum], null, [ 1, 2, 3 ]
proxySum.apply(null, [1, 2, 3]); // [Function: sum], null, [ 1, 2, 3 ]
Reflect.apply(proxySum, null, [1, 2, 3]); // [Function: sum], null, [ 1, 2, 3 ]

getPrototypeof 捕获器

拦截范围:

  1. __proto__
  2. instanceof
  3. Object.getPrototypeOf
  4. Reflect.getPrototypeOf
  5. Object.prototype.isPrototypeOf
const obj = {
  name: "云牧"
};

const proxyObj = new Proxy(obj, {
  getPrototypeOf(target) {
    console.log("target:", target); // target: { name: '云牧' }
    return Reflect.getPrototypeOf(target);
  }
});

proxyObj.__proto__;

console.log(proxyObj instanceof Object); // true

Object.getPrototypeOf(proxyObj);

Reflect.getPrototypeOf(proxyObj);

Object.prototype.isPrototypeOf(proxyObj);

setPrototypeof 捕获器

拦截范围:

  1. Object.setPrototypeOf
  2. Reflect.setPrototypeOf
const obj = {
  name: "云牧"
};

const newProto = {
  name: "黛玉"
};

const proxyObj = new Proxy(obj, {
  setPrototypeOf(target, newProto) {
    console.log(target, newProto); // { name: '云牧' }, { name: '黛玉' }
    return Reflect.setPrototypeOf(target, newProto);
  }
});

Object.setPrototypeOf(proxyObj, newProto);
Reflect.setPrototypeOf(proxyObj, newProto);

construct 捕获器

  • 拦截 new 操作
class Person {}

const ProxyPerson = new Proxy(Person, {
  construct(target, argumentsList, newTarget) {
    console.log(target, argumentsList, newTarget);
    // [class Person], [ '云牧', '黛玉' ], ProxyPerson
    return Reflect.construct(target, argumentsList, newTarget);
  }
});

new ProxyPerson("云牧", "黛玉");

其他捕获器

const obj = {
  name: "云牧"
};

const proxyObj = new Proxy(obj, {
  // 拦截修改属性描述符信息,
  // Object.defineProperty()
  // Reflect.defineProperty(),
  // proxy.property='value'
  defineProperty(target, prop, descriptor) {
    return Reflect.defineProperty(target, prop, descriptor);
  },
  // 拦截 delete 操作
  // delete proxy[property] 和 delete proxy.property
  // Reflect.deleteProperty()
  deleteProperty(target, prop) {
    return Reflect.defineProperty(target, prop);
  },
  // 拦截获取属性描述符
  // Object.getOwnPropertyDescriptor()
  // Reflect.getOwnPropertyDescriptor()
  getOwnPropertyDescriptor(target, prop) {
    return Reflect.getOwnPropertyDescriptor(target, prop);
  },
  // 拦截 in
  // property in proxy
  // foo in Object.create(proxy)
  // with(proxy) { (property); }
  // Reflect.has()
  has(target, prop) {
    return Reflect.has(target, prop);
  },
  // 拦截让对象不可被扩展
  // Object.preventExtensions()
  // Reflect.preventExtensions()
  preventExtensions(target) {
    return Reflect.preventExtensions(target);
  },
  // 拦截查询对象不可被扩展
  // Object.isExtensible()
  // Reflect.isExtensible()
  isExtensible(target) {
    return Reflect.isExtensible(target);
  },
  // 拦截获取对象属性
  // Object.getOwnPropertyNames()
  // Object.getOwnPropertySymbols()
  // Object.keys()
  // Reflect.ownKeys()
  ownKeys(target) {
    return Reflect.ownKeys(target);
  }
});

可取消的代理

  • 语法:Proxy.revocable(target, handler)
  • 返回值:{"proxy": proxy, "revoke": revoke}
    • proxy:等同于通过 new Proxy(target, handler) 创建的代理对象
    • revoke:取消代理对象的行为
const revocableProxy = Proxy.revocable(
  {
    name: "云牧"
  },
  {
    get(target, property, receiver) {
      console.log("get");
      return Reflect.get(target, property, receiver);
    }
  }
);

const proxyObj = revocableProxy.proxy;

console.log(proxyObj.name); // 会触发 get 捕获器

// 取消代理
revocableProxy.revoke();

// 后续的增删改查对象操作全会报错:TypeError: Cannot perform 'set' on a proxy that has been revoked
// console.log(proxyObj.name);

注意事项

  • 捕获器函数里 this 指向 new Proxy 的第二个参数对象
  • new Proxy 生成的代理对象的数据类型和被代理的对象数据一致
const obj = {};
const handler = {
  get() {
    console.log(this === handler); // true
  },
  set() {
    console.log(this === handler); // true
  }
};

const proxyObj = new Proxy(obj, handler);
proxyObj.name = "云牧";
proxyObj.name;
console.log(typeof proxyObj); // object

function sum(n1, n2) {
  return n1 + n2;
}
const proxySum = new Proxy(sum, { apply() {} });
console.log(typeof proxySum); // function