• 【前端知识之Vue3】defineProperty和proxy的区别


    前言

    本系列主要整理前端面试中需要掌握的知识点。本节介绍defineProperty和proxy的区别。


    一、Object.defineProperty

    Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象现有的属性,并返回此对象。defineProperty通过get和set实现响应式。

    • get:属性的getter函数,当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this对象。该函数的返回值会被用作属性的值。
    • set:属性的setter函数,当属性值被修改时,会调用此函数。该方法接收一个参数,会传入赋值时的this对象,默认为undefined。
    • 定义一个响应式函数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()
                }
            }
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 调用defineReactive,数据发生变化触发update方法,实现数据响应式。
    const obj = {}
    defineReactive(obj, 'foo', '')
    obj.foo = "foo"  //可以触发响应式,因为触发了update函数,foo显示在了dom元素中。
    
    • 1
    • 2
    • 3
    • 在对象存在多个key情况下,需要进行遍历,如果存在嵌套对象的情况,还需要在defineReactive中进行递归,当给key赋值为对象的时候,还需要在set属性中进行递归。
    function defineReactive(obj, key, val) {
        // 在对象存在多个key情况下,需要进行遍历
        observe(val)
        Object.defineProperty(obj, key, {
            get() {
                console.log(`get ${key}:${val}`);
                return val
            },
            set(newVal) {
                if (newVal !== val) {
                    observe(newVal) // 新值是对象的情况
                    update()
                }
            }
        })
    }
    // 在对象存在多个key情况下,需要进行遍历
    function observe(obj) {
        if (typeof obj !== 'object' || obj == null) {
            return
        }
        Object.keys(obj).forEach(key => {
            defineReactive(obj, key, obj[key])
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 上述例子能够实现对一个对象的基本响应式,但仍然存在诸多问题,比如对一个对象进行删除与添加属性操作,是无法劫持到的。
    const obj = {
        foo: "foo",
        bar: "bar"
    }
    observe(obj)
    obj.foo = "new"  // ok
    delete obj.foo // no ok
    obj.jar = 'xxx' // no ok
    console.log(obj);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 虽然这里打印obj,可以发现对象已经被修改了,那为什么还说没有触发响应式呢?原因是,响应式的触发是根据get和set判断的,如果触发了get,根据代码,可以打印get ${key}:${val},如果触发set,页面的dom元素的text会发生更改,但是在进行删除和增加属性的时候,都没有触发过get和set,因此是没有触发响应式的。而给foo赋新值的时候,打印了get foo:foo,此时就是触发了get。

    二、Proxy

    • Proxy的监听是针对一个对象的,对整个对象的所有操作都会进入监听操作,这样就可以完全代理所有属性了。
    • 定义一个响应式方法reactive
    function reactive(obj) {
        if (typeof obj !== 'object' && obj != null) {
            return obj
        }
        // Proxy相当于在对象外层加拦截
        const observed = new Proxy(obj, {
            get(target, key, receiver) {
                const res = Reflect.get(target, key, receiver)
                console.log(`获取${key}:${res}`)
                return res
            },
            set(target, key, value, receiver) {
                const res = Reflect.set(target, key, value, receiver)
                console.log(`设置${key}:${value}`)
                return res
            },
            deleteProperty(target, key) {
                const res = Reflect.deleteProperty(target, key)
                console.log(`删除${key}:${res}`)
                return res
            }
        })
        return observed
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 测试简单的数据操作,发现都能劫持,但是嵌套对象就不可以了。
    const state = reactive({
        foo: 'foo'
    })
    // 1.获取
    state.foo // ok
    // 2.设置已存在属性
    state.foo = 'fooooooo' // ok
    // 3.设置不存在属性
    state.dong = 'dong' // ok
    // 4.删除属性
    delete state.dong // ok
    // 5.嵌套对象
    const state1 = reactive({
        bar: { a: 1 }
    })
    
    // 设置嵌套对象属性
    state1.bar.a = 10 // no ok
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 如果要解决,需要在get之上再进行一层代理
    function reactive(obj) {
        if (typeof obj !== 'object' && obj != null) {
            return obj
        }
        // Proxy相当于在对象外层加拦截
        const observed = new Proxy(obj, {
            get(target, key, receiver) {
                const res = Reflect.get(target, key, receiver)
                console.log(`获取${key}:${res}`)
                return typeof(res) == "object" ? reactive(res) : res
            },
            set(target, key, value, receiver) {
                const res = Reflect.set(target, key, value, receiver)
                console.log(`设置${key}:${value}`)
                return res
            },
            deleteProperty(target, key) {
                const res = Reflect.deleteProperty(target, key)
                console.log(`删除${key}:${res}`)
                return res
            }
        })
        return observed
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    三、总结

    • Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象现有属性,并返回此对象。但是,如果存在深层的嵌套对象关系,需要深层次的进行监听,造成了性能的极大问题。
    • Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性,包括新增属性和删除属性,并且Proxy可以监听数组的变化。
  • 相关阅读:
    FPGA基础协议三:SPI读取FLASH
    RSTP详解:对比STP,到底改进了什么?
    时间序列分析(二)--指数平滑
    再获5G RedCap能力认证!宏电5G RedCap工业智能网关通过中国联通5G物联网OPENLAB开放实验室测试验证
    【前端系列】前端如何使用websocket发送消息
    2022.9.29
    【Quark RISC-V】流水线CPU设计(4)数据冒险的处理(主要解决方案:流水线暂停、数据转发、乱序执行)
    influxdb时序库之Python操作
    优品汇系统开发机制介绍
    css 文字单行多行超出长度后显示 ...
  • 原文地址:https://blog.csdn.net/weixin_44337386/article/details/126247023