Vue数据响应Object.defineProperty

Posted 空城机

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue数据响应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等方法生效生效,无法原生监听数组,需要特殊处理

以上是关于Vue数据响应Object.defineProperty的主要内容,如果未能解决你的问题,请参考以下文章

Proxy(vue响应式原理:数据侦测--数据劫持和数据代理)

vue3修改响应式代理值

vue源码之响应式数据

能说说vue的响应式原理吗?

vue2数据响应式原理

vue响应数据的原理