vue源码解析
文章目录
为减少文章篇幅,代码只保留主要方法,需要更详细的代码文档(已注明代码解释)可使用git拉取:
https://gitcode.com/weixin_44862629/vue-source-code/overview
一、总结
1、vue 2 是通过Object.defineProperty()对组件内的data数据进行递归绑定getter和setter,同时通过Dep进行依赖收集管理
2、vue2 是(通过Object.defineProperty递归遍历)劫持的属性
3、vue3 是(通过proxy代理)劫持的整个data对象
二、vue2双向数据绑定(源码)
1.1 关键实现方法及步骤
数据劫持:通过Object.defineProperty()拦截对象属性的访问和修改,将属性转化为响应式属性。
observe 、defineReactive(Object.defineProperty、getter、setter)
依赖收集:初始化组件时,Vue解析模板并为每个依赖于响应式属性的地方创建一个Watcher。这些Watcher会被添加到相应属性的Dep中。
Watcher、Dep(addSub、notify、)
视图更新:当数据属性发生变化时,对应的Dep会通知所有相关的Watcher,进而触发视图更新。
Compiler ()
1.2 vue源码 - Observe
- observe、defineReactive,观察并创建响应式数据
- set和del函数,用于在响应式对象上添加或删除属性,实现数据的“响应式”更新。
- 定义响应式属性和依赖收集(Dep),并在数据变化时自动更新依赖于这些数据的视图。
// 省略依赖引入...
const arrayKeys = Object.getOwnPropertyNames(arrayMethods) // 获取重写后的数组方法名
const NO_INITIAL_VALUE = {} // 用于标记初始值未定义
export let shouldObserve: boolean = true // 全局变量,控制是否应该观察数据
export function toggleObserving(value: boolean) {
shouldObserve = value // 切换观察状态
}
// mockDep对象,用于非生产环境或SSR(服务器端渲染)时的依赖管理
const mockDep = {...} as Dep
export class Observer { // Observer类,用于观察数据对象或数组
dep: Dep
vmCount: number
constructor(public value: any, public shallow = false, public mock = false) {
this.dep = mock ? mockDep : new Dep() // 初始化依赖管理器
this.vmCount = 0
// 在被观察对象上标记Observer实例
def(value, '__ob__', this)
if (isArray(value)) {// 根据数据类型进行不同的处理
if (!mock) { // 对数组进行特殊处理,重写其原型方法或直接在数组上定义新方法
if (hasProto) {
......
}}
if (!shallow) {this.observeArray(value)}// 递归观察数组元素
} else { // 对普通对象,递归观察其属性
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)
}
}
}
observeArray(value: any[]) { // 递归观察数组中的每个元素
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i], false, this.mock)
}
}
}
// observe函数,用于创建Observer实例观察数据
export function observe(
value: any,
shallow?: boolean,
ssrMockReactivity?: boolean
): Observer | void {
// 检查数据是否已被观察,数据已被观察retun
if (value && hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
return value.__ob__
}
if (// 判断是否应该观察该数据
shouldObserve && ...
) {
return new Observer(value, shallow, ssrMockReactivity)//创建并返回新的Observer实例
}
}
export function defineReactive( // defineReactive函数,用于定义响应式属性
...
) {
const dep = new Dep()// 创建新的依赖管理器
// 获取属性描述符,检查属性是否可配置
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// 获取getter和setter
const getter = property && property.get
const setter = property && property.set
// 如果没有setter但有getter,或者初始值为NO_INITIAL_VALUE,则获取当前值
if ((!getter || setter) && (val === NO_INITIAL_VALUE || arguments.length === 2)
) {val = obj[key]}
// 递归观察属性值
let childOb = shallow ? val && val.__ob__ : observe(val, false, mock)
// 使用Object.defineProperty定义响应式属性
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
if (__DEV__) {
...
dep.depend({...})// 依赖收集
...
}
if (childOb) { // 有子内容,递归遍历
childOb.dep.depend()
if (isArray(value)) {dependArray(value)}
}
}
return isRef(value) && !shallow ? value.value : value
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
if (!hasChanged(value, newVal)) {return}
if (__DEV__ && customSetter) {customSetter()}
// 调用自定义setter或原生setter
if (setter) {
setter.call(obj, newVal)
} else if (getter) {
return // 对于只有getter没有setter的属性,直接返回
} else if (!shallow && isRef(value) && !isRef(newVal)) {
value.value = newVal
return
} else {
val = newVal // 处理ref和普通值的更新
}
// 更新依赖并触发通知
childOb = shallow ? newVal && newVal.__ob__ : observe(newVal, false, mock)
...
dep.notify({...})
...
}
})
return dep
}
export function set<T>(array: T[], key: number, value: T): T
export function set<T>(object: object, key: string | number, value: T): T
// set和del函数,用于在响应式对象上添加或删除属性
export function set(
target: any[] | Record<string, any>,
key: any,
val: any
): any {
if (__DEV__ && (isUndef(target) || isPrimitive(target))) {
warn( // 不能在未定义、null或原始值上设置响应属性
`Cannot set reactive property on undefined, null, or primitive value: ${target}`
)
}
// 判断是否为只读
if (isReadonly(target)) {
__DEV__ && warn(`Set operation on key "${key}" failed: target is readonly.`)
return
}
//...省略部分判断内容
defineReactive(ob.value, key, val, undefined, ob.shallow, ob.mock)
// 通知所有订阅者更新
...
ob.dep.notify({...})
...
return val
}
export function del<T>(array: T[], key: number): void
export function del(object: object, key: string | number): void
export function del(target: any[] | object, key: any) {
// ...省略方法内容,和set类似
}
// 遍历数组,对每个元素进行依赖收集
function dependArray(value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
if (e && e.__ob__) {
e.__ob__.dep.depend()
}
if (isArray(e)) {
dependArray(e)
}
}
}
1.3 vue源码 - Dep(依赖收集)
- Dep依赖收集系统,用于Vue.js或类似框架中的响应式系统。
- 它允许对象(Dep实例)被观察,并且当这些对象发生变化时,可以通知所有订阅这些变化的观察者(DepTarget实例)。涉及到依赖收集(depend方法)和通知更新(notify方法)。
- 提供了用于管理当前正在评估的目标观察者的栈(targetStack),以及清理不再需要的订阅者的机制(cleanupDeps函数和相关逻辑)。
// 引入配置文件
let uid = 0; // 初始化一个用于生成Dep实例唯一标识符的变量,从0开始递增
const pendingCleanupDeps: Dep[] = []; // 定义一个数组,用于存储待清理的Dep实例
export const cleanupDeps = () => { // 定义一个函数,用于清理待清理的Dep实例
for (let i = 0; i < pendingCleanupDeps.length; i++) {
const dep = pendingCleanupDeps[i];
dep.subs = dep.subs.filter(s => s);// 过滤掉null的订阅者
dep._pending = false;// 重置待清理状态
}
pendingCleanupDeps.length = 0;// 清空待清理数组
};
...
// 定义一个Dep类,表示一个可以被多个指令订阅的可观察对象
export default class Dep {
static target?: DepTarget | null; // 静态属性,用于存储当前正在评估的目标观察者,全局唯一
id: number; // Dep实例的唯一标识符
subs: Array<DepTarget | null>; // 存储订阅者的数组
_pending = false;// 标记是否有待清理的订阅者
constructor() {
this.id = uid++; // 分配唯一标识符
this.subs = []; // 初始化订阅者数组
}
addSub(sub: DepTarget) {// 添加订阅者
this.subs.push(sub);
}
removeSub(sub: DepTarget) { // 移除订阅者,将其置为null并标记为待清理
this.subs[this.subs.indexOf(sub)] = null;
if (!this._pending) {
this._pending = true;
pendingCleanupDeps.push(this); // 将自己添加到待清理数组
}
}
// 依赖收集,当Dep.target存在时,将自己添加到Dep.target的依赖中
depend(info?: DebuggerEventExtraInfo) {
if (Dep.target) {
Dep.target.addDep(this);
...
}
}
// 通知所有订阅者更新
notify(info?: DebuggerEventExtraInfo) {
const subs = this.subs.filter(s => s) as DepTarget[]; // 稳定订阅者列表,过滤掉null
if (__DEV__ && !config.async) {
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(); // 调用订阅者的update方法
}
}
}
// 当前正在评估的目标观察者,全局唯一,因为一次只能评估一个观察者
Dep.target = null;
// 定义一个栈,用于存储目标观察者
const targetStack: Array<DepTarget | null | undefined> = [];
// 将目标观察者推入栈中,并设置Dep.target为当前目标
export function pushTarget(target?: DepTarget | null) {
targetStack.push(target);
Dep.target = target;
}
// 将目标观察者从栈中弹出,并更新Dep.target为新的栈顶目标
export function popTarget() {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
1.4 vue源码-Watcher
1.4.1 watcher类的实现
- 负责监听Vue实例上的数据变化,并在变化时执行指定的回调函数。
- Watcher类通过收集依赖(即数据变化时会通知watcher的数据对象),并在数据变化时更新自身的值和执行回调,从而实现了响应式的数据绑定。
- 包含了错误处理、深度监听、懒执行、同步执行等特性,以及用于调试的钩子函数和属性。
...
let uid = 0 // 初始化一个唯一的ID生成器
// 定义Watcher的选项接口,继承自DebuggerOptions
export interface WatcherOptions extends DebuggerOptions {
deep?: boolean // 是否深度监听
user?: boolean // 是否是用户定义的watcher
lazy?: boolean // 是否是懒执行watcher
sync?: boolean // 是否同步执行
before?: Function // 执行前的钩子函数
}
// 定义Watcher类,实现DepTarget接口
export default class Watcher implements DepTarget {
... 定义类的属性 ...
... 定义类的属性 ...
constructor(
vm: Component | null,
expOrFn: string | (() => any),
cb: Function,
options?: WatcherOptions | null,
isRenderWatcher?: boolean
) {
// 记录当前watcher的作用域
recordEffectScope(
this,
//如果激活的效果范围是手动创建的(不是一个组件范围),优先级为它
activeEffectScope && !activeEffectScope._vm
? activeEffectScope: vm ? vm._scope: undefined
)
// 如果是Vue组件实例且是渲染watcher,则将当前watcher设为组件的_watcher
if ((this.vm = vm) && isRenderWatcher) { vm._watcher = this }
if (options) { // 初始化选项
...
if (__DEV__) {
this.onTrack = options.onTrack
this.onTrigger = options.onTrigger
}
} else {
this.deep = this.user = this.lazy = this.sync = false
}
// 初始化其他属性
...
// 如果是lazy watcher,则不立即求值,否则调用get方法求值
this.value = this.lazy ? undefined : this.get()
}
get() {// 获取当前值并重新收集依赖
pushTarget(this) // 将当前watcher设为依赖收集的目标
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm) // 执行getter函数获取值
} catch (e: any) {
if (this.user) {
// 用户定义的watcher捕获错误
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e // 非用户定义的watcher直接抛出错误
}
} finally {
if (this.deep) { // 如果是深度监听,则遍历获取的值以收集所有依赖
traverse(value)
}
popTarget() // 将当前watcher从依赖收集的目标中移除
this.cleanupDeps() // 清理依赖
}
return value
}
// 添加依赖
addDep(dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this) // 添加当前watcher为dep的订阅者
}
}
}
//清理依赖
cleanupDeps() {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this) // 从dep的订阅者列表中移除当前watcher
}
}
// 交换新旧依赖数组和集合,并清空新的集合和数组
let tmp: any = 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
}
//当依赖变化时调用
update() {
if (this.lazy) {// 标记为需要重新求值
this.dirty = true
} else if (this.sync) {
this.run() // 同步执行
} else {
queueWatcher(this) // 异步执行,加入调度队列
}
}
run() {
if (this.active) {// 检查Watcher是否处于激活状态
const value = this.get()// 获取当前Watcher所观察的数据的值
if (// 判断值是否变化,或者值是否为对象/数组(因为对象和数组可能内部变化但引用不变),
// 或者是否开启了深度观察
value !== this.value ||isObject(value) ||this.deep
) {
const oldValue = this.value// 更新旧值和新值
this.value = value
if (this.user) {// 如果Watcher是用户定义的,则使用带有错误处理的调用方式执行回调
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(...)
} else {// 否则直接调用回调
this.cb.call(this.vm, value, oldValue)
}
}
}
}
evaluate() { // 更新Watcher的值,并标记(即已评估)
this.value = this.get()
this.dirty = false
}
//取决于这个观察者收集的所有深度(依赖)。
depend() {// 遍历Watcher的所有依赖,并调用它们的depend方法
let i = this.deps.length
while (i--) {this.deps[i].depend()}
}
//从所有依赖项的订阅者列表中删除自身。
teardown() {
// 如果Vue实例存在且未被销毁,则从Vue实例的作用域效果列表中移除当前Watcher
if (this.vm && !this.vm._isBeingDestroyed) {remove(this.vm._scope.effects, this) }
// 如果Watcher处于激活状态,则遍历其所有依赖,并从每个依赖的订阅者列表中移除当前Watcher
if (this.active) {
let i = this.deps.length
while (i--) {this.deps[i].removeSub(this)}
this.active = false // 将Watcher标记为非激活状态
if (this.onStop) {this.onStop()// 如果定义了onStop回调,则调用它
}
}
}
}
1.4.2 watcher队列管理
- watcher用于监听Vue组件或数据的变化,并在变化发生时执行相应的回调函数。
- 该代码通过维护一个队列来批量处理这些变化,以提高性能。同时,它还包含了一些用于处理组件激活和更新钩子的逻辑。
export const MAX_UPDATE_COUNT = 100 // 定义最大更新次数常量,用于检测无限更新循环
const queue: Array<Watcher> = [] // 定义用于存储待执行watcher的队列
const activatedChildren: Array<Component> = [] // 定义用于存储已激活子组件的队列
let has: { [key: number]: true | undefined | null } = {} // 定义一个对象,用于标记已处理的watcher ID
let circular: { [key: number]: number } = {}// 定义一个对象,用于记录循环更新的次数
let waiting = false // 定义一个标志,表示是否正在等待执行队列
let flushing = false // 定义一个标志,表示是否正在刷新队列
let index = 0 // 定义一个索引,用于遍历队列
//重置调度器状态
function resetSchedulerState() {
index = queue.length = activatedChildren.length = 0 // 重置索引和队列长度
has = {} // 重置has对象
if (__DEV__) { circular = {}}// 如果是开发模式,则重置circular对象
waiting = flushing = false // 重置等待和刷新标志
}
export let currentFlushTimestamp = 0 // 定义一个变量,用于存储当前刷新时间戳
let getNow: () => number = Date.now // 定义一个函数,用于获取当前时间戳
if (inBrowser && !isIE) { // 根据浏览器环境选择合适的时间戳获取方式
// 调用getNow()...
}
// 对watcher队列进行排序
const sortCompareFn = (a: Watcher, b: Watcher): number => {
if (a.post) {...} // 根据post属性和id进行排序
return a.id - b.id
}
function flushSchedulerQueue() { //刷新队列并执行watchers
currentFlushTimestamp = getNow() // 更新当前刷新时间戳
flushing = true // 设置刷新标志
let watcher, id
queue.sort(sortCompareFn)// 对队列进行排序
// 遍历队列并执行watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {watcher.before()}
id = watcher.id
has[id] = null
watcher.run()
// 在开发模式下,检查并停止循环更新
if (__DEV__ && has[id] != null) {
...
break
}
}
}
// 保存post队列的副本,然后重置状态
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// 调用组件的updated和activated钩子
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
cleanupDeps()
// 触发devtools钩子
if (devtools && config.devtools) {devtools.emit('flush')}
}
function callUpdatedHooks(queue: Watcher[]) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
// 如果vm存在且watcher是组件的渲染watcher且组件已挂载且未销毁,则调用updated钩子
if (vm && vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated')
}
}
}
//将一个保持活动的组件添加到激活队列中。该队列将在整个树被修补后处理。
export function queueActivatedComponent(vm: Component) {
// 设置_inactive为false,以便渲染函数可以依赖检查它是否在不活动的树中
vm._inactive = false
activatedChildren.push(vm)
}
function callActivatedHooks(queue) {
// 遍历激活队列,调用组件的activate钩子
for (let i = 0; i < queue.length; i++) {
queue[i]._inactive = true
activateChildComponent(queue[i], true /* true */)
}
}
//将一个watcher推入watcher队列。具有重复ID的作业将被跳过,除非它是在队列正在刷新时推送的。
export function queueWatcher(watcher: Watcher) {
const id = watcher.id
if (has[id] != null) {return} // 如果已处理过该watcher,则直接返回
// 如果watcher是当前Dep的target且设置了noRecurse,则直接返回
if (watcher === Dep.target && watcher.noRecurse) {return}
has[id] = true // 标记该watcher为已处理
if (!flushing) { // 如果不在刷新状态,则将watcher添加到队列中
queue.push(watcher)
} else {
// 如果正在刷新,则根据id将watcher插入到合适的位置
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {i--}
queue.splice(i + 1, 0, watcher)
}
if (!waiting) { // 队列化刷新操作
waiting = true
if (__DEV__ && !config.async) {// 如果是开发模式且未启用异步更新,则直接刷新队列
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)// 否则,使用nextTick进行异步刷新
}
}
1.5 Compiler-解析指令
1.5.1 createCompiler函数
- 使用createCompilerCreator创建一个默认的编译器,createCompilerCreator允许创建使用替代解析器/优化器/代码生成器的编译器
- 例如,用于服务器端渲染(SSR)优化的编译器
export const createCompiler = createCompilerCreator(function baseCompile(
template: string, // 模板字符串,Vue组件的模板部分
options: CompilerOptions // 编译器选项,用于自定义编译行为
): CompiledResult { // 解析模板字符串为AST
const ast = parse(template.trim(), options)
// 如果未禁用优化,则对AST进行优化
if (options.optimize !== false) {optimize(ast, options) }
const code = generate(ast, options)// 将优化后的AST转换为渲染函数代码
// 返回编译结果,包含AST、渲染函数和静态渲染函数数组
return {
ast, // 抽象语法树,用于调试或其他目的
render: code.render, // 渲染函数,用于生成和更新虚拟DOM
staticRenderFns: code.staticRenderFns // 静态渲染函数数组,用于渲染不依赖组件状态的静态内容
};
})
1.5.2 createCompilerCreator-编译器工厂函数
- 创建编译器工厂函数的实现。Vue的模板编译器负责将模板字符串转换成可执行的渲染函数。
- 导出一个函数 createCompilerCreator,接收一个编译函数 baseCompile 作为参数,并返回一个新的函数
export function createCompilerCreator(baseCompile: Function): Function {
return function createCompiler(baseOptions: CompilerOptions) {
// compile 函数是创建编译器的核心,它接收一个模板字符串 template 和一个可选的编译选项对象 options
function compile(
template: string,
options?: CompilerOptions
): CompiledResult {
// 创建一个新的对象 finalOptions,其原型为 baseOptions,用于存储最终的编译选项
const finalOptions = Object.create(baseOptions)
// 初始化一个空数组,用于存储编译过程中的错误信息
const errors: WarningMessage[] = []
// 初始化一个空数组,用于存储编译过程中的提示信息
const tips: WarningMessage[] = []
// 定义一个 warn 函数,用于在编译过程中记录警告信息或错误信息
// 它接收一个消息 msg,一个位置范围 range,以及一个可选的提示信息 tip
let warn = (msg,range,tip) => {
// 根据是否有 tip 参数,将警告信息推入到对应的数组(errors 或 tips)中
;(tip ? tips : errors).push(msg)
}
if (options) {
// 在开发模式下,如果启用了输出源码范围的功能,则进行以下处理
if (__DEV__ && options.outputSourceRange) {
// 计算模板字符串开头的空白字符数量
const leadingSpaceLength = template.match(/^\s*/)![0].length
// 重新定义 warn 函数,使其能够处理源码范围,并考虑开头的空白字符
warn = (msg,range,tip) => {
// 将 msg 转换为 WarningMessage 对象
const data: WarningMessage = typeof msg === 'string' ? { msg } : msg
if (range) {
// 调整 start 和 end 以考虑开头的空白字符
if (range.start != null) {data.start = range.start + leadingSpaceLength }
if (range.end != null) { data.end = range.end + leadingSpaceLength }
}
// 根据是否有 tip 参数,将警告信息推入到对应的数组(errors 或 tips)中
;(tip ? tips : errors).push(data)
}
}
if (options.modules) { // 如果提供了自定义模块,则将它们合并到 finalOptions.modules 中
finalOptions.modules = (baseOptions.modules || []).concat(options.modules)
}
if (options.directives) { // 如果提供了自定义指令,则将它们合并到 finalOptions.directives中
finalOptions.directives = extend(Object.create(baseOptions.directives || null),
options.directives
)
}
for (const key in options) { // 复制其他选项到 finalOptions 中
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key as keyof CompilerOptions]
}
}
}
finalOptions.warn = warn // 将 warn 函数赋值给 finalOptions.warn
// 调用 baseCompile 函数进行编译,传入处理后的模板字符串和最终的编译选项
const compiled = baseCompile(template.trim(), finalOptions)
if (__DEV__) {detectErrors(compiled.ast, warn)} // 在开发模式下,检测编译后的 AST 中的错误
// 将错误信息和提示信息赋值给编译结果对象
compiled.errors = errors
compiled.tips = tips
return compiled // 返回编译结果
}
// 返回包含 compile 函数和 compileToFunctions 函数的对象
// compileToFunctions 函数用于将模板编译成可执行的渲染函数
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}