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 Vue
Vue._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 => hello
reactiveGetter
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 钩子函数,可用于调试和追踪更新过程。