Vue2响应式原理

  1. defineProperty——简单理解

组件的render函数会生成一个虚拟DOM树来影响界面

Vue会遍历Data数据对象的每一个属性,并且使用Object.defineProperty生成两个访问器函数getter和setter,调用属性时就会调用getter,修改属性时会调用setter函数。

Object.defineProperty(person,'age',{
    //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
    get(){
        console.log('有人读取age属性了')
        return number
    },
    //当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
    set(value){
        console.log('有人修改了age属性,且值是',value)
        number = value
    }
})
console.log(person)

要将render函数和使用Object.defineProperty链接起来,就是当render函数运行生成数据的时候,会运行getter访问器函数去读取。

// render中当我们使用h元素中的h1标签创建一个
// msg就是响应式数据
h("h1", this.msg)
// this.msg就会去调用getter访问器函数

getter会同时记录当前有一个render函数使用了自己——依赖收集

处于发布订阅模式的Watcher就会将依赖存储监听起来——有一个属性用到了render函数

如果后期需要修改这个属性,就会调用到setter访问器函数,setter就会通知到Watcher,Watcher之前又记录了这个属性之前是调用了render函数来收集依赖,所以又会调用render函数重新收集依赖达到了响应式的目的。

  1. 深入解析

响应式数据的最终目标,适当对象本身或对象属性发生变化时,将会运行一些函数,例如render函数。

运用到的核心部件:

  1. Observer

  1. Dep

  1. Watcher

  1. Scheduler

  1. Observer —— Vue内部的构造器

Observer的目的就是将普通对象转化为响应式的对象

实现原理:Ovserver循环遍历对象的每个属性通过Object.defineProperty转换为带有getter和setter的属性,这样在获取和修改的时候就会调用访问器函数,调用函数就能够被Vue监听到。

由于Observer是Vue内部的构造器,无法直接使用,但是可以通过Vue提供的静态方法Vue.observable{object}间接的使用该功能,在组件生命周期中,这件事会发生在beforeCreate之后,created之前。

var obj = {
    a:1,
    b: {
        c: 1
    }
}
Vue.observable(obj);
// 调用过后会将每一个属性变成响应式数据
// 通过getter调用展示

Observer会递归遍历对象,改变每一个属性。

由于以上原理,在created之后新增属性到data中的数据时,Vue2是无法检测到的(Vue3使用proxy解决了这一问题),所以Vue2提供了一个$set()和$delete()方法,来达到新增/删除响应式属性的目的。

数组问题:sort、push、pop都是浏览器中Array提供的,但是不会通知Vue,Vue为了处理这个问题将数组的隐式原型__proto__叠加了一层,自定义了一个对象让数组的__proto__指向这个Vue自定义对象,自定义对象再指向Array的隐式原型。

所以Vue是无法监听到数组中某一个下标的赋值和删除,还是只能通过$set和$delete才能。但数组中的对象是响应式的,可以被监听到。

arr[0] = 100 // 监听不到
arr[0].name = 100 // 可以监听

b. Dep

Observer只是将数据变成了可被监听的访问器,并没有记录下当前调用getter/setter时的函数组件,这个时候就需要另外一个构造器Dep——Dependency 来进行依赖的收集

所以在Observer数据处理后,Vue就会对每一个需要进行响应式处理的地方添加一个Dep对象实例

{ // dep
   a: 1, // dep
   b: 2, // dep
   c: [1,2,3] // dep
}

dep实例的作用:

  1. 记录依赖:谁在调用

  1. 派发更新:当监听到变化时,通知调用该实例的对象

当改变某个属性的时候,会派发更新到每一个调用的对象。

c. Watcher

Dep如何知道谁在调用这个函数

当某个函数执行中,用到了响应式数据,响应式数据是无法知道到底是哪个函数在调用自己的

Vue采用的方式就是,不让Dep记录时本身这个函数来执行,而是必须将所有函数交给watcher执行,wathcer是一个对象,每一个需要派发更新的函数执行都应该new一个watcher,通过这个watcher来执行

watcher会设置一个全局的变量,让全局变量记录当前负责执行的watcher等于自己,然后再去执行函数,在函数执行过程中,如果发生了依赖记录dep.depend()那么Dep就会把这个全局变量记录下来,表示有一个watcher用到了我这个属性

当Dep进行派发更新时,就会通知以前记录的所有watcher

所以每一个vue组件实例,都至少对应一个watcher,记录该组件的render函数。

watcher首先会把render函数运行一次来收集依赖,所以在render中用到的响应式数据就会记录这个watcher用到了它。

当数据发生变化时,dep就会通知该watcher,而watcher将重新运行render函数,从而让界面重新渲染同时重新记录依赖。

d. Scheduler —— 调度器

通过前三个核心构造器,已经完成了响应式数据的创建,记录,更新,但是出现了一个新的问题,每一次数据的改变都会导致watcher执行的话,就会导致函数频繁运行,导致代码低下。

所以watcher不能是一个立即执行函数,需要形成一个队列管理,这个队列就是调度器。

调度器在维护一个执行队列,该队列同一个watcher只会存在一次,队列中的watcher都不是立即执行,而是通过nextTick的工具方法,把要执行的watcher放入到事件循环的微队列中(不同浏览器中放入微队列的方法是不同的,会考虑到兼容性,是否兼容promise,定时器等方法),nextTick的具体就是通过Promise完成的。

也就是说,当响应式发生数据变化时,render函数的执行是异步的,并且在微队列中。

3.总体流程图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值