胖蔡叨叨叨
你听我说

简单了解Vue3中的proxy

Vue3使用的proxy是什么?

众做周知Vue2的双向绑定原理是利用了ES5的一个APIObject.defineProperty()对数据进行劫持,结合发布订阅模式的方式来实现的。 而Vue3 中则是使用了ES6的Proxy 对数据代理。

Vue2 的Object.defineProperty()

  1. 定义: Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。 例子:
	const object1 = {};
	Object.defineProperty(object1, 'property1', {
	  value: 42,
	  writable: false
	});

	object1.property1 = 77;
	console.log(object1.property1);  //打印出来的一人是42
  1. 语法:
	Object.defineProperty(obj, prop, descriptor)
	// obj 要定义属性的对象
	// 要定义或修改的属性的名称或 Symbol
	//descriptor 要定义或修改的属性描述符

返回值:被传递给函数的对象。

  1. 如何实现响应式的? 通过defineProperty 两个属性,get及set
  • get 属性的getter函数,当访问改属性时,会调用此函数。执行时不传入任何参数,但是会传入this对象(由于继承管理,this不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
  • set 属性的setter函数,当属性值被修改时,会调用此函数。改方法接受一个参数(也就是被赋的新值),会传入赋值时的this对象。默认undefined.

简单的例子: 监听person 的 namep变化

	let person ={}
	let name = '张小张'
	Object.defineProperty(person,'namep',{
		// 默认是不可枚举的(for in打印不出来) 可以使用:enumerable:true
		// 默认不可修改,可:wirtable:true
		//默认不可删除 可:configurable:true
		get(){
			console.log('触发了get的方法');
			return name
		},
		set(val){
			console.log('触发了set的方法');
			name = val
		}
	})
	// 读取对象的属性
	console.log(person.namep);   // 触发了get的方法  张小张
	
	// 修改name,再次读取
	name = '王小王'
	console.log(person.namep);   // 触发了get的方法  王小王
	
	// 修改对象
	person.namep = '李小里'
	console.log(person.namep);   // 触发了set的方法 触发了get的方法 李小里

如果监听多个属性的变化,这里就要用到Object.keys(obj)进行遍历等,以及深度监听时又要借助递归来实现。 而在监听数组方面

	let arr = [1,2,3,4,5]
	let obj = {}
	
	Object.defineProperty(obj,'arr',{
		get(){
			console.log('get');
			return arr
		},
		set(val){
			console.log('set',val);
			arr = val
		}
	})
	
	console.log(obj.arr); // get  [1,2,3,4,5]
	obj.arr = [2,3,4]   // set [2,3,4] 
	obj.arr.push(7)  // get 但是监听不到下面的了

由此看出:数组的一些方法,例如push等,get是监听不到的。 通过索引访问或者修改数组中已经存在的元素,是可以触达get和set,但是对于通过push、unshift增加的元素,会增加索引,这种情况无法监听,通过pop或shift删除元素,会更新索引,也会触发。 在vue2 中,是通过重写Array原型上的方法解决这个问题。 但是其实依然会有很多问题,类似给对象新增一个属性时,也需要手动监听,所以在vue2中给数组或者对象新增属性时,使用this.$set 或者vue.Set()

Vue3的Proxy

Es6的proxy正好可以解决以上的问题。

  1. 定义: Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 其实就是在对目标对象的操作之前提供了拦截,可以对外界的操作进行过滤和改写,修改某些操作的默认行为,这样我们可以不直接操作对象本身,而是通过操作对象的代理对象来间接来操作对象,达到预期的目的。
  2. 语法:
	const p = new Proxy(target, handler)
	//`target` 要使用  `Proxy`  包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

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

简单的例子:

	// 定义一个需要代理的对象
	let person = {
		name:'张小张',
		age:18
	}
	
	//定义handler 对象
	let handler = {
		get(target,key){   // target就是代理的对象,key 是属性值
			return key in target?target[key]:'没有这个属性'
		},
		set(target,key,val){   // val 是新值
			target[key] = val
			return true
		}
	}
	
	let proxyObj = new Proxy(person,handler)
	
	console.log(proxyObj.name)   // 张小张
	
	console.log(proxyObj.sex) // 没有这个属性
	
	proxyObj.age = 19
	console.log(proxyObj.age) //19

这里注意一下:set()方法必须返回一个布尔值,返回 true 代表属性设置成功,- 在严格模式下,如果 set() 方法返回 false,那么会抛出一个异常。

  1. Reflect:是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers 的方法相同。 为操作对象而提供的新API,将Object对象的属于语言内部的方法放到Reflect对象上,即从Reflect对象上拿Object对象内部方法。 如果出错将返回false。 所以上面的例子改一下:
	// 定义一个需要代理的对象
	let person = {
		name:'张小张',
		age:18
	}
	
	//定义handler 对象
	let handler = {
		get(target,key){   // target就是代理的对象,key 是属性值
			console.log(Reflect.get(target, key));
			return Reflect.get(target, key)?Reflect.get(target, key):'没有这个属性'
		},
		set(target,key,val){   // val 是新值
			Reflect.set(target,key,val)
			return true
		}
	}
	
	let proxyObj = new Proxy(person,handler)
	
	console.log(proxyObj.name)   // 张小张  张小张 
	
	console.log(proxyObj.sex) // undefined 没有这个属性
	
	proxyObj.age =22 
	console.log(proxyObj.age) //22

用这个例子和上面的例子作比较发现:Proxy代理的是整个对象,而不是对象的某个特定属性,不需要我们通过遍历来逐个进行数据绑定。 不光如此,对于上面遇到的数组方法、对象新增属性等都可以得到解决。

  1. Proxy 的拦截: 除了上面用到的set()和get(),Proxy支持的拦截操作一共有13种。具体信息可以查看MDN([https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy]
  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]。
  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值。
  • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。
  1. Proxy 总结: Proxy相比Object.defineProperty,可以直接监听整个对象而非属性,也可以直接监听数组的变化,并且提供13中拦截方法。但是缺点是不兼容IE,存在浏览器兼容问题,也没有polyfill(指的是一个代码块。这个代码块向开发者提供了一种技术, 这种技术可以让浏览器提供原生支持,抹平不同浏览器对API兼容性的差异)。Object.defineProperty支持IE9。
赞(0) 打赏
转载请附上原文出处链接:胖蔡叨叨叨 » 简单了解Vue3中的proxy
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