Proxy | ES 6 Proxy实现对象代理,实现简单的数据响应式变化视图更新

367 阅读3分钟

vue2的数据响应式是利用Object.defineProperty()来实现,在setget中,创建依赖、收集依赖,当数据变化时,Watcher再去通知该数据所依赖的地方,进行数据响应式更新。Object.defineProperty()无法监听到对象上的新增属性和删除属性,因此vue2中,对象新增和删除的属性都需要调用$set方法,从而手动的执行创建依赖、收集依赖...等过程。

Vue3中使用了ES 6中新提供的Proxy代理,来实现数据响应式,MDN上是这样来定义Proxy的:

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

换句话说,Proxy对象能够包装你的目标对象,并且可以拦截到对该对象进行数据操作。Proxy接受两个参数:

const obj = new Proxy(target, handler)

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

  • handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 obj 的行为。

handler对象常用的方法有:

* `handler.get()` 属性读取操作的捕捉器。
* `handler.set()` 属性设置操作的捕捉器。
* `handler.deleteProperty()` delete 操作符的捕捉器。
* `handler.apply()` 函数调用操作的捕捉器。
* `handler.has()` in 操作符的捕捉器。

详细的Proxy文档介绍,可查阅MDN Proxy

先用Proxy包装一个简单的对象,实现读取、新增、删除属性的拦截:

const obj = {
	name: 'curry',
	age: 34
}
const handler = {
	get(target, key) {
		const result = Reflect.get(target, key)
		console.log('读取数据', key)
		return result
	},
	set(target, key, val) {
		const result = Reflect.set(target, key, val)
		console.log('写入数据', key)
		return result
	},
	deleteProperty(target, key, val) {
		const result = Reflect.deleteProperty(target, key)
		console.log('删除数据', key)
		return result
	},
}

//用new Proxy()生成一个代理的对象
const  proxyObj = new Proxy(obj, handler)

//访问name属性
proxyObj.name //控制台打印:读取数据 name
proxyObj.team = 'GSW' //控制台打印:写入数据 team
delete proxyObj.age //控制台打印:删除数据 age

console.log(proxyObj)

可以看到,我们对于proxyObj的读、写操作,甚至delete都可以拦截到,这是Object.defineProperty()所不能实现的。

拦截到数据读取操作之后,利用数据的变化,来实现页面的更新。接下来就写一个简单的例子:定义一个counter,counter1秒钟增加一次,并且把增加后的值同时显示在页面上。

<div id="app"></div>
const obj = { counter: 1 }
const handler = {
  get(target, key) {
    const result = Reflect.get(target, key)
    console.log('读取数据', key)
    return result
  },
  set(target, key, val) {
    const result = Reflect.set(target, key, val)
    console.log('写入数据', key)
    updateHtml()
    return result
  },
  deleteProperty(target, key, val) {
    const result = Reflect.deleteProperty(target, key)
    console.log('删除数据', key)
    return result
  },
}

const proxyObj = new Proxy(obj, handler)

const app = document.getElementById('app');

setInterval(() => {
  proxyObj.counter++
}, 1000);

function updateHtml() {
  app.innerHTML = obj.counter
}

实现效果:

可以看到counter每次变化之后,都会去执行updateHtml方法,从而实现视图的更新,vue3的响应式基本原理大概就是如此,vue会在数据更新的时候,在updateHtml中进行更多的操作,例如:diff算法比较VNode,从而实现差量更新,节省DOM操作性能等。


文章首发自作者博客:博客地址