proxy in Vue3 is compared with vue2 Object in 0 Advantages of defineproperty

Posted by netcoord99 on Mon, 07 Mar 2022 15:28:54 +0100

1, Object defineProperty

Definition: object The defineproperty () method will directly define a new property on an object, or modify an existing property of an object and return the object

Why can we achieve responsiveness

Define set property and define get property

  • get

Property. This function will be called when the property is accessed. No parameters are passed in during execution, but this object will be passed in (due to inheritance, this here is not necessarily the object that defines this attribute). The return value of this function is used as the value of the property

  • set

Property. This function will be called when the property value is modified. This method accepts a parameter (that is, the new value assigned) and passes in the this object at the time of assignment. The default is undefined

The following code shows:

Define a responsive function defineReactive

function update() {
    app.innerText = obj.foo
}

function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
        get() {
            console.log(`get ${key}:${val}`);
            return val
        },
        set(newVal) {
            if (newVal !== val) {
                val = newVal
                update()
            }
        }
    })
}

Call defineReactive, and the update method is triggered when the data changes, so as to realize the data response

const obj = {}
defineReactive(obj, 'foo', '')
setTimeout(()=>{
    obj.foo = new Date().toLocaleTimeString()
},1000)

When there are multiple key s in the object, traversal is required

function observe(obj) {
    if (typeof obj !== 'object' || obj == null) {
        return
    }
    Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key])
    })
}

If there are nested objects, you also need to recurse in defineReactive

function defineReactive(obj, key, val) {
    observe(val)
    Object.defineProperty(obj, key, {
        get() {
            console.log(`get ${key}:${val}`);
            return val
        },
        set(newVal) {
            if (newVal !== val) {
                val = newVal
                update()
            }
        }
    })
}

When assigning a key as an object, you also need to recurse in the set attribute

set(newVal) {
    if (newVal !== val) {
        observe(newVal) //The new value is the case of the object
        notifyUpdate()
    }
}

The above example can realize the basic response to an object, but there are still many problems

You can't hijack an object by deleting and adding attributes

const obj = {
    foo: "foo",
    bar: "bar"
}
observe(obj)
delete obj.foo // no ok
obj.jar = 'xxx' // no ok

When we listen to an array, it's not so easy

const arrData = [1,2,3,4,5];
arrData.forEach((val,index)=>{
    defineProperty(arrData,index,val)
})
arrData.push() // no ok
arrData.pop()  // no ok
arrDate[0] = 99 // ok

It can be seen that the api of the data cannot be hijacked, so the data response cannot be realized,

Therefore, in Vue2, set and delete APIs are added, and the array API method is rewritten

Another problem is that if there is a deep nested object relationship, it needs deep listening, resulting in a great performance problem

Summary

  • The addition and deletion of object properties were not detected

  • Array API method cannot listen

  • Each attribute needs to be traversed and monitored. If nested objects need to be deeply monitored, resulting in performance problems

2, proxy

Proxy listening is for an object, so all operations on this object will enter the listening operation, which can completely proxy all properties

In the ES6 series, we have explained the use of Proxy in detail, so we won't talk about it any more

The following is shown by code:

Define a reactive method

function reactive(obj) {
    if (typeof obj !== 'object' && obj != null) {
        return obj
    }
    //Proxy is equivalent to adding interception on the outer layer of the object
    const observed = new Proxy(obj, {
        get(target, key, receiver) {
            const res = Reflect.get(target, key, receiver)
            console.log(`obtain ${key}:${res}`)
            return res
        },
        set(target, key, value, receiver) {
            const res = Reflect.set(target, key, value, receiver)
            console.log(`set up ${key}:${value}`)
            return res
        },
        deleteProperty(target, key) {
            const res = Reflect.deleteProperty(target, key)
            console.log(`delete ${key}:${res}`)
            return res
        }
    })
    return observed
}

Test the operation of simple data and find that it can be hijacked

const state = reactive({
    foo: 'foo'
})
// 1. obtain
state.foo // ok
// 2. Set existing properties
state.foo = 'fooooooo' // ok
// 3. Set non-existent property
state.dong = 'dong' // ok
// 4. Delete attribute
delete state.dong // ok

Then test the nested objects. At this time, it is found that it is not so OK

const state = reactive({
    bar: { a: 1 }
})

//Set nested object properties
state.bar.a = 10 // no ok

If you want to solve this problem, you need another layer of proxy on top of get

function reactive(obj) {
    if (typeof obj !== 'object' && obj != null) {
        return obj
    }
    //Proxy is equivalent to adding interception on the outer layer of the object
    const observed = new Proxy(obj, {
        get(target, key, receiver) {
            const res = Reflect.get(target, key, receiver)
            console.log(`obtain ${key}:${res}`)
            return isObject(res) ? reactive(res) : res
        },
    return observed
}

3, Summary

Object.defineProperty can only traverse object properties for hijacking

function observe(obj) {
    if (typeof obj !== 'object' || obj == null) {
        return
    }
    Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key])
    })
}

Proxy can directly hijack the whole object and return a new object. We can only operate the new object to achieve the purpose of responsiveness

function reactive(obj) {
    if (typeof obj !== 'object' && obj != null) {
        return obj
    }
    //Proxy is equivalent to adding interception on the outer layer of the object
    const observed = new Proxy(obj, {
        get(target, key, receiver) {
            const res = Reflect.get(target, key, receiver)
            console.log(`obtain ${key}:${res}`)
            return res
        },
        set(target, key, value, receiver) {
            const res = Reflect.set(target, key, value, receiver)
            console.log(`set up ${key}:${value}`)
            return res
        },
        deleteProperty(target, key) {
            const res = Reflect.deleteProperty(target, key)
            console.log(`delete ${key}:${res}`)
            return res
        }
    })
    return observed
}

Proxy can directly monitor the changes of the array (push, shift, splice)

const obj = [1,2,3]
const proxtObj = reactive(obj)
obj.psuh(4) // ok

Proxy has up to 13 interception methods, not limited to apply, ownKeys, deleteProperty, has, etc. This is object Defineproperty does not have

Because of the defects of defineProperty itself, Vue2 needs to implement other auxiliary methods (such as rewriting array methods, adding additional set and delete methods) in the process of implementing responsiveness

//Array rewriting
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto)
['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => {
  arrayProto[method] = function () {
    originalProto[method].apply(this.arguments)
    dep.notice()
  }
});

// set,delete
Vue.set(obj,'bar','newbar')
Vue.delete(obj),'bar')

Proxy , is not compatible with ie, nor does , polyfill, defineProperty , support IE9

Topics: Vue.js