关于Vue的数据响应式

Vue的数据响应式是怎么一回事?其实理解了之前我写过的一篇《从根源理解Proxy对比defineProperty优势》(原文链接:http://t.csdnimg.cn/DipXh),应该对Vue利用defineProperty实现数据劫持数据监听的方式有一定的了解。那我们这篇文章就以Vue2的defineProperty实现数据劫持数据监听的原理为基础,进一步展开讲述Vue是怎么进一步利用该数据劫持的方法实现数据响应式的。

<script>
const vm=new Vue({
    el:'#root',
    data:{
        firstName:'木',
        lastName:'鱼',
        isActive:true,
    },
    computed:{
        fullName(){
            return this.firstName+this.lastName
        }
    },
    watch:{
        isActive:{
            immediate:true,
            handler(newValue,oldValue){
                console.log('isActive被修改了');
            }
        }
    }
})
</script>

以这段代码为例,我们通过在computed、watch上指定某个函数或变量后,每当computed中的firstName、lastName发生变化时则会重新计算fullName,每当watch中的isActive发生变化,也会执行其handler函数。

实际上,对于数据响应式,Vue底层做的事情,就是监听指定的响应式数据(如定义在data上的数据),当监听的数据发生变化,则会自动重新运行依赖该数据的函数

为何computed、watch等上指定的数据也是响应式的?因为computed、watch这些函数都有依赖到响应式数据,数据一旦发生变化,computed、watch就会重新执行相应的函数。

 Vue做的事情也就是在数据变化时重新执行相关函数。那么为什么数据的变化会引起界面的更新?其实本质也是这个道理。界面是由render()函数运行渲染呈现的,而实际上Vue上定义的响应式数据都是囊括在render函数里的,因此响应式数据一旦发生改变,Vue底层就会自动重新运行依赖该数据的所有函数,包括render函数,因此页面也会重新渲染。该函数或变量所依赖的数据则都会是响应式的。

好了,明白了Vue数据响应式的本质,我们再来简述一下其实现的原理,也就是:响应式数据(被监听到的数据)发生变化,该怎么确保涉及到该数据的函数都执行一遍呢?

我们知道数据截取是基于defineProperty实现的。

在以下实现数据截取基础上我们逐一推进思考(如果看不懂以下代码请参照前篇 《从根源理解Proxy对比defineProperty优势》(原文链接:http://t.csdnimg.cn/DipXh):

function observe(obj){
    for(const key in obj){
        let initialValue=obj[key]
        Object.defineProperty(obj,key,{
            get(){
                console.log(`该函数的调用说明有人读取了${key}属性`)
                return initialValue
            },
            set(value){
                console.log(`该函数的调用说明有人将${key}属性的值修改为${value}`)
                initialValue=value
            }
        })
    }
}

1、备一个存放有依赖响应式数据的数组:响应式数据作为对象data的属性通过类似上述的observe函数截取到了,那么如何知道哪些函数调用了该数据呢?也就是要确认哪些函数调用了obj的key。那么我们可以定义一个数组funcs用于存放调用了这些数据的函数。当数据修改时(也就是set调用时),则遍历funcs数组里的函数,将每个数组里的函数都调用一遍。

2、通过"window.特定名称"拿到函数塞进数组中:那么我们怎么该怎么把调用的函数一个个push到funcs数组里呢?我们也不知道函数名称是什么,所以不如就将调用的函数都统一起一个名称,如叫"__func",框架也好实现了,只要调用到响应式数据的函数临时改名为__func,直接通过window.__func就可以拿到调用响应式数据的函数并将其往funcs数组里塞就行了。响应式数据key被调用了,则会触发get方法,这时再在get方法中用funcs.push(window.__func)即可(push时要注意保证数组元素单一性)。

//1、2思路代码实现如下
function observe(obj){
    for(const key in obj){
        let initialValue=obj[key]
        let funcs=[]//用于存放所有依赖到响应式数据key的函数
        Object.defineProperty(obj,key,{
            get(){
                console.log(`该函数的调用说明有人读取了${key}属性`)
                // 通过window.__func拿到依赖该响应式数据的函数
                if(window.__func && !funcs.includes(window.__func)){
                    funcs.push(window.__func)
                }
                return initialValue
            },
            set(value){
                console.log(`该函数的调用说明有人将${key}属性的值修改为${value}`)
                initialValue=value
                // 只要响应式数据改变,依赖到该数据的数组全都要再执行一遍
                for(let i in funcs){
                    funcs[i]()
                }
            }
        })
    }
}

那么只要我们通过__func拿到依赖响应式数据的函数,再通过以上代码,就可以实现将数据一变,依赖该数据的函数也跟着重新执行一遍了。

3、将所有调用响应式数据的函数临时改为统一的特定名称:那么接下来的问题就是如何把调用到响应式数据的函数临时改名为__func了。我们仅需在调用该函数的前将该函数赋值给window.__func即可,该函数执行完毕后,再将window.__func还原为null。我们可以通过定义一个autorun函数实现,如下:

function autorun(fn){
    window.__func=fn
    fn()
    window.__func=null
}

比如fullName()有依赖到响应式数据,则通过autorun(fullName)执行。

我们以computed为例,实际上在computed上定义fullName(),Vue底层就会通过类似autorun(fullName)的方式处理computed中的函数。而fullName中有依赖到firstName、lastName都是定义在data中的,都是Vue监听范围里的数据,这意味着一旦被调用就会触发其get函数

  • 因此,当fullName被模板读取调用时,则会执行autorun(fullName),此时window.__func正是fullName;
  • 而在执行fullName()时,fullName函数里会读取到firstName、lastName,则会调用到firstName、lastName的get函数;
  • 则firstName、lastName的get函数会将window.__func(此时也就是fullName)push进各自的funcs数组里(注意每个key各配一个funcs数组存依赖该key的函数);
  • 而后比如你修改了firstName,则firstName的set函数会执行,则所有依赖到firstName的函数(即firstName的funcs数组元素)都会被遍历执行一遍,因此fullName也会随之执行一遍,随之更新最新值。

这就是Vue响应式数据实现的整个过程。

  • 29
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值