I am reviewing Vue recently, and I will inevitably come into contact with vue3, so I will inevitably think about these issues.

  1. Why use proxy to replace Object.defineProperty in vue3 to achieve responsiveness? What are the advantages and disadvantages of Proxy compared to Object.defineProperty?
  2. How to achieve responsiveness through Proxy?

This article will answer these two questions and discuss Proxy through these questions and the application scenarios of Proxy in daily development.

Meet Proxy

The translation of Proxy means proxy . The outside world's access to the target object will be intercepted by the Proxy, so that the basic operation can be intercepted and customized.

usage

let proxy = new Proxy(target,handler)
  • target: the target object to be intercepted
  • handler: A handler is an object that contains the object you want to intercept and process. When the object is proxied, the handler implements the interception of various behaviors through the trap.

Currently proxy supports the interception of 13 behaviors

handler methodWhen to trigger
getRead attributes
setWrite attributes
hasin operator
deletePropertydelete operator
applyFunction call
constructnew operator
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 translates to the meaning of mapping, which is defined on MDN

Reflect is a built-in object that provides methods for intercepting JavaScript operations.

Each available proxy trap has a corresponding Reflect function with the same name and can produce the same behavior.

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

It can be seen from this that Reflect and trap exhibit the same behavior. So when you worry about how to trigger the trap, maybe this Reflect can help you.

Two questions

After roughly learning the content of proxy, let's try to answer the two questions mentioned at the head of the next page.

Why use proxy to replace Object.defineProperty in vue3 to achieve responsiveness? Pros and cons?

Advantages

  • performance is better , Object.defineProperty can only hijack the properties of the object, so if there are nested objects, you need to traverse each property in the data during initialization. In vue3, the proxy can proxy the object, and you don’t need to do the properties like vue2. Perform traversal operations
//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操作
})
  • automatically proxy new properties, the array , the realization of Object.defineProperty hijacks the properties, so when adding new properties, you need to re-traverse and hijack the new ones again. So vue2 need for new property, as well as an array $ the SET to ensure the property is responsive, this process is manual.
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 supports 13 interception operations, Object.defineProperty cannot compare
  • Proxy is a new standard and will be optimized in the follow-up. The setter and getter of Object.defineProperty should have a lower priority in the follow-up optimization.

Disadvantages

Obviously, the compatibility of Proxy is lower than that of Object.defineProperty, and it does not support Internet Explorer. However, from the current market share, the IE browser's market share is not much. At present, Microsoft has also replaced IE with the edge of the chrome kernel, so it is completely possible to use proxy for aggressive projects.

image-20211202224940291.png

How to achieve responsiveness through 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 application scenario

  • Write the default value, daily development often encounter ReferenceError: xxx is not defined this kind of error, here we can proxy, when the attribute does not exist, do not report an error, but set a default value
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
  • Use Proxy to wrap fetch to make fetch easier to use
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})
})
  • Check form
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
  • Negative index array
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

kerin
497 声望573 粉丝

前端菜鸟