javascript进阶知识29 - Proxy与Reflect

801 阅读5分钟

如果要说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()进行的数据代理,但是这个方法只有getset方法,也就是说,它并不能监测到给一个对象增加属性和删除属性。也不能监测到直接修改数组本身。

所以Vue2为了解决这个问题,就重新写了Vue.setVue.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,{})

image.png

我们可以发现,这已经完成了数据的代理了,但是还没有完成数据的响应式。因为我们的配置项是空的。

Proxy的配置项里有很多,但是常用的就是有getsetapplydeletePropertyhasconstructor...

对对象进行增删查改代理:

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]; 
    }
})

image.png

对数组进行增删查改代理:

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];
    }
})

image.png

对函数进行代理:

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的配置项中都有。比如getset......而巧的还是他们的参数都是一模一样的。

他在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就是这样进行数据的响应式的!!!