Vue2.6.x源码阅读 - 7.源码阅读-core-响应式原理
最后更新于
最后更新于
数据类型(data、prop、watch、computed)初始化,构建数据双向绑定等相关源码学习。
关于响应式原理的基本概念的介绍可见Vue响应式原理一文。了解基本概念后,继续结合源码对响应式原理展开更深入的了解。
观察者模式
为JavaScript设计模式
中常用的设计模式之一,定义了对象间一对多的依赖关系,当被观察者发生变化时,观察者也会根据依赖关系随之进行更新。形象点来说,被观察者相当于一个视频号,观察者就是关注视频号的人,当视频号更新时,关注人都会收到对应的推送,然后开展其某些行为。这种模式很容易就能联想到Vue
的数据响应,被订阅的数据发生变化时,最终相应到view
层的dom
变化。
不过Vue
的响应式原理并没有仅限于停留在观察者模式。在观察者模式
的基础上,优化出了一个新的模式,即发布-订阅模式
。它与前者的区别在于,在发布者(被观察者)与订阅着(观察者)之间,增加了一个调度中心。由调度中心对发布者发布的内容进行统一处理,发送至订阅了该内容的订阅者。订阅者与发布者之间并不存在直接沟通,其沟通是通过第三方的调度中心完成的。再形象点来说,就好比视频号的订阅人与视频号的所有人之间并没有直接联系,而通过视频平台构建两者的关系。
两种模式的结构可见下图。摘自观察者模式和发布订阅模式有什么区别?
Vue
即采用了发布订阅模式
来作为响应式原理
的核心设计模式。回看官方给出的结构图。可以根据源码实现与设计模式的进行一定的转化。转化后图摘自从发布-订阅模式到Vue响应系统
Observer
部分就是发布者(被订阅者),Dep
(Dependency)即调度中心,在原图中合并为了Data
部分。Watcher
即订阅者(观察者),在原图中依旧为Watcher
。
用更Vue
化的意思来说,整个流程即是通过Observer
与数据(Data)构建联系,整体作为一个发布者。在数据发生变化时,通过Dep
通知到Watcher
,而Watcher
又与界面元素建立了联系,从而完成了dom
的更新。
Dep(调度中心)在Vue
中主要做了两件事,依赖收集(Collect Dependency)与派发更新(Notify)。即收集来自订阅者的订阅,通知订阅者相关发布者的更新。
在上述设计模式的解释中,通过图解,已将设计模式转换成了Vue
源码的实现。在Vue核心源码(core)中,整个core/observer
目录均为响应式原理
的实现代码。先整体预览一遍目录内的文件构成。
array.js
创建含有重写数组方法的数组,让所有的响应式数据数组继承自该数组
dep.js
Dep Class相关代码
index.js
Observer Class相关代码
scheduler.js
任务调度工具,Wathcer执行的核心
traverse.js
递归遍历响应式数据,用于触发依赖收集。
watcher.js
Watcher Class相关代码
该文件的功能,上述也已提到,创建含有重写数组方法的数组,让所有的响应式数据数组继承自该数组。即对定义在Vue data
中的数据进行响应式处理,当数组发生改变的时,触发对应的响应式操作。那么如何去触发响应式操作,在Vue
中,就是对Array.prototype
中的原生方法进行了重写,在原生方法的基础上,加入了Vue
所需要的触发依赖收集与派发更新两部分的代码。
分段解析该文件中的内容,首先该文件引入了util/lang.js
中的一个def
方法。该方法实质上是封装了Object.defineProperty
,固定了writable
与configurable
属性为true
。而开放了enumerable
属性的配置。关于这几个属性的作用,可详见[Object.defineProperty()简介](https://github.com/fff455/tech-share/blob/master/JavaScript/Object.defineProperty()%E7%AE%80%E4%BB%8B.md)
那么为何开放enumberable
可配置。首先,该属性意为是否可枚举,即是否能够通过Object.keys()
访问到。当我们在遍历整个Vue
实例对象的属性时,不难发现其中存在循环定义。在5.源码阅读-core-Vue构造函数中的_init()
方法内,就存在一行循环定义的代码vm._self = vm
,将实例本身定义进了_self
属性,即循环定义。除此之外Vue
中存在的另一个常见循环定义还有vm.$el.__vue__ = vm
。正因为这样的定义存在,所以必须让这些属性不可遍历,否则对Object
属性的遍历也会进入死循环。也因此开放了enumerable
的配置。
继续看array.js
的后续代码
通过array.js
的代码也可以发现Vue2
中的一个问题,数组发生变化,设置数组的length
并不会触发响应式更新,在Vue3
中通过Proxy
替代Object.defineProperty
后进行了解决。
了解完响应式原理
最核心几个类的实现后,回看构造函数中的数据初始化。在5.源码阅读-core-Vue构造函数中,可以了解到数据(state)的初始化位于构造函数中的initState
方法中完成。该方法对包括method
在内的五种数据进行了初始化。那么根据重要程度,先从data
类型的初始化方法initData
开始进行解析。
直接上initData的代码,其中对开发环境下的warning
部分的代码进行了一定的省略。