Vue数据响应Object.defineProperty

举报
空城机 发表于 2022/04/18 21:36:16 2022/04/18
【摘要】 Vue是使用数据驱动视图,也就是数据改变,DOM也相应完成变化,并且因为是非侵入式的数据响应,所以需要一个关键方法Object.defineProperty

Vue数据响应

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

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

侵入式和非侵入式

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

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

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

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

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

Vue:

this.a++;

React:

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

小程序:

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

Object.defineProperty() 数据代理

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

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

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

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

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

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

参考: https://caniuse.com/?search=proxy

之前在《前端面试中有趣的题目》 中也有用到过该方法去定义一个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

封装一个defineProperty方法

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

let personInfo = {
    name: 'zhangsan',
    age: 20,
    info: {
        address: '江苏'
    }
}

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

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;
            }
        }
    })
}

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

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

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

function observe(obj) {
    if (typeof obj != 'object') {
        return ;
    } 
    for(let key in obj) {
        observeReactive(obj, key);
    }
}

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

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

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

personInfo.info.address = '浙江';

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

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);
                }
            }
        }
    })
}

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


Object.defineProperty缺点

Object.defineProperty还是有挺多不足的

  • 比如上面说到的深度监听,需要递归到底,一次性的计算量很大。
  • 无法监听新增属性/删除属性 (所以在vue中会有Vue.setVue.delete)
  • 数据劫持并不能对数组的pushshift等方法生效生效,无法原生监听数组,需要特殊处理
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。