最近在温习Vue,不可避免的会接触到vue3,所以也不可避免的会思考这些问题

  1. vue3实现响应式为什么要应用proxy替换Object.defineProperty?Proxy比照Object.defineProperty有啥优缺点?
  2. 怎么通过Proxy实现响应式?

本文会答复这两个问题,通过这些问题探讨Proxy,以及Proxy在日常开发中的利用场景。

意识Proxy

Proxy意思翻译过去就是代理,外界对指标对象的拜访都会被Proxy拦挡,从而能够实现基本操作的拦挡和自定义。

用法

let proxy = new Proxy(target,handler)
  • target: 所要拦挡的指标对象
  • handler: handler是一个蕴含你要拦挡和解决的对象,当对象被代理时,handler通过捕获器(trap)实现对各种行为的拦挡

目前proxy反对13种行为的拦挡

handler办法何时触发
get读取属性
set写入属性
hasin操作符
deletePropertydelete操作符
apply函数调用
constructnew操作符
getPrototypeOfObject.getPrototypeOf
setPrototypeOfObject.setPrototypeOf
isExtensibleObject.isExtensible
preventExtensionsObject.preventExtensions
definePropertyObject.defineProperty,
Object.defineProperties
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor,
for...in,
Object.keys/values/entries
ownKeysObject.getOwnPropertyNames,
Object.getOwnPropertySymbols,
for...in,
Object.keys/values/entries

Reflect

reflect翻译过去是映射的意思,在MDN上是这样定义的

Reflect 是一个内置的对象,它提供拦挡 JavaScript 操作的办法。

每个可用的代理捕获器(trap)都有一个对应的同名Reflect函数,并能产生雷同的行为。

let obj = {    a: 10,    name: 'oyc'}let newTarget = new Proxy(obj, {    set(target, key, val) {        console.log(`Set ${key}=${val} `);    }})// newTarget.a = 20;  //Set a=20// Reflect.set(newTarget, 'a', 20); //Set a=20newTarget.name = 'oyq'; //Set name=oyqReflect.set(newTarget, 'name', 'oyq'); //Set name=oyq

从这能够看出,Reflect和trap体现进去的行为是雷同的。所以当你为如何去触发trap而懊恼的时候,兴许这个Reflect能够帮到你。

两个问题

大抵学习完proxy的内容后,再来尝试解答下页头提到的两个个问题。

vue3实现响应式为什么要应用proxy替换Object.defineProperty?优缺点?

长处

  • 性能更好,Object.defineProperty只能劫持对象的属性,所以如果有嵌套对象,初始化时须要遍历data中的每个属性,在vue3中,proxy能够代理对象,不须要像vue2那样对属性进行遍历的操作
//vue2function reactive(obj) {    // 遍历对象    for (const item in obj) {        if (obj[item] && typeof obj[item] == 'object') {            // 递归,又从新遍历            reactive(obj[item])        } else {            defineReactive(obj, item, obj[item])        }    }}function defineReactive(obj, key, val) {    Object.defineProperty(obj, key, {        //set,get操作    })}//vue3let newTarget = new Proxy(obj, {  // set,get操作})
  • 主动代理新增属性,数组,Object.defineProperty的实现是对属性进行劫持,所以当新增属性时,须要从新遍历,对新增的从新进行劫持。所以须要vue2对新增的属性,以及数组进行 $set 能力保障属性是响应式的,这个过程是手动的。
let obj = {  a: 10,  name: 'oyc'}//vue2this.$set(this.obj, 'age', 18); //每次新增都须要进行这个操作//vue3//主动代理let newTarget = new Proxy(obj, {  get(target, key) {    return Reflect.get(target, key);  },  set(target, key, val) {    return Reflect.set(target, key, val);  }})
  • Proxy反对13中拦挡操作,Object.defineProperty无法比拟
  • Proxy是新规范,后续也会优先优化,Object.defineProperty的setter,getter后续应该优化的优先级较低

毛病

不言而喻的,Proxy的兼容性相较于Object.defineProperty,是较低的,不反对IE浏览器。不过以目前的市场份额来看,IE浏览器的市场份额也不多,目前微软也将ie换成了chrome内核的edge,所以激进点的我的项目是齐全能够应用proxy的。

怎么通过Proxy实现响应式?

let obj1 = {    a: 10,    name: 'John',    list: [1, 2, 3],    obj2: {        obj3: 'oo',        obj4: {            name: 'oyc'        }    }}// 判断是否是对象const isObj = (obj) => typeof obj === 'object' && obj !== null;const render = (key, val) => {    console.log(`Render ${key}=${val}`);}function reactive(obj) {    if (!isObj(obj)) {        return obj;    }    const handler = {        get(target, key) {            // 对嵌套对象遍历            if (isObj(target[key])) {                // 递归                return reactive(target[key]);            }            return Reflect.get(target, key);        },        set(target, key, val) {            // 渲染            render(key, val);            return Reflect.set(target, key, val);        }    }    const targetProxyObj = new Proxy(obj, handler);    return targetProxyObj}let myObj = reactive(obj1);myObj.a = 20; // Render a=20myObj.b = 30; //新增属性 Render b=30myObj.list = [1, 2, 5, 6]; //批改数组 //Render list=1,2,5,6myObj.obj2.obj4.name = 'oyq'; //批改嵌套对象 //Render name=oyq

Proxy利用场景

  • 写入默认值,日常开发常常碰到 ReferenceError: xxx is not defined 这种谬误,这里咱们能够代理,当属性不存在时,不报错,而设置一个默认值
let obj = {    name: 'oyq'}let proxyObj = new Proxy(obj, {    get(target, key) {        if (Reflect.has(target, key)) {            return target[key];        } else {            return 'OYC';        }    },})console.log(proxyObj.age);//OYC
  • 用Proxy来包装fetch,让fetch更易用
let handlers = {  get (target, property) {    if (!target.init) {      // 初始化对象      ['GET', 'POST'].forEach(method => {        target[method] = (url, params = {}) => {          return fetch(url, {            headers: {              'content-type': 'application/json'            },            mode: 'cors',            credentials: 'same-origin',            method,            ...params          }).then(response => response.json())        }      })    }    return target[property]  }}let API = new Proxy({}, handlers)await API.GET('XXX')await API.POST('XXX', {  body: JSON.stringify({name: 1})})
  • 测验表单
let formData = {    name: '',    age: ''}let proxyObj = new Proxy(formData, {    set(target, key, val) {        if (key === 'age' && typeof val !== 'number') {            console.log('age must be number');        }        if (key === 'name' && typeof val !== 'string') {            console.log('name must be string');        }    }})proxyObj.age = 'oyc'; //age must be numberproxyObj.name = 18; //name must be string
  • 负索引数组
let arr = [1, 2, 3, 4, 5]let proxyArray = new Proxy(arr, {    get(target, key) {        const index = key < 0 ? target.length + Number(key) : key;        return Reflect.get(target, index)    }})console.log(proxyArray[-1]); //5