Vue的依赖收集和性能问题

什么是依赖收集

Vue能够实现当一个数据变更时,视图就进行刷新,而且用到这个数据的其他地方也会同步变更;而且,这个数据必须是在有被依赖的情况下,视图和其他用到数据的地方才会变更。 所以,Vue要能够知道一个数据是否被使用,实现这种机制的技术叫做依赖收集。

每个组件实例都有相应的watcher实例 - 渲染组件的过程,会把属性记录为依赖 - 当我们操纵一个数据时,依赖项的setter会被调用,从而通知watcher重新计算,从而致使与之相关联的组件得以更新。

Vue2用defineProperty来劫持属性,生成watcher实例来响应属性的变化。

注意,Dep的target是watcher实例

依赖收集与观察者模式

在Vue依赖收集里:谁是观察者?谁是观察目标? 显然: - 依赖的数据是观察目标(Watcher) - 视图、计算属性、侦听器这些是观察者Watcher

依赖收集分析

依赖收集的三个类:
  • Dep:发布者。扮演观察目标的角色,每一个数据都会有Dep类实例,它内部有个subs队列,subs就是subscribers的意思,保存着依赖本数据的观察者,当本数据变更时,调用dep.notify()通知观察者

  • Watcher:观察者。扮演观察者的角色,进行观察者函数的包装处理。如render()函数,会被进行包装成一个Watcher实例

  • Observer:观测类。辅助的可观测类,数组/对象通过它的转化,可成为可观测数据

每一个数据都有的Dep类实例:

由于JavaScript是单线程模型,所以虽然有多个观察者函数,但是一个时刻内,就只会有一个观察者函数在执行,那么此刻正在执行的那个观察者函数,所对应的Watcher实例,便会被赋给Dep.target这一类变量,从而只要访问Dep.target就能知道当前的观察者是谁。 在后续的依赖收集工作里,getter里会调用dep.depend(),而setter里则会调用dep.notify()

Watcher观察者

一个组件里可以有多个Watcher类实例,Watcher类包装观察者函数,而观察者函数使用数据。 观察者函数经过Watcher是这么被包装的:

- 模板渲染:this._watcher = new Watcher(this, render, this._update)

- 计算属性:

computed: {
    name() {
        return `${this.firstName} ${this.lastName}`;
    }
}
/*
会形成
new Watcher(this, function name() {
    return `${this.firstName} ${this.lastName}`
}, callback);
*/

在Watcher类里做的事情,概括起来则是:

1、传入组件实例、观察者函数、回调函数、选项:

先解释清楚4个变量:deps、depIds、newDeps、newDepIds,它们的作用如下:

- deps:缓存上一轮执行观察者函数用到的dep实例

- depIds:Hash表,用于快速查找

- newDeps:存储本轮执行观察者函数用到的dep实例

- newDepIds:Hash表,用于快速查找

2、进行初始求值,初始求值时,会调用watcher.get()方法

3、watcher.get()会做以下处理:初始准备工作、调用观察者函数计算、事后清理工作

①在初始准备工作里,会将当前Watcher实例赋给Dep.target,清空数组newDeps、newDepIds

②执行观察者函数,进行计算。由于数据观测阶段执行了defineReactive(),所以计算过程用到的数据会得以访问,从而触发数据的getter,从而执行watcher.addDep()方法,将特定的数据记为依赖

③对每个数据执行watcher.addDep(dep)后,数据对应的dep如果在newDeps里不存在,就会加入到newDeps里,这是因为一次计算过程数据有可能被多次使用,但是同样的依赖只能收集一次。并且如果在deps不存在,表示上一轮计算中,当前watcher未依赖过某个数据,那个数据相应的dep.subs里也不存在当前watcher,所以要将当前watcher加入到数据的dep.subs里

④进行事后清理工作,首先释放Dep.target,然后拿newDeps和deps进行对比,接着进行以下的处理:

- newDeps里不存在,deps里存在的数据,表示是过期的缓存数据。相应的,从数据对应的dep.subs移除掉当前watcher

- 将newDeps赋给deps,表示缓存本轮的计算结果,这样子下轮计算如果再依赖同一个数据,就不需要再收集了

⑤当某个数据更新时,由于进行了setter拦截,所以会对该数据的dep.subs这一观察者队列里的watchers进行通知,从而执行watcher.update()方法,而update()方法会重复求值过程(即为步骤3-7),从而使得观察者函数重新计算,而render()这种观察者函数重新计算的结果,就使得视图同步了最新的数据

Vue的依赖收集,是观察者模式的一种应用。其原理总结如图:

    • 配置依赖观测
    • 收集依赖
    • 数据值变更

依赖收集带来的问题:

依赖收集需要对数据元进行递归绑定。当数据元数据量过大(例如明细表或透视表数十万条数据),会严重影响前端的性能效率。

解决方法:

①避开依赖收集:

不使用this.xxx = data绑定数据

对数据使用Object.freeze进行冻结(避免依赖收集,Vue2对configurable为false的对象不进行数据劫持)或者将数据绑定为class私有属性

②将数据对象约定为数组

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值