主要针对 vue 源码的核心流程进行梳理:
各个流程图生效,晚点再补上,这里是完整的 pdf 链接 文件
整体流程
[图片上传失败...(image-b5c28-1606886798297)]
目录分析
|---- benchmarks 性能测试文件
|---- dist
|---- examples
|---- flow vue 使用 Flow 进行静态类型检查, 这里是静态类型检查的类型声明文件
|---- packages 独立的 vue 相关 npm 包
|---- scripts 构建相关文件和配置
|---- src 源码
| |---- compile 模板编译代码
| |---- core 核心代码
| |---- components 内置组件 keep-alive
| |---- global-api 全局 API
| |---- instance 实例化与渲染相关
| |---- observer observer/dep/watcher
| |---- util
| |---- vdom 虚拟dom 相关
| |---- platforms 平台相关的
| |---- web
| |---- weex
| |---- server 服务端渲染
|---- test
|---- types ts 类型声明文件
模板编译与渲染
vm.options.template
经过parse
生成AST
抽象语法树,AST
经过编译生成字符串形式的render
函数,调用render
方法, 生成virtual dom
。
-
解析模板字符串,生成 AST
-
优化 AST
-
基于 AST 生成字符串形式的
render / staticRenderFns
-
web
目录下定义的mount
钩子函数中,通过new Function(code)
方法,将字符串形式的渲染函数变为匿名函数形式 -
在
Vue.prototype._render
方法中,调用render
函数 -
render Watcher
中,将Vue.prototype._update
方法(传入的第一个参数为vnode
,本质上调用vm.__patch__
方法[createPatchFunction
]),包装成一个匿名函数,vm._update
方法传入的第一个参数为vm._render()
updateComponent = () => { vm._update(vm._render(), hydrating) }
以一个例子来说明这个流程
<div id="app"></div>
<script src="../dist/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!',
lists: [
{
name: 'sy',
age: '88'
},
{
name: 'jane',
age: '99'
}
]
},
methods: {
func: function(){
console.log('点击成功')
}
},
template:
`<div @click.stop=func style="background-color: red; width: 100px; height: 100px;">
<p>{{ message }}</p>
<div v-for="(item, index) in lists" v-bind:class="item.name">
<span>{{ item.name }}</span>
<span>{{ item.age }}</span>
</div>
</div>`
})
</script>
解析
调用parseHTML
方法,传入template
和options
,parseHTML
中通过正则匹配各标签和各动态及静态属性(如v-bind
,v-for
等)并对其进行处理,将起始标签入栈,匹配到结束标签后将其出栈,最终生成 AST
对象。
// 创建 AST 元素
export function createASTElement (
tag: string,
attrs: Array<ASTAttr>,
parent: ASTElement | void
): ASTElement {
return {
type: 1,
tag, // 标签名
attrsList: attrs, // 属性list
attrsMap: makeAttrsMap(attrs), // 属性 name/value map
rawAttrsMap: {}, // 基于attrsList展开形式的原始 attrsList
parent,
children: []
}
}
主要流程:
- 对
template
字符串递归进行正则校验,匹配起始标签 - 处理起始标签中的属性, 将标签信息以对象形式入栈
- 基于起始标签创建
AST
对象,处理pre
,v-if
,v-for
,v-once
等属性并对其增强,删除template
已经处理过的部分 - 解析到结束标签后,从栈中寻找最近的结束标签,将匹配结果从栈中弹出,对元素的
key
,ref
,slot
,component
,attrsList
设置与事件绑定等进行处理, 对AST
进行增强 - 直至
template
为空,返回AST
对象
stack
中的存储示例
return stack = [{
attrsList: [
{name: "@click.stop", value: "func", start: 5, end: 21},
{name: "style", value: "background-color: red; width: 100px; height: 100px;", start: 22, end: 81}
],
attrsMap: {@click.stop: "func", style: "background-color: red; width: 100px; height: 100px;"}
children: []
end: 82
parent: undefined
rawAttrsMap: {@click.stop: {…}, style: {…}}
start: 0
tag: "div"
type: 1
}, {
attrsList: []
attrsMap: {}
children: []
end: 94
parent: {type: 1, tag: "div", attrsList: Array(2), attrsMap: {…}, rawAttrsMap: {…}, …}
rawAttrsMap: {}
start: 91
tag: "p"
type: 1
}]
最终生成的 AST
对象
return ast = {
attrsList: [{
end: 21,
name: "@click.stop",
start: 5,
value: "func"
}],
attrsMap: { @click.stop: "func", style: "background-color: red; width: 100px; height: 100px;" },
children: (3) [{…}, {…}, {…}]
end: 300
events: {
click: {
dynamic: false,
end: 21,
modifiers: {stop: true},
start: 5,
value: "func",
}
}
hasBindings: true,
parent: undefined,
plain: false,
rawAttrsMap: {
@click.stop: { name: "@click.stop", value: "func", start: 5, end: 21 },
style: { name: "style", value: "background-color: red; width: 100px; height: 100px;", start: 22, end: 81 }
},
start: 0,
staticStyle: `{"background-color":"red","width":"100px","height":"100px"}`,
tag: "div",
type: 1
}
优化
优化 AST
,标记静态节点。遍历生成的AST
并检测纯静态节点的子树,对其进行处理
- 将其提升为常量, 这样不需要在每次重新渲染的时候为它们创建新的节点
- 在
patch
过程中跳过
function isStatic (node: ASTNode): boolean {
if (node.type === 2) { // expression 表达式
return false
}
if (node.type === 3) { // text 文本
return true
}
return !!(node.pre || ( //有 pre 标记
!node.hasBindings && // no dynamic bindings
!node.if && !node.for && // not v-if or v-for or v-else
!isBuiltInTag(node.tag) && // not a built-in slot component
isPlatformReservedTag(node.tag) && // not a component
!isDirectChildOfTemplateFor(node) && // 不是template的直接子节点
Object.keys(node).every(isStaticKey) // 每个key 都是静态的
))
}
如果一个元素存在子节点并且所有子节点为静态节点的情况下,标记该节点为静态节点。
编译
基于 AST
对象创建渲染函数,最终生成的render
函数是一个由with
包裹的字符串,在 beforeMount
阶段和update
阶段在vm
实例上执行。基于 AST
创建生成render
的函数为:
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")' /* _c 为挂载在 vm 实例上的 createElement 方法, 用于根据 ast 创建 vnode*/
return {
render: `with(this){return ${code}}`, /* 将表达式添加到指定的作用域链上 https://www.zhihu.com/question/49929356 */
staticRenderFns: state.staticRenderFns
}
}
在这之前需要先了解一些挂载在Vue.prototype
上的方法
export function installRenderHelpers (target: any) {
target._o = markOnce // 用唯一的key标记静态节点
target._l = renderList // renderList(val, render) 渲染列表
target._t = renderSlot // renderSlot(string, fallback, props, bindObject) 渲染插槽
target._m = renderStatic // 静态树渲染
target._b = bindObjectProps // 绑定对象属性
target._v = createTextVNode // 创建文本vnode
target._e = createEmptyVNode // 创建空vnode
target._u = resolveScopedSlots // 加载局部插槽
target._d = bindDynamicKeys // 绑定动态 key
}
主要的执行流程为,基于AST
调用 genElement(ast, state)
,所有已处理过的节点会被不同的操作做上标记。 genElement(ast, state)
的主要逻辑为:
el 属性 | 操作逻辑 |
---|---|
static 节点 |
将genElement 方法推入staticRenderFns ,调用_m 执行静态树渲染,打上staticProcessed 标记 |
v-once 节点 |
打上onceProcessed 标记①同时为 v-if 节点,进入v-if 处理逻辑;②为 v-for 里面的静态节点,genElement(el, state) 返回 _c , 执行后生成 vnode ,在调用_o 用唯一的key 进行标记 ;③ 都不是,进入静态节点渲染逻辑 |
v-for 节点 |
调用列表渲染方法_l ,将genElement 传入,打上 forProcessed 标记 |
v-if 节点 |
根据el.ifConditions 判断正则校验是否通过,通过的情况下调用genElement ,打上ifProcessed 标记 |
template 节点 |
调用 genChildren
|
slot 节点 |
调用插槽渲染方法_t
|
else |
调用getData 方法生成字符串形式的 data ,包括directives ,key ,ref ,pre ,component ,attributes ,props ,event handlers ,v-model 等,然后调用_c createElement 方法创建虚拟 dom
|
最终生成的code
形式为
return code = {
render: `with(this){return _c('div',{staticStyle:{"background-color":"red","width":"100px","height":"100px"},on:{"click":function($event){$event.stopPropagation();return func($event)}}},[_c('p',[_v(_s(message))]),_v(" "),_l((lists),function(item,index){return _c('div',{class:item.name},[_c('span',[_v(_s(item.name))]),_v(" "),_c('span',[_v(_s(item.age))])])})],2)}`,
staticRenderFns: []
}
DOM更新机制
更新流程
path
方法由 createPatchFunction
生成,挂载在 Vue.prototype.__patch__
方法上,vm.__patch__
方法在vm._update()
时机被调用,最终生成的是 dom
节点return vnode.elm
。createPatchFunction
函数中的逻辑为
[图片上传失败...(image-4c4450-1606886798297)].svg)
createElm
逻辑,根据vnode
生成dom
,并进行挂载
- 创建
dom
节点 (如html
标签, 注释节点, 文本节点) - 递归创建子元素
dom
节点 - 调用
created
钩子, 生成标签, 属性, 样式, 事件等 - 将生成的节点插入父节点
function sameVnode (a, b) { // 判断两个节点是否是同一个节点
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
虚拟dom --vNode结构
export default class VNode {
tag: string | void; // 标签名
data: VNodeData | void; // 当前节点的数据对象
children: ?Array<VNode>; // 子节点
text: string | void; // 当前节点的文本, 文本节点或注释节点会有该属性
elm: Node | void; // 当前虚拟 dom 对应的真实 dom 节点
ns: string | void; // 当前节点的名字空间
context: Component | void; // rendered in this component's scope 在这个组件范围内渲染
key: string | number | void; // 节点标识
componentOptions: VNodeComponentOptions | void; // 组件的 option 选项
componentInstance: Component | void; // component instance 组件实例
parent: VNode | void; // component placeholder node 节点的父节点
}
diff算法
[图片上传失败...(image-10cf7c-1606886798297)].svg)
流程:
-
前提:
const elm = vnode.elm = oldVnode.elm
,oldCh = oldVnode.children
,ch = vnode.children
-
vnode
是文本节点且oldVnode.text !== vnode.text
,为elm
更新textContent
-
vnode
不是文本节点-
oldCh
和ch
都存在且不相等,进入updateChildren
逻辑
-
-
ch
存在,oldCh
不存在 -
checkDuplicateKeys(ch)
检查ch
上是否有重复的key
,有的话抛出异常-
oldVnode
是文本节点,清空elm
的textContent
- 为
elm
添加虚拟节点
-
-
ch
不存在,oldch
存在 -
移除虚拟节点,触发
destroy
钩子 -
oldVnode
是文本节点,清空elm
的textContent
updateChildren
逻辑
const elm = vnode.elm = oldVnode.elm
const oldCh = oldVnode.children // 旧的虚拟dom的子节点
let oldStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
const ch = vnode.children // 新的虚拟dom的子节点
let newStartIdx = 0
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
递归得以进行的条件:oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx
oldStartVnode |
oldEndVnode |
|
---|---|---|
newStartVnode |
① sameVnode --> patchVnode; newStartIdx++; oldStartIdx++
|
④sameVnode --> patchVnode; newStartIdx++; oldEndIdx--; insertBefore(oldStartVnode.elm)
|
newEndVnode |
③sameVnode --> patchVnode; newEndIdx--; oldStartIdx++; insertBefore(oldEndVnode.elm.next)
|
②sameVnode --> patchVnode; newEndIdx++; oldEndIdx++
|
如果以上逻辑均没有匹配
- 以
oldCh
中oldStartIdx
至newStartIdx
之间元素的key
和index
为键和值建立map
- 如果
newStartVnode
有key
,用newStartVnode
的key
在map
中进行寻找 - 如果
newStartVnode
有key
,在oldCh
中逐个进行比较寻找- 如果找到,继续进行
sameVnode --> patchVnode
的逻辑- 如果
isSameNode
,将oldch
中的元素置空,insertBefore(oldStartVnode)
- 如果不是同一个元素,
createElm
创建一个新元素并插入到合适的位置。
- 如果
- 如果没有找到,
createElm
创建一个新元素并插入到合适的位置。
- 如果找到,继续进行
[图片上传失败...(image-672396-1606886798297)]
数据更新
数据更新流程图
[图片上传失败...(image-a6dc9d-1606886798297)]
Class | 功能 |
---|---|
Observer |
数据变动的观察者 |
Watcher |
数据变动的订阅者, 数据的变化会通知到 watcher , 由watcher 进行相应的操作, 如视图更新 |
Dep |
进行依赖收集, 数据变化时, 会被 Observer 观察到, 然后由 Dep 通知到 Watcher
|
整体流程概述:
-
initData
时,为传入的options.data
直接添加__ob__
属性(Observe
的实例),对数组和对象的逻辑不同。- 数组,为数组中每个元素添加
__ob__
属性,重写数组实例__proto__
指向的原型Array.prototype
,只处理push
,pop
,shift
,unshift
,splice
,sort
被调用时的更新,对通过push
,unshift
,splice
新增的数组元素增加__ob__
,通过ob.dep.notify()
通知相应的Watcher
,因此无法检测通过数组或者索引修改数组元素引起的变化。 - 对象,对对象上的所有属性,通过
Object.defineProperty
对值的get
和set
操作添加相应回调。get
中将栈顶的Watcher
订阅到这个数据持有的dep
的watcher
中,后续数据变化时通知到哪些watcher
做更新;set
中当检测到值变化时,调用dep.notify
通知到watcher
执行相应的update
方法。
- 数组,为数组中每个元素添加
-
mount
时机调用mountComponent
方法,内部初始化一个render watcher
,在Watcher
内部的逻辑中将其入栈,当通过set
改变通知到render watcher
,触发vm._update(vm._render(), hydrating)
进行重新渲染 -
对于计算属性值的更新,再
initComputed
时调用计算属性上用户定义的get
方法,会触发所依赖的响应式变量内部的get
方法,将dep.target
添加到自身的订阅者列表中, 响应式变量 变化时, 会触发set
函数, 其中的逻辑遍历执行订阅者, 计算属性的值在此时被更新 -
watch
的更新使用调用Vue.prototype.$watch
方法,方法内部创建一个user watcher
,初始化时通过Watcher
的this.get
进行依赖收集,但不会执行其中的回调,数据变化时触发update
方法,同步或者异步地执行其中的回调。
[图片上传失败...(image-88c060-1606886798297)]
Observer
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this) /* 为对象定义属性 (obj, key, val, enumerable) __ob__ 指向Observer 实例, 对象和数组都有一个__ob__ 属性 */
if (Array.isArray(value)) {
if (hasProto) { /* 重写 Array.prototype */
protoAugment(value, arrayMethods) /* value.__proto__ = arrayMethods ( Object.create(Array.prototype) ) */
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value) /* 为数组中的每一项执行 observe 方法, 添加 Observer 实例 */
/**
* 对数组的元素没有进行依赖收集, 是对每一个值添加了一个观察者实例, 如果对数组进行依赖收集,
* 每次更新的情况下(不管是通过索引, 长度, 或是数组方法)修改数组, 会大量频繁触发 get 和 set 中定义的方法,
* 像forEach 这种调用 触发量更大, 因此指针对数组中的 push, pop, shift, unshift, splice, sort, reverse 方法,
* 触发后对新增数据新增观察者实例, 调用 ob.dep.notify() 方法通知更新
*/
} else {
this.walk(value) /* 对对象的处理, 遍历对象上的自身可枚举属性并将其转化为 getter 和 setter, 对数组中的属性值调用 defineReactive 方法 */
}
}
}
/**
* Define a reactive property on an Object.
* 在对象上定义响应式属性 defineReactive(obj, key, val, customSetter, shallow)
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean /* 用于判断是否将子属性转换成响应式的, 默认为 false */
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key) /* 返回对象上一个自有属性对应的属性描述符 */
if (property && property.configurable === false) {
return
}
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val) /* 为值创建一个观察者实例, 深度监听 */
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
/* get 中处理的操作是依赖收集 */
const value = getter ? getter.call(obj) : val
if (Dep.target) { /* Dep.target 实际上是一个 Watcher */
dep.depend()
/* 把当前的 watcher 订阅到这个数据持有的 dep 的 watchers (subs) 中, 后续数据变化时能通知到哪些 watcher 做准备 */
if (childOb) {
childOb.dep.depend()
/**
* 劫持对象属性的变化, 在 getter 的时候, 拿到 Observer 实例中的 dep 实例, 执行 dep.depend 方法
* 将 watcher 实例添加到 Dep 实例的 subs 属性中
*/
if (Array.isArray(value)) { /* 值是数组的情况下 */
dependArray(value) /* 无法向 getter 一样拦截对数组元素的访问, 收集对数组元素的依赖关系, value[i].__ob__.dep.depend()*/
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) { /* 值没有变化, 不触发 */
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
/**
* 1. notify, 将 subs 中的元素按照 id 排序, 执行 watcher 中的 update 方法
* 2. update --> run --> get --> 执行用户定义的 getter 函数
*/
}
})
}
/**
* 原生方法上直接根据索引修改数组会触发 set
* vue 不能检测两种数组变动: 1. 利用索引设置数组项 2. 直接修改数组长度
*/
Watcher
/**
* watcher 有三种
* 1. render watcher 更新视图, $mount 里创建一个render watcher, 调用 mountComponent方法, expOrFn 回调里传入 vm._update(vm._render(), hydrating)
* 2. computed watcher 更新计算属性的值与更新计算属性对应的视图
* 3. user watcher 开发者注册的 watch 回调函数执行
*
* A watcher parses an expression, collects dependencies, and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
* 观察值解析表达式, 收集依赖, 在表达式更改时触发回调.
*/
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean; /* lazy 为 true 时表示是一个 computed watcher */
sync: boolean; /* user watcher 的 options 对象中的 sync 属性, 默认 false 异步 */
dirty: boolean; /* 检测当前的 computed watcher 是否需要重新执行的标志, false 不会重新执行 */
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this /* vm._watcher 上是 watcher 实例 */
}
vm._watchers.push(this)
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid /* uid for batching uid用于批处理 */
this.active = true
this.dirty = this.lazy /* dirty 的值等于 lazy 的值, for lazy watchers */
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production' /* 非生产环境, 将 expOrFn 转化为字符串, 生产环境为'' */
? expOrFn.toString()
: ''
if (typeof expOrFn === 'function') { /* 如果是函数, 直接赋给getter */
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn) /* 否则转换成方法赋给getter, 返回闭包 */
if (!this.getter) { /* watcher 仅接受简单的点分割路径 */
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy /* lazy 为 true 时, 为 computed watcher, value 为 undefined, 否则直接调用 get 方法 */
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies. 评估getter, 重新收集依赖关系
*
*
* 1. 将当前的 watcher 作为栈顶的 watcher 推入栈, 通过 dep.target 将当前的 computed watcher 推入栈中,
* 此时 Dep.target 就指向栈顶的 computed watcher
*
* 2. 执行 getter 方法, 对于 computed watcher, getter 方法是计算属性的函数, 执行函数将返回的值赋给 value 属性,
* 当计算属性的函数执行时,如果内部含有其他响应式变量, 会触发它们内部的 getter, 将第一步放入作为当前栈顶的
* computed watcher 存入响应式变量内部的 dep 对象中
*
* 在这一步中, 页面读取计算属性的值时, 会调用 Vue 上的计算属性对应的方法, 方法中如果依赖某个响应式属性的变量
*
* [vue 会维护一个全局的栈用来存放 watcher, 每当触发响应式变量内部的 getter 时, 收集这个全局的栈的顶部的
* watcher (Dep.target) 将这个 watcher 存入响应式变量内部保存的 dep 中]
*
*
* 3. 将这个 computed watcher 弹出全局的栈, 之所以将 computed watcher 推入又弹出, 是为了让第二步执行内部的
* getter 时, 能让计算属性内部依赖的响应式变量收集到这个 computed watcher
*
*/
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm) /* 在 vm 实例上执行用户定义的 getter 函数 */
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
/**
* "touch" every property so they are all tracked as dependencies for deep watching
* 每个属性作为深度监听的依赖项进行跟踪设置 deep: true, 监听对象内部值的变化
*/
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
/* 向指令添加依赖项 Add a dependency to this directive. */
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) { /* 避免重复收集依赖 */
this.newDepIds.add(id) /* id 放入newDepIds 中 */
this.newDeps.push(dep) /* dep 放入 newDeps 中 */
if (!this.depIds.has(id)) {
dep.addSub(this) /* 向 subs 中push this */
}
}
}
/* Clean up for dependency collection 清除依赖收集 */
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/**
* Subscriber interface. Will be called when a dependency changes.
* 订阅者 interface, 依赖项更改时将被调用
*/
update () {
if (this.lazy) { // 如果 lazy, dirty 为 true
this.dirty = true
} else if (this.sync) { /* 同步 */
this.run()
} else { /* 异步 */
queueWatcher(this)
}
}
/* Scheduler job interface. Will be called by the scheduler. 任务调度 interface, 将被调度者使用 */
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
/**
* Deep watchers and watchers on Object/Arrays should fire even when the value is the same, because the value may have mutated.
* 即使值相同, 在 对象/ 数组上的 watcher 也应该触发, 值有可能突变
*/
isObject(value) ||
this.deep
) {
/* set new value, 设置新值 */
const oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue) /* vm 上执行回调, 传入旧值和新值 */
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/* Evaluate the value of the watcher. This only gets called for lazy watchers. 计算 lazy watchers 的值 */
evaluate () {
this.value = this.get() /* 执行 get 方法 */
this.dirty = false /* 将 dirty 设置为 false */
}
/**
* Depend on all deps collected by this watcher. 依赖这个watcher的所有deps
*
* depend 方法会遍历当前 computed watcher 的 deps 属性, 依次执行 dep 的 depend 方法
* dep 是每个响应式变量内部保存的一个对象, deps 是所有响应式变量内部 dep 的集合
* 每个响应式变量内部的 dep 会保存所有的 watcher, 每个 watcher 的 deps 属性会保存所有收集到这个 watcher 的响应式变量内部的 dep 对象
* 是一个相互依赖的关系
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
/**
* Vue 在 watcher 中保存 deps, 一方面需要让计算属性能够收集依赖, 一方面可以在注销这个 watcher 的时候知道哪些
* dep 依赖了这个 watcher, 只需要调用 dep 里面对应的注销方法即可
*/
}
}
/* Remove self from all dependencies' subscriber list. 从所有依赖的订阅者列表中删除自身 */
teardown () {
if (this.active) {
/**
* remove self from vm's watcher list this is a somewhat expensive operation so we skip it if the vm is being destroyed.
* 从 vm 列表中删除当前 watcher
*/
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
Dep
export default class Dep {
static target: ?Watcher; /* 类通过 static 关键字定义静态方法, 不能在类的实例上调用静态方法, 应该通过类本身调用 */
id: number;
subs: Array<Watcher>; /* 用于存储依赖 (Watcher 实例) 的数组 */
constructor () {
this.id = uid++
this.subs = []
}
/* 添加 watcher */
addSub (sub: Watcher) {
this.subs.push(sub)
}
/* 移除 watcher */
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) { /* target 是 watcher */
Dep.target.addDep(this) /* 将 watcher 实例添加到 Dep 实例的 subs 属性中 */
}
}
notify () {
/* stabilize the subscriber list first 稳定订阅列表 */
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
/**
* subs aren't sorted in scheduler if not running async we need to sort them now to make sure they fire in correct order
* subs 在 scheduler 中未排序, 进行排序保证正确的触发顺序
*/
subs.sort((a, b) => a.id - b.id) // 按照 id 增序排列
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() /* 调用 watcher 的 update 方法 */
}
}
}
/**
* 全局同时只能有一个的被观察的目标观察者
* The current target watcher being evaluated. This is globally unique because only one watcher can be evaluated at a time.
*/
Dep.target = null
const targetStack = []
/* 全局观察者指向当前入栈的观察者 */
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
/* 将目标观察者栈中的最后一个观察者弹出, 更新Dep.target */
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
参考资料:
网友评论