You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
export functionset(target: Array<any>|Object,key: any,val: any): any{if(process.env.NODE_ENV!=='production'&&(isUndef(target)||isPrimitive(target))){warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)}if(Array.isArray(target)&&isValidArrayIndex(key)){target.length=Math.max(target.length,key)target.splice(key,1,val)returnval}if(keyintarget&&!(keyinObject.prototype)){target[key]=valreturnval}constob=(target: any).__ob__if(target._isVue||(ob&&ob.vmCount)){process.env.NODE_ENV!=='production'&&warn('Avoid adding reactive properties to a Vue instance or its root $data '+'at runtime - declare it upfront in the data option.')returnval}if(!ob){target[key]=valreturnval}defineReactive(ob.value,key,val)ob.dep.notify()returnval}
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金
先看张图,了解一下大体流程和要做的事
初始化
在 new Vue 初始化的时候,会对我们组件的数据 props 和 data 进行初始化,由于本文主要就是介绍响应式,所以其他的不做过多说明来,看一下源码
源码地址:
src/core/instance/init.js - 15 行
初始化这里调用了很多方法,每个方法都做着不同的事,而关于响应式主要就是组件内的数据
props
、data
。这一块的内容就是在initState()
这个方法里,所以我们进入这个方法源码看一下initState()
源码地址:
src/core/instance/state.js - 49 行
又是调用一堆初始化的方法,我们还是直奔主题,取我们响应式数据相关的,也就是
initProps()
、initData()
、observe()
一个一个继续扒,非得整明白响应式的全部过程
initProps()
源码地址:
src/core/instance/state.js - 65 行
这里主要做的是:
props
列表defineReactive
设置成响应式proxy()
把属性代理到当前实例上,如把vm._props.xx
变成vm.xx
,就可以访问initData()
源码地址:
src/core/instance/state.js - 113 行
这里主要做的是:
proxy()
把 data 里的每一个属性都代理到当前实例上,就可以通过this.xx
访问了observe
监听整个 dataobserve()
源码地址:
src/core/observer/index.js - 110 行
这个方法主要就是用来给数据加上监听器的
这里主要做的是:
Observer
源码地址:
src/core/observer/index.js - 37 行
这是一个类,作用是把一个正常的数据成可观测的数据
这里主要做的是:
defineReactive()
源码地址:
src/core/observer/index.js - 135 行
这个方法的作用是定义响应式对象
这里主要做的是:
上面说了通过 dep.depend 来做依赖收集,可以说 Dep 就是整个 getter 依赖收集的核心了
依赖收集
依赖收集的核心是 Dep,而且它与 Watcher 也是密不可分的,我们来看一下
Dep
源码地址:
src/core/observer/dep.js
这是一个类,它实际上就是对 Watcher 的一种管理
这里首先初始化一个 subs 数组,用来存放依赖,也就是观察者,谁依赖这个数据,谁就在这个数组里,然后定义几个方法来对依赖添加、删除、通知更新等
另外它有一个静态属性 target,这是一个全局的 Watcher,也表示同一时间只能存在一个全局的 Watcher
Watcher
源码地址:
src/core/observer/watcher.js
Watcher 也是一个类,也叫观察者 (订阅者),这里干的活还挺复杂的,而且还串连了渲染和编译
先看源码吧,再来捋一下整个依赖收集的过程
补充:
就是在 watcher.run() 里面会执行回调,并且把新值和老值传过去
因为 Vue 是数据驱动的,每次数据变化都会重新 render,也就是说 vm.render() 方法就又会重新执行,再次触发 getter,所以用两个数组表示,新添加的 Dep 实例数组 newDeps 和上一次添加的实例数组 deps
依赖收集过程
在首次渲染挂载的时候,还会有这样一段逻辑
mountComponent
源码地址:src/core/instance/lifecycle.js - 141 行
依赖收集:
watcher
,进入watcher
构造函数里就会执行this.get()
方法pushTarget(this)
,就是把Dep.target
赋值为当前渲染watcher
并压入栈 (为了恢复用)this.getter.call(vm, vm)
,也就是上面的 updateComponent() 函数,里面就执行了vm._update(vm._render(), hydrating)
vm._render()
就会生成渲染vnode
,这个过程中会访问 vm 上的数据,就触发了数据对象的 getterdep
,在触发 getter 的时候就会调用dep.depend()
方法,也就会执行Dep.target.addDep(this)
移除订阅
移除订阅就是调用 cleanupDeps() 方法。比如在模板中有 v-if 我们收集了符合条件的模板 a 里的依赖。当条件改变时,模板 b 显示出来,模板 a 隐藏。这时就需要移除 a 的依赖
这里主要做的是:
派发更新
notify()
触发 setter 的时候会调用
dep.notify()
通知所有订阅者进行派发更新update()
触发更新时调用
queueWatcher()
源码地址:
src/core/observer/scheduler.js - 164 行
这是一个队列,也是 Vue 在做派发更新时的一个优化点。就是说在每次数据改变的时候不会都触发 watcher 回调,而是把这些 watcher 都添加到一个队列里,然后在 nextTick 后才执行
这里和下一小节
flushSchedulerQueue()
的逻辑有交叉的地方,所以要联合起来理解主要做的是:
flushSchedulerQueue()
源码地址:
src/core/observer/scheduler.js - 71 行
这里主要做的是:
updated()
终于可以更新了,
updated
大家都熟悉了,就是生命周期钩子函数上面调用
callUpdatedHooks()
的时候就会进入这里, 执行updated
了至此 Vue2 的响应式原理流程的源码基本就分析完毕了,接下来就介绍一下上面流程中的不足之处
defineProperty 缺陷及处理
使用 Object.defineProperty 实现响应式对象,还是有一些问题的
而这些问题,Vue2 里也有相应的解决文案
Vue.set()
给对象添加新的响应式属性时,可以使用一个全局的 API,就是 Vue.set() 方法
源码地址:
src/core/observer/index.js - 201 行
set 方法接收三个参数:
这里主要做的是:
__ob__
,说明不是一个响应式对象,直接赋值返回重写数组方法
源码地址:
src/core/observer/array.js
这里做的主要是:
往期精彩
结语
如果本文对你有一丁点帮助,点个赞支持一下吧,感谢感谢
https://juejin.cn/post/7017327623307001864
The text was updated successfully, but these errors were encountered: