关于前端:初识Proxy

最近在温习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 写入属性
has in操作符
deleteProperty delete操作符
apply 函数调用
construct new操作符
getPrototypeOf Object.getPrototypeOf
setPrototypeOf Object.setPrototypeOf
isExtensible Object.isExtensible
preventExtensions Object.preventExtensions
defineProperty Object.defineProperty,
Object.defineProperties
getOwnPropertyDescriptor Object.getOwnPropertyDescriptor,
for…in,
Object.keys/values/entries
ownKeys Object.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=20

newTarget.name = 'oyq'; //Set name=oyq

Reflect.set(newTarget, 'name', 'oyq'); //Set name=oyq

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

两个问题

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

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

长处

  • 性能更好,Object.defineProperty只能劫持对象的属性,所以如果有嵌套对象,初始化时须要遍历data中的每个属性,在vue3中,proxy能够代理对象,不须要像vue2那样对属性进行遍历的操作
//vue2
function 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操作
    })
}


//vue3
let newTarget = new Proxy(obj, {
  // set,get操作
})
  • 主动代理新增属性,数组,Object.defineProperty的实现是对属性进行劫持,所以当新增属性时,须要从新遍历,对新增的从新进行劫持。所以须要vue2对新增的属性,以及数组进行 $set 能力保障属性是响应式的,这个过程是手动的。
let obj = {
  a: 10,
  name: 'oyc'
}
//vue2
this.$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=20
myObj.b = 30; //新增属性 Render b=30
myObj.list = [1, 2, 5, 6]; //批改数组 //Render list=1,2,5,6
myObj.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 number
proxyObj.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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理