Vue 源码分析
Vue
项目入口:core => instance => index.ts
src/core/instance/index.ts
import { initMixin } from './init'
// src/core/instance/index.ts
function Vue(options) {
if (__DEV__ && !(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
// 在Vue原型上挂载 _init()方法
initMixin(Vue)
export default VueVue._init => initState()
src/core/instance/init.ts
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this
// 合并配置选项
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
// 代理初始化
/* istanbul ignore else */
if (__DEV__) {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 初始化实例属性和生命周期
initLifecycle(vm)
initEvents(vm) // 在Vue实例上初始化事件相关的属性和方法
initRender(vm) // 初始化与渲染相关的属性和方法 $options.render
// 调用 beforeCreate 钩子
callHook(vm, 'beforeCreate')
// 初始化注入和响应式数据
initInjections(vm)
initState(vm) // => initState(vm)
initProvide(vm)
// 调用 created 钩子
callHook(vm, 'created')
// 挂载组件
// options中如果有el,就会自动调用$mount()
// 如果没有el,有template,需要手动调用$mount
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}initState() => initData
src/core/instance/state.ts
export function initState(vm: Component) {
const opts = vm.$options
// 初始化props
if (opts.props) initProps(vm, opts.props)
// 初始化 setup (Vue 3 的 Composition API)
initSetup(vm)
// 初始化 methods
if (opts.methods) initMethods(vm, opts.methods)
// 初始化 data
if (opts.data) {
initData(vm) // 往下
} else {
// 如果没有 data 选项,则将 vm._data 设置为一个响应式的空对象
const ob = observe((vm._data = {}))
ob && ob.vmCount++
}
// 初始化 computed
if (opts.computed) initComputed(vm, opts.computed)
// 初始化 watch
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}initData
src/core/instance/state.ts
function initData(vm: Component) {
// 获取data选项
let data: any = vm.$options.data
// 处理 data 选项,确保它是一个对象
// 如果 data是一个函数,执行getData(data, vm),获取该函数返回的值作为新的data
// 如果不是,使用 data || {} 的值作为新的 data,确保data至少是一个空对象
// 将新获取的 data 值同时赋给了 vm._data 和 data 变量。
// 这里vm._data => 表示Vue 实例上的 _data 属性,本身就是用来存储数据
data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
// 代理 data 对象的属性 到 vm 实例上
let i = keys.length
while (i--) {
const key = keys[i]
proxy(vm, `_data`, key)
}
// observe data
// 观测 data 对象,使其变成响应式
const ob = observe(data)
ob && ob.vmCount++
}vm.$data => vm._data
src/core/instance/state.ts
通过 Object.defineProperty 方法给 Vue.prototype 原型对象定义了一个 $data 访问器属性。该属性的 getter 函数直接返回了 this._data。 所以,当我们访问 vm.$data 时,实际上就相当于访问了 vm._data
const dataDef = {}
dataDef.get = function () {
return this._data
}
Object.defineProperty(Vue.prototype, '$data', dataDef)proxy 数据代理
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function proxy(target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
// 上面代码改造一下 =>
export function proxy(target: Object, sourceKey: string, key: string) {
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get: function proxyGetter() {
return this[sourceKey][key]
},
set: function proxySetter(val) {
this[sourceKey][key] = val
}
})
}解释数据代理 通过一个对象代理对另一个对象的中属性的操作(读/写)
// proxy(vm, `_data`, key) 比如key为msg属性
// 带到函数中,参数分别为
// target => vm
// sourceKey => _data
// key => msg
Object.defineProperty(vm, 'msg', {
enumerable: true,
configurable: true,
get: function proxyGetter() {
return this['_data']['msg']
},
set: function proxySetter(val) {
this['_data']['msg'] = val
}
})
// 也就是说当我们操作 vm.msg = 'hello',Vue内部实际上执行的是vm._data.msg = 'hello'const vm = new Vue({
el: '#app',
// template:'<div>hhh</div>',
// data:{}
// 如果data是一个函数,每次创建一个vue组件的时候
// data里面的数据,都不会相互有影响~~
data: function () {
return {
msg: 'hello',
age: 18
}
},
created() {
console.log(this.msg)
}
})
console.log(vm)
// vm.msg ===> 访问的是 data函数return出来的对象中的msg
// vm.age ===> {age:18}
// vm.msg / vm.age ===> data中的msg和age
// this.msg ===> 代理了 data 中的 msg
// Vue中,生命周期/方法中等, this指向的就是vm实例
// 1. Vue中的数据代理
// 通过vm对象代理了data对象中属性的操作(读/写)
// 2. Vue中数据代理的好处
// 更加方便的操作data中的数据
// 使用this.msg => 实际上操作的是 vm._data.msg 也就是data中的数据
// 3. 基本原理
// 通过proxy方法,内部使用Object.defineProperty 把 data中 的所有属性 添加到 vm 对象上,
// 为每一个添加的属性,都设置getter和setter方法,
// 在getter和setter方法内部去操作data中对应的属性observe(data) 数据劫持
src/core/observer/index.ts
/**
* 尝试为一个值创建一个Observer实例
* 如果值已经拥有Observer实例,直接返回这个实例
* 如果满足一定条件,则为该值创建一个新的Observer实例
*
* 条件:
* 1. 传入的值必须是对象或数组
* 2. 不能是VNode实例
* 3. 不能是只读对象
* 4. 不能是ref对象
* 5. 必须可扩展(Object.isExtensible为true)
*/
export function observe(
value: any, // 需要观察的值
shallow?: boolean, // 是否为浅观察
ssrMockReactivity?: boolean // 是否为服务器端渲染模拟响应式
): Observer | void {
// 如果值已经是一个观察者实例,直接返回该实例
// 1. 如果已经对data做了响应式处理了,就不做了
if (value && hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
return value.__ob__
}
// 2. 如果传入的值满足一定条件
// shouldObserve为真
// 如果是服务器端渲染,则ssrMockReactivity为真,否则不在服务器端渲染环境
// 传入的值是数组或纯对象
// 传入的值是可扩展的
// 传入的值没有__v_skip标记
// 传入的值不是ref对象
// 传入的值不是VNode实例
// 则创建一个新的观察者实例并返回。
if (
shouldObserve &&
(ssrMockReactivity || !isServerRendering()) &&
(isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value.__v_skip /* ReactiveFlags.SKIP */ &&
!isRef(value) &&
!(value instanceof VNode)
) {
return new Observer(value, shallow, ssrMockReactivity)
}
}export function isPlainObject(obj: any): boolean {
return _toString.call(obj) === '[object Object]'
}简化 observe 方法
export function observe(value: any): Observer | void {
// 1. 如果已经对data做了响应式处理了,就不做了
// Observer会为每一个做了劫持的data添加__ob__
if (value && hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
return value.__ob__
}
// 2. 数组或者对象 数据劫持
if (isArray(value) || isPlainObject(value)) {
return new Observer(value)
}
}Observer(value)
src/core/observer/index.ts
observe => return new Observer(value) ; value 指的就是 data
=> Observer 本身只针对对象或者数组处理,并给他们 def 一个ob属性
data(){
return {
msg:'hello',
age:18,
arr:[1, 2, 3]
}
},
// value 就是这个 =>
// {
// msg:'hello',
// age:18
// }// 获取 arrayMethods 对象自身所有属性的键名(包括非枚举属性),
// 并将它们存储在 arrayKeys 数组中
// 不可枚举属性指 enumerable: false ,处于安全策略,有些属性可能会这么设置
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
// arrayKeys = ['length','concat','find', ...,'push','pop',...]
// 数组原型上的方法和重新后的7个方法
export class Observer {
// 用于收集依赖和发送通知
dep: Dep
// 记录有多少个 Vue 实例将此对象作为根 $data
// 作用
// 1. 垃圾回收 vmCount等于0,表示当前observer实例没有被任何组件实例引用,可以回收
// 2. 避免重复观察 多个组件实例可能共享同一个数据对象,比如父子组件通信,子组件引用了父组件数据
vmCount: number // number of vms that have this object as root $data
constructor(public value: any, public shallow = false, public mock = false) {
// this.value = value
// 初始化 dep,如果是 mock 模式则使用 mockDep,否则创建新的 Dep 实例
this.dep = mock ? mockDep : new Dep()
this.vmCount = 0
// 在 value 对象上定义一个不可枚举的 __ob__ 属性,指向当前 Observer 实例
// 给当前value对象添加__ob__属性,属性值就是当前Observer实例
// def => Object.defineProperty(obj,key,val)
def(value, '__ob__', this)
// 如果 value 是数组
if (isArray(value)) {
// 如果不是 mock 模式,则覆盖数组原型
if (!mock) {
if (hasProto) {
// 直接修改 __proto__ 指向 arrayMethods
;(value as any).__proto__ = arrayMethods
} else {
// 如果无法修改 __proto__,则直接在数组实例上定义 arrayMethods 的方法
// 1. 比如浏览器安全机制,某些浏览器禁止修改内置对象的原型
// 2. ES5 strict mode 严格模式下,禁止修改Array.prototype
// 等等
for (let i = 0, l = arrayKeys.length; i < l; i++) {
// arrayKeys = ['length','concat','find', ...,'push','pop',...]
const key = arrayKeys[i]
// value => arr [1,2,3]
// key => 'push'
// arrayMethods[key] => 改写后的'push'
def(value, key, arrayMethods[key])
}
}
}
// 如果不是浅观察,则递归观察数组中的每一项
if (!shallow) {
this.observeArray(value)
}
} else {
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
// 如果 value 是对象,则遍历对象的每一个属性,
// 使用 defineReactive 方法将它们转换为 getter/setter
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
}
}
}
/**
* Observe a list of Array items.
*/
// 对数组中的每一个元素进行观察(observe),使它们也成为响应式对象
observeArray(value: any[]) {
for (let i = 0, l = value.length; i < l; i++) {
// observe作用:将一个普通对象转为响应式对象
// false => 表示进行深层观察,如果数组元素是一个对象,那么它的属性也会转为响应式
observe(value[i], false, this.mock)
}
}
// 遍历数组中的每一个元素,并使用 observe 方法将它们转换为响应式对象。
// 这样,当数组中的对象发生变化时,Vue 就可以检测到并做出相应的响应
}数组的处理
src/core/observer/array.ts value.proto = arrayMethods
// 为什么要创建arrayMethods?
// 这样做的原因是,Vue 需要对数组的一些可变方法(如 push、pop、splice 等)进行拦截和重写,
// 以实现响应式更新。
// 但是,我们不能直接修改 Array.prototype,因为这会影响所有数组实例。
// 通过创建一个继承自 Array.prototype 的新对象 arrayMethods,
// Vue 可以在这个新对象上安全地重写需要拦截的数组方法,而不会影响其他数组实例。
// 之后,Vue 会将数组实例的原型指向 arrayMethods,从而使重写的方法生效/*
* not type checking this file because flow doesn't play well with
* dynamically accessing methods on Array prototype
*/
import { TriggerOpTypes } from '../../v3'
import { def } from '../util/index'
// Array.prototype 是所有数组实例的原型对象
// Object.create(proto) 是一个内置方法,
// 用于创建一个新对象,并将新对象的原型(__proto__)指向传入的 proto 参数
const arrayProto = Array.prototype
// 创建一个新的对象arrayMethods,继承自数组原型Array.prototype
// 这个新对象 arrayMethods 可以访问和继承 Array.prototype 上的所有属性和方法
export const arrayMethods = Object.create(arrayProto)
// 7个方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
拦截修改后的数组方法,并发出通知,比如当执行了arr.push()拦截,并通知使用了arr的组件实例
*/
methodsToPatch.forEach(function (method) {
// cache original method
// 缓存原始数组方法
const original = arrayProto[method]
def(arrayMethods, method, function mutator(...args) {
// mutator /ˈmjuːteɪtə/ 改变,增变
// 调用原始数组方法,获取结果
// eg,如果是push方法: Array.prototype.push.apply(this, args)
// this指向的是谁? 比如data中有一个arr:[1,2,3], 当执行arr.push()
// 执行的是重写后的push方法,this指向的调用 push 方法的数组实例 arr
const result = original.apply(this, args)
// 见前面:有一句
// ;(value as any).__proto__ = arrayMethods
// 获取当前数组的观察者实例
const ob = this.__ob__
// 根据不同的方法,处理新传入的值
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 如果有新值插入,则观察新值
if (inserted) ob.observeArray(inserted)
// notify change
// 开发环境,发送更详细通知
if (__DEV__) {
ob.dep.notify({
type: TriggerOpTypes.ARRAY_MUTATION,
target: this,
key: method
})
} else {
// 生产环境,发送通知,告知所有依赖当前arr的观察者,数组发生了变化
// 触发时机,在重写的数组方法执行之后 !!!
ob.dep.notify()
}
return result
})
})被重写的数组方法是挂载在 arrayMethods 对象上的,数组的原型又会被修改为 ArrayMethods
defineReactive/对象的处理
src/core/observer/index.ts defineReactive 用于定义对象的响应式属性,数据劫持,getter/setter
export function defineReactive(
obj: object, // 要定义响应式属性的对象
key: string, // 要定义响应式属性的键名
val?: any, // 可选的属性值,如果没有提供则从obj[key]中获取
customSetter?: Function | null, // 可选的自定义setter函数
shallow?: boolean,
mock?: boolean,
observeEvenIfShallow = false
) {
// 创建一个新的 Dep 实例,用于收集和通知观察者
const dep = new Dep()
// 获取obj中当前属性的描述符
const property = Object.getOwnPropertyDescriptor(obj, key)
// / 如果property存在且它的configurable不可配置,则直接返回,则直接返回
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 获取属性的getter和setter方法(如果存在)
const getter = property && property.get
const setter = property && property.set
if (
// (!getter || setter) 没有getter函数或者有setter函数
// 如果一个属性只有getter函数而没有setter函数,表示该属性时只读的,无法被修改
// 这里的条件是为了判断属性是否可写
(!getter || setter) &&
(val === NO_INITIAL_VALUE || arguments.length === 2)
// 初始值未定义或者传入的参数为2
) {
// 当用户没有提供属性的初始值时,从对象中获取该属性的现有值作为初始值
val = obj[key]
}
// 如果shallow true : 浅观察
// val && val.__ob__ 检测属性值val是否已经是一个响应式对象,是就直接返回它的观察者对象
// 如果val为falsy值(null, undefined),childOb将赋值为falsy值
// 如果shallow为false, 进行深度观察 deep observe, 调用observe函数,
// 将属性值val转为一个响应式对象(observe -> 对属性值继续进行响应式处理)
// 如果val为基本数据类型,childOb => undefined
// 也就是说,如果childOb有值,那么表示该属性的值就是对象或者数组
let childOb = shallow ? val && val.__ob__ : observe(val, false, mock)
// obj {msg:'hello'}
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可重新配置
get: function reactiveGetter() {
/* 获取属性值的操作 */
},
set: function reactiveSetter(newVal) {
/* 设置属性值的操作 */
}
})
return dep // 返回 Dep 实例,用于收集依赖和发送通知
}val = obj[key] 理解
if (
// (!getter || setter) 没有getter函数或者有setter函数
// 如果一个属性只有getter函数而没有setter函数,表示该属性时只读的,无法被修改
// 这里的条件是为了判断属性是否可写
(!getter || setter) &&
(val === NO_INITIAL_VALUE || arguments.length === 2)
// 初始值未定义或者传入的参数为2
) {
// 从对象中获取属性的初始值
val = obj[key]
}
// function defineReactive(
// obj: object, // 要定义响应式属性的对象
// key: string, // 要定义响应式属性的键名
// val?: any, // 可选的属性值,如果没有提供则从obj[key]中获取
// customSetter?: Function | null, // 可选的自定义setter函数
// shallow?: boolean, // 是否进行浅观察
// mock?: boolean, // 是否为模拟模式
// observeEvenIfShallow = false // 即使是浅观察,也要对值进行观察
// )
const NO_INITIAL_VALUE = {}
const obj = {
foo: 'hello'
}
defineReactive(obj, 'foo', NO_INITIAL_VALUE, undefined, shallow, mock)
// 上面的情况,
// foo的 getter 为 undefined,即没有 getter 函数
// setter 为 undefined,即没有 setter 函数
// val 等于特殊值 NO_INITIAL_VALUE
// (val === NO_INITIAL_VALUE || arguments.length === 2) 满足
// 所以 val = obj[key]
val = obj['foo'] // val => helloreactiveGetter
let childOb = shallow ? val && val.__ob__ : observe(val, false, mock)=> 如果 childOb 存在, 表示该属性的值是一个对象或者数组!
getter 函数会在获取属性值时被调用。 它首先获取属性的值, 然后如果存在依赖收集的目标对象 Dep.target,则会调用 dep.depend 函数收集依赖。 如果是开发环境,会额外记录一些信息,如目标对象、获取操作类型和键名。 如果该属性的值是一个对象或数组,会递归地收集其子元素的依赖。 最后,如果是浅层观察且值是 Ref 对象,则返回 Ref 对象中包裹的实际值 value.value,否则直接返回值本身。
get: function reactiveGetter() {
// 如果getter存在,在obj对象上下文中调用getter函数获取属性值
// 不存在,直接取val
const value = getter ? getter.call(obj) : val // 获取属性的值
if (Dep.target) { // 如果有依赖收集的目标对象
if (__DEV__) {
// 在开发环境下,收集依赖时记录更多信息
dep.depend({
target: obj,
type: TrackOpTypes.GET,
key
})
} else {
dep.depend() // 在生产环境下,直接收集依赖
}
if (childOb) { // 如果是对象或数组,递归地收集子元素的依赖
// 让当前渲染watcher订阅childOb的依赖
childOb.dep.depend()
if (isArray(value)) {
dependArray(value)
}
}
}
// 如果是shallow,false且值是 Ref 对象,返回 Ref 对象中包裹的实际值 value.value
// 否则直接返回值本身
// Vue3中ref => 需要.value
return isRef(value) && !shallow ? value.value : value
},// 为数组中的每个元素收集相应的依赖
function dependArray(value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
// e表示当前遍历到的元素
e = value[i]
// 如果 e 存在(为真)并且 e 有 __ob__ 属性(说明 e 是一个响应式对象)。
if (e && e.__ob__) {
// 让当前渲染watcher订阅 e ,
// 即如果数组中的元素是一个对象,那么当这个对象的属性发生变化时,
// 我们需要通知渲染 watcher 进行相应的更新
e.__ob__.dep.depend()
}
// 如果遍历到的e又是数组,递归处理嵌套数组元素
if (isArray(e)) {
dependArray(e)
}
}
}对象结构分析
// 比如有这么一个对象
data(){
return (){
msg:{
age:18,
name:'xd',
like:{
book:'xx',
music:'yy'
}
},
wa:'hello',
yo:'huohuo'
}
}
// value => 最外层大对象
// key => msg, wa, yo
// Observer类中
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
}
// 进入defineReactive后
// obj => 最外层
// key => msg
// val = obj[key] => val就是 msg的属性值,即为第二层子对象
// 一般对象第一次执行执行,表示对子对象再次调用observe进行响应式拦截处理
let childOb = observe(val, false, mock)reactiveSetter
setter 函数会在设置属性值时被调用。 它首先获取属性的旧值, 如果新旧值相同,则直接返回,不做任何操作。然后做了一系列判断,更新新值 并且通知依赖进行更新。
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val // 获取属性的旧值
if (!hasChanged(value, newVal)) {
// 如果新旧值没有变化,直接返回
return
}
if (__DEV__ && customSetter) {
// 如果是开发环境且定义了自定义 setter
customSetter() // 执行自定义 setter
}
if (setter) {
// 如果原生存在 setter 函数
setter.call(obj, newVal) // 调用原生 setter 函数
} else if (getter) {
// 如果只存在 getter 函数
// #7981: for accessor properties without setter
return // 跳过设置操作
} else if (!shallow && isRef(value) && !isRef(newVal)) {
// 如果是深层观察且旧值是 Ref 对象但新值不是,则更新 Ref 对象的值
value.value = newVal
return
} else {
val = newVal // 否则直接更新 val
}
// 根据 shallow 选项决定是直接观察新值还是新建一个观察器实例
childOb = shallow ? newVal && newVal.__ob__ : observe(newVal, false, mock)
if (__DEV__) {
// 在开发环境下,通知依赖更新时记录更多信息
dep.notify({
type: TriggerOpTypes.SET,
target: obj,
key,
newValue: newVal,
oldValue: value
})
} else {
dep.notify() // 在生产环境下,直接通知依赖更新
}
}Dep 类
src/core/observer/dep.ts
import config from '../config'
import { DebuggerOptions, DebuggerEventExtraInfo } from 'v3'
// 用于生成唯一的 dep 实例 id
let uid = 0
// 存储需要被清理的 dep 实例
// 在移除订阅者时,会将订阅者设置为 null,并将 dep 实例推入此数组
// cleanupDeps 函数会过滤掉这些 null 值,并重置 _pending 标志
const pendingCleanupDeps: Dep[] = []
// 清理 dep 实例中无效的订阅者
export const cleanupDeps = () => {
for (let i = 0; i < pendingCleanupDeps.length; i++) {
const dep = pendingCleanupDeps[i]
// 过滤掉 dep.subs 数组中的 null 值(已移除的订阅者)
dep.subs = dep.subs.filter(s => s)
// dep.subs.filter(s => s) 这行代码的作用是过滤掉 dep.subs 中的无效订阅者。
// s => s 是一个箭头函数,它的作用是返回 s 本身。
// filter 方法会创建一个新数组,只包含满足条件(即 s 为真值)的元素。
// 因此,这行代码会移除 dep.subs 中所有的 falsy 值(如 null、undefined 等)。
dep._pending = false // 重置 _pending 标志
}
pendingCleanupDeps.length = 0 // 清空数组
}
/**
* @internal
* DepTarget 接口定义了 watcher 需要实现的方法
* 用于将 dep 实例添加到 watcher 的依赖列表中,以及执行更新操作
*/
export interface DepTarget extends DebuggerOptions {
id: number // watcher 实例的唯一 id
addDep(dep: Dep): void // 将 dep 实例添加到 watcher 的依赖列表中
update(): void // 执行更新操作
}
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
* Dep 是一个可观察对象,可以有多个指令订阅它
* @internal
*/
export default class Dep {
static target?: DepTarget | null // 当前正在计算的 watcher 实例
id: number // dep 实例的唯一 id
subs: Array<DepTarget | null> // 订阅者列表
// pending subs cleanup
_pending = false // 标记是否需要清理无效订阅者
constructor() {
this.id = uid++ // 为每个 dep 实例分配唯一 id
this.subs = [] // 初始化订阅者列表
}
addSub(sub: DepTarget) {
this.subs.push(sub) // 添加订阅者
}
removeSub(sub: DepTarget) {
// #12696 deps with massive amount of subscribers are extremely slow to
// clean up in Chromium
// to workaround this, we unset the sub for now, and clear them on
// next scheduler flush.
// 如果一个依赖项 (Dep 实例) 有大量的订阅者 (subscribers),
// 清理这些订阅者的过程会变得非常缓慢。
// 为了解决这个性能问题,Vue 采取了一种权宜之计:
// 首先,取消订阅者,即将订阅者从订阅者列表中移除。
// 然后,在下一次调度器刷新时,彻底清除这些已取消订阅的订阅者。
// 如果一个依赖项有成千上万个订阅者,那么立即清理所有这些订阅者可能会导致浏览器卡顿或阻塞。
this.subs[this.subs.indexOf(sub)] = null
if (!this._pending) {
this._pending = true
pendingCleanupDeps.push(this)
}
}
depend(info?: DebuggerEventExtraInfo) {
// 当前有 watcher 实例在计算
if (Dep.target) {
// 将当前 dep 实例添加到 watcher 的依赖列表中
// > watcher 依赖于 dep
// 将当前 dep 实例添加到 watcher 的依赖列表中
Dep.target.addDep(this)
if (__DEV__ && info && Dep.target.onTrack) {
// 在开发环境下,触发 onTrack 钩子函数
// 可用于调试和追踪依赖收集过程
Dep.target.onTrack({
effect: Dep.target,
...info
})
}
}
}
notify(info?: DebuggerEventExtraInfo) {
// stabilize the subscriber list first
// 过滤无效订阅者,生成新的有效订阅者数组
const subs = this.subs.filter(s => s) as DepTarget[]
if (__DEV__ && !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
// 在非异步更新的情况下,确保订阅者按照 id 顺序执行
// 这是为了保证在组件更新时,子组件在父组件之前更新
subs.sort((a, b) => a.id - b.id)
}
// 遍历所有有效订阅者,执行更新操作
for (let i = 0, l = subs.length; i < l; i++) {
const sub = subs[i]
if (__DEV__ && info) {
sub.onTrigger &&
sub.onTrigger({
effect: subs[i],
...info
})
}
sub.update()
}
}
}
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
// 当前正在计算的 watcher 实例 Dep.target
Dep.target = null
// 用于存储 Dep.target 的栈
// 这是为了支持嵌套的 effect 执行
const targetStack: Array<DepTarget | null | undefined> = []
// 将 watcher 实例压入栈,设置为 Dep.target
// 这个操作发生在 effect 执行的入口
export function pushTarget(target?: DepTarget | null) {
targetStack.push(target)
Dep.target = target
}
// 从栈中弹出 watcher 实例,重置 Dep.target
// 这个操作发生在 effect 执行完毕后
export function popTarget() {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}一个 Watcher 实例会读取组件渲染所依赖的数据,从而触发依赖收集。在依赖收集过程中,当前渲染的 Watcher 实例会被添加到相应数据的 Dep 实例的订阅者列表(subs)中 这里是一些重要注释的解释:
pendingCleanupDeps数组存储需要被清理的dep实例。当移除订阅者时,会将订阅者设置为 null,并将 dep 实例推入此数组。cleanupDeps函数会过滤掉这些 null 值,并重置 _pending 标志。这种做法是为了提高性能,避免直接从数组中删除元素。DepTarget接口定义了watcher需要实现的方法,用于将 dep 实例添加到 watcher 的依赖列表中,以及执行更新操作。Dep.target表示当前正在计算的 watcher 实例。在计算过程中,会将 watcher 实例压入栈,并设置为 Dep.target。计算完成后,从栈中弹出 watcher 实例,重置 Dep.target。这是为了支持嵌套的 effect 执行。depend方法将当前 dep 实例添加到 watcher 的依赖列表中。在开发环境下,会触发 onTrack 钩子函数,可用于调试和追踪依赖收集过程。notify方法遍历所有有效订阅者,执行更新操作。在非异步更新的情况下,会确保订阅者按照 id 顺序执行,这是为了保证在组件更新时,子组件在父组件之前更新。在开发环境下,会触发 onTrigger 钩子函数,可用于调试和追踪更新过程。
