学懂Proxy代理

314 阅读5分钟

前段时间vue官方正式将3的版本设定为默认版本,所以针对vue3学习我算是留级生了(毕竟天天用React),应为vue3使用Proxy方式做了响应式,那么我们写话少说,一起看看Proxy

1、什么是Proxy

Proxy对象是用于创建一个对象的代理,通过代理的方式去实现拦截、取值、枚举等等一系列操作,它的基本写法就是const obj = new Proxy(target, handler)这样的方式;

  • target: 需要代理的对象(任意的对象(对象、数组、代理对象,函数))
  • handler: 提供的一些对象操作的捕获器;

2、handler中所有的捕获器(对应object里面的一些操作)

下面是摘自MDN一些说明,我们先对应一下之前object里面的一些操作,熟悉object一些操作的同学们,可以按照object方式对号入座即可
handler.getPrototypeOf() 对应 Object.getPrototypeOf 方法的捕捉器。
handler.setPrototypeOf() 对应 Object.setPrototypeOf 方法的捕捉器。
handler.isExtensible() 对应 Object.isExtensible 方法的捕捉器。
handler.preventExtensions() 对应 Object.preventExtensions 方法的捕捉器。
handler.getOwnPropertyDescriptor() 对应 Object.getOwnPropertyDescriptor 方法的捕捉器。
handler.defineProperty() 对应Object.defineProperty 方法的捕捉器。
handler.has() 对应 in 操作符的捕捉器。
handler.get() 属性读取操作的捕捉器。
handler.set() 属性设置操作的捕捉器。
handler.deleteProperty() 对应 delete 操作符的捕捉器。
handler.ownKeys() 对应 Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
handler.apply() 对应 调用操作的捕捉器。
handler.construct() 对应 new 操作符的捕捉器。

3、关于对象的代理

(1)关于get 与set用法

那么我们一步步去揭开Proxy代理的办法,首先我们来一个简单一些的例子:

let data = {}
const obj = new Proxy(data, {
  get: function(target, key, receiver) {
    return target[key];
  },
  set: function(target, key, value, receiver) {
    target[key] = value;
    return true;
  }
})

obj.a = 123;
console.log(obj.a); // 123
console.log(data.a) // 123

我们先去了解proxy在代理对象时候对于get, set基本用法

  • 对于代理对象,我们在赋值过程中回去触发set方法,set方法会将最新的值赋值给target,注意对于set方法,我们需要返回true,这样代表我们设定返回值成功;
  • 对于get方式,我们在获取值中做的一些操作并返回值;
  • 代理会将我们操作的内容转发到被代理对象上;

接下来,我们在一个别的例子:

const data = {
  text: {
    a: '我是深层次的文本',
  },
  show: true
}

const obj = new Proxy(data, {
  get: function(target, key, receiver) {
    console.log('触发获取');
    return target[key];
  },
  set: function(target, key, value, receiver) {
    console.log('触发设置');
    target[key] = value;
    return true;
  }
})

obj.text.a = '调整文本'; 
// ’触发获取‘
// ’调整文本‘

在执行obj.text.a = '调整文本'事实上,我们的操作是一个设置值的过程,然后他值触发了一次get操作,然后进行了赋值,而赋值过程中并没有触发set捕捉器,不难发现其实Proxy在代理过程中是一个浅层代理,深层的引用类型数据并没有进行代理,通常我们需要通过递归的方式进行改进,具体如下:

const data = {
  text: {
    a: '我是深层次的文本',
  },
  show: true
}

const handle = {
  get: function(target, key, receiver) {
    if (Object.prototype.toString.call(target[key]) === '[object Object]') {
      return new Proxy(target[key], handle)
    }
    console.log('触发获取')
    return target[key];
  },
  set: function(target, key, value, receiver) {
    console.log('触发设置');
    target[key] = value;
    return true;
  }
}

const obj = new Proxy(data, handle)

obj.text.a = '调整文本内容'
// 结果
// 触发设置
// 调整文本设置

(2)has用法

const data = {
  text: 'hello world',
  _other: true
}

const obj = new Proxy(data, {
  has: function(target, key) {
    console.log('触发has方法')
    if (key[0] === '_') {
      return false;
    }
    return true
  }
})

console.log('_other' in obj) // false

has是对于对象 key in proxy操作符号的拦截

(3)defineProperty

当调用Object.defineProperty(),传递给 defineProperty 的 descriptor 有一个限制 ,限制属性如下,都是我们熟知的一些属性

  • enumerable 是否可悲枚举
  • configurable 属性是否可删除,value及 writable是否可被修改
  • writable 是否可修改
  • value 值
  • get 获取属性值钩子
  • set 设置属性值的钩子

我们简单用下面例子演示用一下用法:

var p = new Proxy({}, {
  defineProperty: function(target, prop, descriptor) {
    return true;
  }
});

var desc = { configurable: true, enumerable: true, value: 10 };
Object.defineProperty(p, 'a', desc);

(4)ownKeys

这个主要是用来捕获 Oject.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()、Reflect.keys()(这里简单说一下Reflect其实就是类似于Math一样,他是一个内置的对象,我们无法通过new的方式实例化,但是他提供了一些方法类似于Object,可以让我们方便的操作对象),当获取obj的key值时候,就会触发ownKeys, 注意ownKeys返回的一个可枚举的对象,下面做一个简短的例子来演示一下:

const data = {
  text: 'hello world',
  name: 'kanade',
}

const obj = new Proxy(data, {
  ownKeys(target) {
    console.log('触发 ownKeys')
    return Reflect.ownKeys(target)
  },
})

for (const key of Object.keys(obj)) { 
  console.log(key)
}
// 触发 ownKeys
// ...

(5)deleteProperty

当我们对于 delete obj.keys 操作时候,我们可以通过deleteProperty进行捕获,返回true or false标识删除成功与否

4、对于函数的proxy

apply

proxy不经可以对常规对象进行代理,还可以对于函数进行代理,具体如下例子所示,当我们对于一个函数进行调用,我们可以拿到函数本身和他的arguments实参,我们在调用前可以做一些预处理;

function add(a, b) {
  console.log(a + b)
  return a + b
}

const addProxy = new Proxy(add, {
  apply: function(target, thisArgs, args) {
    return target(args[0]* 2, args[1])
  }
})

addProxy(1, 2) //  4

写在最后

以上就是一些proxy的基础用法,了解了这些,我们再去解读vue3的响应式数据的设计,最近我在读《vuejs设计与实现》一书,对于这些用法一步步去了解,去模拟,有兴趣可以看一下我的读书笔记整理,希望对你能有所帮助