Vue原理

本文详细探讨了Vue框架的初始化流程,包括Vue实例的创建、data数据的处理及响应式转换。通过分析`_ob__`标识、Watcher类和Dep类的实现,揭示了依赖收集和数据更新的机制。同时,解释了数组为何不直接使用`defineProperty`进行响应式处理,以及Vue如何实现异步更新队列。此外,还详细阐述了数组的依赖收集和更新触发过程,全面揭示Vue的响应式原理。
摘要由CSDN通过智能技术生成

Vue02 基础原理

1、vue的初始化流程

  1. new Vue时,传入options参数,并进行初始化操作_init
  2. 该初始化函数_init在initMixin中定义,主要是为了_init方法传递vue构造函数,并在原型上挂载_init方法
  3. _init方法中进行初始化
    1. initState:主要处理props、methods、data、compute、watch等对象
  4. 最后,在_init方法中判断有无传入el参数,如果传入则将数据挂载到页面上

2、vue中如何处理传入的data数据

由于在初始函数_init方法中,调用initState处理props、methods、data、computed、watch等数据,对于data通过initData的方法对数据进行处理

initData函数的处理:

  1. 如果传入的是一个函数,则让函数的this指向Vue实例,并将函数返回值作为对象;如果传入的是一个对象,则直接使用对象
  2. 数据代理:通过proxy的方法进行代理data数据,即vm.name = vm._data.name,实际还是通过Object.defineProperty进行数据劫持
  3. 通过observe方法递归对象的所有属性及其子属性,通过object.defineProperty方法重写data对象的所有属性,将data变成响应式。

3、如何将传入的data变成响应式

  1. new Vue会调用_init方法进行初始化
  2. 会将用户的选项放到vm.$options上
  3. 会对当前属性上搜索有没有data数据 (initState)
  4. 有data判断data是不是一个函数,如果是函数取返回值 (initData)
  5. observe 去观测data中的对象
  6. 如果更新对象不存在的属性,会导致视图不更新,如果是数组更新索引和长度不会触发更新。
  7. 如果是替换成一个新对象,新对象会被进行劫持,如果是数组存放新内容 push unshift()新增的内容也会被劫持
  8. 通过__ob__进行标识这个对象被监控过。

Vue中主要通过observe方法对data中的数据进行检测:

1、先判断传入的是不是对象,如果不是对象,则直接return,不做观测(如果是函数则取函数的返回值作为对象)

