• Vue数据响应Object.defineProperty


    Vue数据响应

    在网页开发中,一般数据想要渲染到页面中,需要依赖于操作DOM更新。 VueReact则是使用数据驱动视图,也就是数据改变,DOM也相应完成变化。

    而数据变化更新DOM也分为侵入式和非侵入式

    侵入式和非侵入式

    Vue属于非侵入式,React小程序数据变化输入侵入式。

    侵入式设计,就是设计者将框架功能“推”给客户端;

    而非侵入式设计,则是设计者将客户端的功能“拿”到框架中用

    侵入式设计带来的最大缺陷是,代码需要依赖框架的代码,如果把框架拿掉或者换一个框架,就需要重新修改代码

    下面的例子中,vue改变a的值没有调用其他的API,而react小程序则调用了setStatesetData的API

    Vue:

    this.a++; 
    
    • 1

    React:

    this.setState({
        a: this.state.a + 1
    }); 
    
    • 1
    • 2
    • 3

    小程序:

    this.setData({
        a: this.state.a + 1
    }); 
    
    • 1
    • 2
    • 3

    Object.defineProperty() 数据代理

    当然,本文的重点在于Object.defineProperty

    在上面说到,在React和小程序当中,因为调用API方法改变数值,所以界面改变也很好理解,API方法对应会改变DOM。

    那么Vue改变数值时是非侵入式,那么界面应该响应数值改变而改变?

    在这里就需要提到Object.defineProperty数据代理了,MDN地址:developer.mozilla.org/zh-CN/docs/…

    这是一个JavaScript引擎的API,可以来检测对象属性的变化

    在vue3中使用proxy代替defineProperty,但是proxy具有兼容性问题,且无法polyfill (基本上IE浏览器可以放弃)

    参考: caniuse.com/?search=pro…

    之前在《前端面试中有趣的题目》 中也有用到过该方法去定义一个const

    Object.defineProperty中具有get和set方法,当Vue去改变a值时,a属性已经被set给劫持了。(get和set都需要变量进行周转)

    例子: 在下面会看到例子当中obj对象存在一个_a属性,这是为了对应之后设置agetset,现在修改obj.a的数值,也能够打印console中的语句。如果在set方法中添加updateView更新视图方法,那么就能够简单的达到了数据同步的效果

    let obj = {
        _a: 0
    }
    
    Object.defineProperty(obj, 'a', {
        get() {
            return this._a;
        },
        set(n) {
            console.log('设置a属性的值' + n);
            this._a = n;
            // 触发更新视图
            //updateView();
        }
    })
    
    obj.a = 0;  // 设置a属性的值0
    obj.a++;  // 设置a属性的值1
    obj.a++;  // 设置a属性的值2
    obj.a += 10;  // 设置a属性的值12
    console.log(obj.a);  // 12 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    封装一个defineProperty方法

    先定义一个等一下要测试的数据

    let personInfo = {
        name: 'zhangsan',
        age: 20,
        info: {
            address: '江苏'
        }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在上面的基础上,可以对其进行封装

    function defineProperty(object, key, data) {
        if (!data) {
            data = object[key]
        }
        Object.defineProperty(object, key, {
            get() {
                return data;
            },
            set(val) {
                if (data != val) {
                    console.log(`${key}的原值${data}被更新为:${val}`)
                    data = val;
                }
            }
        })
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    对其中的name属性调用方法进行代理,能够在控制台看到name的原值zhangsan被更新为:lisi被打印

    defineProperty(personInfo, 'name');
    personInfo.name = 'lisi'  // name的原值zhangsan被更新为:lisi 
    
    • 1
    • 2

    并且在此封装好的基础上,可以再写一个方法,对对象进行分析,遍历对象各个属性放入方法代理

    function observe(obj) {
        if (typeof obj != 'object') {
            return ;
        } 
        for(let key in obj) {
            observeReactive(obj, key);
        }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样一来层次不深的属性基本已经被defineProperty代理了

    observe(personInfo);
    personInfo.age = 30;  // age的原值20被更新为:30 
    
    • 1
    • 2

    不过如果是内部还有层次的就不行了,比如下面的例子,就没有打印出来

    personInfo.info.address = '浙江'; 
    
    • 1

    那么就对属性再做一次判断,如果还是对象,继续遍历

    function defineProperty(object, key, data) {
        if (!data) {
            data = object[key]
        }
        // 递归
        if (typeof data == 'object') {
            observe(data);
        }
        Object.defineProperty(object, key, {
            get() {
                return data;
            },
            set(val) {
                if (data != val) {
                    console.log(`${key}的原值${data}被更新为:${val}`)
                    data = val;
                    // 新增加对象也要observe
                    if (typeof val == 'object') {
                        observe(data);
                    }
                }
            }
        })
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这样一来address的原值江苏被更新为:浙江就能够被打印出来了


    Object.defineProperty缺点

    Object.defineProperty还是有挺多不足的

    • 比如上面说到的深度监听,需要递归到底,一次性的计算量很大。
    • 无法监听新增属性/删除属性 (所以在vue中会有Vue.setVue.delete)
    • 数据劫持并不能对数组的pushshift等方法生效生效,无法原生监听数组,需要特殊处理
  • 相关阅读:
    uniapp 扫码功能
    【算法|滑动窗口No.2】leetcode904. 水果成篮
    IO流文件相关部分
    [环境]Ubuntu20.04-SLAM测评工具-evo安装
    Android 复习string.xml中的替换符
    『现学现忘』Docker命令 — 18、镜像常用命令
    DSPE PEG Azide, DSPE-PEG-N3;磷脂聚乙二醇叠氮
    【基础篇】二、Flink的批处理和流处理API
    【优化调度】基于改进遗传算法的公交车调度排班优化的研究与实现(Matlab代码实现)
    3A通过pmp有多大比例?
  • 原文地址:https://blog.csdn.net/qq_53225741/article/details/125482400