vue2是用Object.defineProperty()来实现的,vue3是用proxy来实现的.所以本文主要讲Object.defineProperty和proxy的区别
先来浅浅的了解一下Object.defineProperty() 和Proxy:
Object.defineProperty()
是在对象上定义一个新属性,或者修改一个对象的现有属性.并返回这个对象.
语法:Object.defineProperty( 要操纵做的对象 , 要监听的属性名 , 一个包含set/get的操作对象 )
1.试一试,Object.defineProperty()给person对象加一个name属性,并看set和get什么时候触发
let person = {} let personName = 'lili' //在person对象上添加属性name,值为personName Object.defineProperty(person, 'name', { get: function () { console.log('触发了get方法') return personName }, set: function (val) { console.log('触发了set方法') personName = val } })
基于上述代码,以下是在控制台的操作,以及输出的结果:
如果要监听一个对象上的所有属性,需要遍历循环整个对象.
缺陷:循环遍历监听整个对象里的所有属性之后,如果这个对象再加了一个属性,就监听不到了,所以Vue2中对象新增属性的修改需要使用vue.$set来设值。
引申 :$set的原理
是在vue的原型上添加$set的方法,同时针对以下三种情况情况分别进行处理
1. 当key已经存在于target上的时候,直接修改target中对应key的值
2. 当target是数组的时候,借助vue内部拦截处理后数组的splice方法进行赋值,vue对数组的'push','pop','shift','unshift','splice','sort','reverse'方法进行拦截处理成响应式,调用这些方法,可以触发界面的更新,如果只是通过arr[1]=1的方式进行赋值不会触发视图更新。
3. 对于新增的属性,通过defineReactive把数据转化成getter和setter的方式,并触发数据变化通知
Proxy
Proxy 是一个对象,他用于创建另一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
语法:const a = new Proxy(target, handler)
target:要使用 Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p
的行为。
写一个简单的例子:用 Proxy 构造个叫proxy的对象,(创建了一个代理对象)。
在创建代理对象时,我们定义了一个 get 捕获器,用于捕获属性读取的操作。
定义了一个 set 捕获器,代理会将所有应用到它的操作转发到这个对象上
const person = {name:"lili"}; const proxy = new Proxy(person, { get(target, property, receiver) {
// target:目标对象。
// property:被读取的属性名。
// receiver:指向当前的 Proxy 对象或者继承于当前 Proxy 的对象。
console.log(`触发了get方法,正在访问${property}属性`); return target[property]; },
set(target, property, value, receiver) {
console.log(`触发了set方法,正在给${property}属性设值`);
target[property] = value
}
});
基于上述代码,以下是在控制台的操作,以及输出的结果:
由上可见 捕获器的作用就是用于拦截用户对目标对象的相关操作,在这些操作传播到目标对象之前,会先调用对应的捕获器函数,从而拦截并修改相应的行为。
proxy操作的是proxy实例出来的对象,并不会操作原对象本身。
语法中的第二个参数handler对象:支持 13 种捕获器,所有的捕获器是可选的。如果没有定义某个捕获器,那么就会保留源对象的默认行为。
在实际的 Proxy 使用场景中,往往会结合 Reflect 对象提供的静态方法来实现某些特定的功能。
-----思考:为什么Proxy和Reflect
一起使用呢?
----->因为Proxy和Reflect
的方法都是一一对应的,在Proxy
里使用Reflect
会提高语义化
还有解决proxy的this指向问题,用Reflect把this放在receiver
上,而不放在target
上
先来看一下proxy的this指向的问题:
const target = { checkThis: function () { console.log(this === p); } }; const p = new Proxy(target, {}); console.log(target.checkThis) // false console.log(p.chceckThis) // true
在 Proxy 代理的情况下,目标对象内部的this关键字会指向new的proxy对象
在这里来浅浅了解一下Reflect
Reflect:反射,它提供拦截 JavaScript 操作的方法。
这里直接引用了林三心老师在掘金上 《林三心画了8张图,最通俗易懂的Vue3响应式核心原理解析》的解说:
举Reflect两个方法的例子
const person = { name: '林三心', age: 22 } const proxyPerson = new Proxy(person, { get(target, key, receiver) { return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { Reflect.set(target, key, value, receiver) } }) console.log(proxyPerson.name) // 林三心 proxyPerson.name = 'sunshine_lin' console.log(proxyPerson.name) // sunshine_lin
-
Proxy 中接受的 receiver 形参表示代理对象本身或者继承与代理对象的对象。
-
Reflect 中传递的 receiver 实参表示修改执行原始操作时的 this 指向。
所以~就知道为什么要尽量把this放在代理对象receiver上,而不建议放原对象target上了:
因为原对象target有可能本来也是是另一个代理的代理对象,所以如果this一直放target上的话,出bug的概率会大大提高
-------------------------------------------------------------------------------分割线------------------------------------------------------------------------------------------------------
简单了解完Object.defineProperty和Proxy之后,来看一下区别
区别一:
Object.defineProperty()作用是在对象上定义一个新属性,或者修改一个对象的现有属性.并返回这个对象.
通过深层遍历循环整个对象,来达到监听这个对象上所有属性的目的
监听的是对象的属性
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
代理的是整个对象
思考:由于用Object.defineProperty做监听时,set监听不到数组的push和unshift的变化,所以有的时候操作数组,页面不会变化.但是proxy就没有这个问题,因为它代理的是整个对象
引:vue2中通过改变下标改变数组,也监听不到是为什么呢? 这个并不是Object.defineProperty的问题,而是vue本身考虑性能问题,而没有监听数组的改变下标的这一功能
区别二:
Object.defineProperty操作的是对象本身
proxy操作的是proxy实例的对象
写在后面:响应式原理是Vue的核心特性之一,数据驱动视图,修改数据视图随之响应更新。
区分与双向数据绑定:
双向数据绑定是vue的一个特性,他只是事件和通过事件对象给state属性赋值的语法糖而已。是指我们使用的v-model
指令的实现:vue通过v-model为组件添加上input事件处理和value属性的赋值
<template>
<input v-model='localValue'/>
</template>
------------------------------------------------------
//上面的v-model就相当于做了如下操作
--------------------------------------------------------
<template>
<!-- 这里添加了input时间的监听和value的属性绑定 -->
<input @input='onInput' :value='localValue' />
<span>{{localValue}}</span>
</template>
<script>
export default{
data(){
return {
localValue:'',
}
},
methods:{
onInput(v){
//在input事件的处理函数中更新value的绑定值
this.localValue=v.target.value;
console.log(this.localValue)
}
}
}
</script>。