2、通过Observe类来对data进行处理,如果观测过就直接跳过观测(__ob__

4、vue中如何识别被观测过的数据?(__ob__

Observe类具体实现:

  1. 在data上添加__ob__属性,为Observe类的实例,所有被劫持过的属性都有__ob__
  2. 如果传入的值是对象,通过调用walk方法,循环遍历对象,通过defineReactive方法重新定义属性,进行数据劫持
  3. 如果传入的是数组
    1. 先对数组的方法进行重写,因为push、pop、shift、unshift、reverse、sort、splice,因为这些方法会改变数组本身
    2. 然后通过observeArray方法,使数组里的引用类型定义成响应式,实际上就是通过遍历数组对每一项进行observe函数处理

5、数组为什么不用defineProperty进行响应式处理

数组也可以使用defineProperty,但是我们很少采用 arr[i] = xxx,如果数组也使用了defineProperty还是可以实现修改索引触发更新的,但是这种操作概率低,所以源码没有采用这种方式。

所以数组修改索引不会导致视图更新,修改length也不会更新。

但是是vue3中为了兼容proxy,内部对数组用的就是defineProperty

6、Watcher类是如何实现的?

Watcher更新时,传入5个参数

  • vm:vue实例
  • expOrFn:页面渲染逻辑
  • cb:回调函数
  • options
  • isRenderWatcher:是否为渲染Watcher

传入参数后,进行参数的处理

  • this.getter = fn :fn就是页面渲染逻辑

然后调用get方法,进行初始化Watcher

  1. 调用pushTarget,将当前Watcher放入到Dep.target中用于依赖收集
  2. 调用this.getter方法,进行渲染逻辑
  3. 这样就能保证之后当前渲染到的属性才能获取到当前的Watcher

当收集依赖时,会调用addDep方法

  1. 其中维护着一个数组newDepIds,用于Dep去重
  2. 然后把传入的Dep加入到newDeps中将Dep存储起来
  3. 然后调用dep.addSub的方法,将当前的Watcher存储到Dep的subs数组中

7、Dep类是如何实现的?

1、Dep的属性:

  • subs:用于存放Watcher的数组
  • id:用于做唯一标识
  • target:表示当前依赖的目标是谁(哪个Watcher)

2、当用户取属性时,会触发depend的方法,调用Watcher即Dep.target的addDep方法,用于Dep去重,然后Watcher存储当前dep,然后调用addSub方法

3、当调用addSub方法,就是将当前的Watcher存入subs数组中

8、收集依赖的过程(Dep.append)

进行数据初始化的时候,如果传入的值是对象的话,通过调用walk方法,循环调用defineReactive方法对每个属性进行数据劫持。所以要为每个属性增加Dep的话,可以在defineReactive中定义

1、页面更新的时候,会调用mountComponent的方法,在该方法中会再实例化一个渲染Watcher,然后调用Watcher中的get方法,主要做一下工作:

  • 调用pushtarget,将当前Watcher放到Dep.target的属性中
  • 然后调用updateComponent的方法,进行初渲染
  • 然后通过popTarget,重置Dep.target的值

2、当用户取值时,会调用defineProperty的get方法,这时触发Dep.append

3、在Dep.append方法中,会调用Dep.target(即Watcher)的addDep方法进行收集Watcher

4、而Watcher.addDep方法,会通过id的方法,用Set数据结构先对Dep进行去重,然后将dep收集到newDeps数组中,然后调用Dep.addSub方法,将当前Watcher放到Dep.subs数组中

5、这样dep就收集到了多个Watcher,而Watcher也收集到了Dep

9、依赖收集后,数据该如何进行更新呢?(Dep.notify、Watcher.update)

用户取数据后,会调用defineProperty的get方法,进行依赖收集,即Dep获得多个Watcher,Watcher也获得了多个Dep

当用户修改数据的时候,会调用Dep.notify方法,进行数据更新

  notify() {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }

实际上是操作就是,通过循环subs数组中收集的Watcher,调用Watcher中的update方法,实际上就是调用Watcher的get方法(updateComponent)进行渲染

在update方法中可以做异步更新逻辑

10、如何实现vue异步更新逻辑?(queue Watcher)

当用户更新数据时,会调用defineProperty中的set方法,设置值后,会调用Dep.notify方法,进行循环调用Watcher中的update方法。

由于每次更新都会调用这个update方法,我们可以将更新的逻辑存储起来,等到同步更新数据的逻辑执行完毕后,依次调用(含有去重逻辑)

每次更新调用update方法,都会执行queue Watcher方法

  • 去重:维护着has数组,用于存储已有的Watcher的id,用has[id] == null来判断是否重复
  • 缓存:通过维护queue队列,每次把watcher都放入queue队列当中
  • 防抖:通过维护waitting变量,默认值为false,只有清空queue中的watcher才会设置为true
  • 异步:当waitting变量为false,才会设置nextTick,清空queue队列,然后执行flushSchedulerQueue的方法,实际就是遍历每个watcher执行其run()方法

11、数组如何依赖收集?数组更新时,又如何触发更新?

对数据进行响应式观测的时候,会调用observe方法进行观测,如何在observer方法中,进行new Observer实例化

数组依赖收集

  1. 在实例化Observer类的时候,添加dep属性,值为new Dep
  2. 当取属性值时,会调用defineProperty的get方法,在get方法中,调用数组或对象的Observer实例上的dep.depend()进行依赖收集
  3. 如果是数组中嵌套着数组,通过调用dependArray方法,遍历数组第一项,调用__ob__的dep.depend方法,进行依赖收集,再递归调用dependArray
function dependArray(value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}

数组触发更新:

对于数组的响应式处理,是通过重重写数组的七个方法进行处理的,只需要再重写的方法中,再调用数组本身的dep.notify方法

12、vue的响应式原理

1、默认vue在初始化的时候,会对对象每个属性进行劫持,添加dep属性,当取值的时候会做依赖收集

2、默认还会对属性值是对象和数组本身进行添加dep属性,进行依赖收集

3、如果属性变化,触发属性对应的dep进行更新

4、如果是数组更新,触发数组的本身的dep进行更新

5、如果取值的时候是数组还要让数组中的对象类型也进行依赖收集(递归依赖收集)

6、如果数组里面放对象,默认对象里的属性是会进行依赖收集的,因为在取值时,会进行JSON.stringify操作

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值