如果要说JS新加的Proxy代理与Reflect反射,就不得不提VUE3了。(刚好前不久学了VUE3,最近实习又在做VUE3的项目哈哈哈~)
Vue2的数据响应式
我们都知道Vue2的数据代理是有问题的,啥问题?那就是Vue2本身对对象和数组的数据代理是不全的。如何说呢?
<template>
<h1 v-if="person.name">姓名:{{person.name}}</h1>
<h1 v-if="person.sex">性别:{{person.sex}}</h1>
<h1>{{person.loves}}</h1>
<button @click="addSex">添加性别</button>
<button @click="deleteName">删除姓名</button>
<button @click="editLoves">修改爱好</button>
<button @click="deleteLoves">删除第一个爱好</button>
</template>
<script>
export default {
data() {
return {
person:{
name:'ly',
age:18,
loves:['吃饭','睡觉']
}
}
},
methods:{
addSex() {
this.person.sex = '男';
},
deleteName(){
delete this.person.name;
},
editLoves() {
this.person.loves[0] = '玩游戏';
},
deleteLoves() {
delete this.loves[0];
}
}
}
</script>
但是当我们点击添加性别后,我们发现页面并没有出现性别:男,我们点击删除姓名,页面也并没有删除,我们点击修改爱好和删除第一个爱好按钮页面也都没有刷新.......这就是vue2的数据代理的缺陷,因为Vue2是使用的Object.defineProperty()进行的数据代理,但是这个方法只有get和set方法,也就是说,它并不能监测到给一个对象增加属性和删除属性。也不能监测到直接修改数组本身。
所以Vue2为了解决这个问题,就重新写了Vue.set和Vue.delete方法来监测对象的属性进行添加和删除,也重写了数组的7大方法(会修改数组本身的7大方法,分别是push、pop、shift、unshift、reverse、sort、splice)来监测数组的变化。
Vue2的解决办法:
<template>
<h1 v-if="person.name">姓名:{{person.name}}</h1>
<h1 v-if="person.sex">性别:{{person.sex}}</h1>
<h1>{{person.loves}}</h1>
<button @click="addSex">添加性别</button>
<button @click="deleteName">删除姓名</button>
<button @click="editLoves">修改爱好</button>
<button @click="deleteLoves">删除第一个爱好</button>
</template>
<script>
import Vue from 'vue.js '
export default {
data() {
return {
person:{
name:'ly',
age:18,
loves:['吃饭','睡觉']
}
}
},
methods:{
addSex() {
this.$set(this.person,'sex','男');
//Vue.set(this.person,'sex','男')
},
deleteName(){
this.$delete(this.person,'name');
//Vue.delete(this.person,'name');
},
editLoves() {
this.person.loves.splice(0,1,'玩游戏');
//this.$set(this.person.loves,0,'玩游戏');
//Vue.set(this.person.loves,0,'玩游戏');
},
deleteLoves() {
this.person.loves.unshift();
//this.person.loves.splice(0,1);
...
}
}
}
</script>
Vue2实现的原理:(操作数组的时候,不会触发set)
let data = {person:{name:'ly',age:18},loves:['1','2']}
observe(data)
function observe(target) {
// 如果不是对象或者数组就直接return 这也是递归结束的条件
if (typeof target !== 'object' || target === null) return
// 遍历对象每个属性 对每个属性添加响应式
for(let k in target) {
defineReactive(target, k, target[k])
}
}
function defineReactive(target, key, value) {
// value子属性的值 对其observe一下,看是否是对象类型,是的话就递归
observe(value)
// 再让这个子属性添加响应式
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue === value) return
// 设置新值也要observe一下
observe(newValue)
value = newValue
console.log('更新视图')
}
})
}
对数组进行响应式操作:
let data = {person:{name:'ly',age:18},loves:['1','2']}
observe(data)
const oldArrayProto = Array.prototype
let newArrayProto = Object.create(oldArrayProto)
let arr = ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort']
arr.forEach(methodName => {
newArrayProto[methodName] = function() {
console.log('自定义处理')
// 保留原来的处理结果
return oldArrayProto[methodName].call(this, ...arguments)
}
})
function observe(target) {
// 如果不是对象或者数组就直接按照原值return 这也是地柜结束的条件
if (typeof target !== 'object' || target === null) return target
// 遍历对象每个属性 对每个属性添加响应式
if (Array.isArray(target)) {
Object.setPrototypeOf(target, newArrayProto)
}
for(let k in target) {
defineReactive(target, k, target[k])
}
}
function defineReactive(target, key, value) {
// value子属性的值 对其observe一下 递归
observe(value)
// 再让这个子属性添加响应式
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue === value) return
// 设置新值也要observe一下
observe(newValue)
value = newValue
console.log('更新视图')
}
})
}
Proxy
Proxy是什么:
- Proxy是一个构造函数,接收两个参数,且需使用
new关键字进行创造proxy实例对象。 - 第一个参数是要代理的源对象。
- 第二个参数是配置项。
let person = {
name:'ly',
age:18
};
let p = new Proxy(person,{})
我们可以发现,这已经完成了数据的代理了,但是还没有完成数据的响应式。因为我们的配置项是空的。
Proxy的配置项里有很多,但是常用的就是有get、set、apply、deleteProperty、has、constructor...
对对象进行增删查改代理:
let person = {
name:'ly',
age:18
};
let p = new Proxy(person,{
set(target,property,value){
console.log('set 响应式');
return target[property] = value;
},
get(target,property){
console.log('get 响应式');
return target[property];
},
deleteProperty(target,property){
console.log('delete 响应式');
return delete target[property];
}
})
对数组进行增删查改代理:
let loves = ['玩','吃'];
let p = new Proxy(loves,{
get(arr,index) {
console.log('get 响应式');
return arr[index];
},
set(arr,index,value) {
console.log('set 响应式');
return arr[index] = value;
},
deleteProperty(arr,index) {
return delete arr[index];
}
})
对函数进行代理:
let fn = function(num) {
return num*2;
}
let p = new Proxy(fn,{
apply(target,context,argumentsList) {
console.log('apply 函数响应');
return target.apply(this,argumentsList)
}
})
p(3); // 6
// 参数说明:
// target
// ->目标对象(函数)。
// thisArg
// ->被调用时的上下文对象。
// argumentsList
// ->被调用时的参数数组。
Reflect
Reflect --- ‘反射’.
在Window这个全局对象上,有那么一个属性,叫Reflect。它是一个对象,它身上有很多方法,而巧的是,这些方法,在Proxy的配置项中都有。比如get、set......而巧的还是他们的参数都是一模一样的。
他在MDN上面的介绍是这样的:与大多数全局对象不同
Reflect并非一个构造函数,所以不能通过new 运算符对其进行调用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的(就像Math)对象)。
Reflect对象提供了以下静态方法,这些方法与 [proxy handler methods (en-US)] 的命名相同。其中的一些方法与Object相同,尽管二者之间存在某些细微上的差别。
let obj = {
name:'ly'
}
Reflect.get(obj,name); // 'ly'
Reflect.set(obj,'sex','男');
console.log(obj.sex); // '男'
所以会Proxy的配置项咋写,也就会Reflect的方法咋写了。
Vue3的代理
在vue3中,对于引用类型,Vue官方使用了Proxy代替了Object.defineProperty来进行响应式处理(原始数据类型,还是使用的Object.definePropperty)。
Proxy搭配Reflect:
let person = {
name:'ly',
age:18
};
let p = new Proxy(person,{
set(...args){
console.log('set 响应式');
return Reflect.set(...args);
},
get(...args){
console.log('get 响应式');
return Reflect.get(...args);
},
deleteProperty(...args){
console.log('delete 响应式');
return Reflect.deleteProperty(...args);
}
})
Vue3就是这样进行数据的响应式的!!!