vue2.7.16源码解读
vue2的源码调试比较容易,先将代码依赖安装好,然后执行构建开发命令,这要调试时看到的代码和原文件代码差不多。
js
pnpm install
// 构建dev环境
pnpm dev
// 这时会在dist目录下创建一个vue.js的文件
接着找到examples 文件目录,随便找一个示例文件,修改index.html 中引用vue的路径,改成我们刚刚生产的vue.js。本地启动这个index.html就可以断点调试了。
html
<body>
<div id="app"></div>
<script src="../../../dist/vue.js"></script>
<script src="app.js"></script>
</body>
vue初始化流程
可以按照我下面梳理的流程,找到对应位置打上断点看一下执行流程,能快速理清楚运行过程,加深理解。
vue执行入口文件、对应源码目录src/core/instatnce/index.ts
js
// 在外面使用new Vue调用的就是这个方法
function Vue(options) {
if (__DEV__ && !(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// 初始化vue,这里的_init方法是来自initMixin挂在原型上的
this._init(options)
}
// 挂在_init方法
initMixin(Vue)
// 在vue原型上挂在$data、$props、$set、$delete、$watch方法
stateMixin(Vue)
// 在vue原型上挂在$on、$once、$off、$emit方法
eventsMixin(Vue)
// 在vue原型上挂在_update、$forceUpdate、$destory方法
lifecycleMixin(Vue)
// 在vue原型上挂在$nextTick、render方法
renderMixin(Vue)
export default Vue as unknown as GlobalAPI
初始化的核心流程在_init方法中,下面看一下源码内容。源码目录src/core/instance/init.ts
js
export function initMixin(Vue: typeof Component) {
Vue.prototype._init = function (options?: Record<string, any>) {
const vm: Component = this
vm._uid = uid++
let startTag, endTag
vm._isVue = true
vm.__v_skip = true
vm._scope = new EffectScope(true /* detached */)
vm._scope.parent = undefined
vm._scope._vm = true
if (options && options._isComponent) {
initInternalComponent(vm, options as any)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor as any),
options || {},
vm
)
}
if (__DEV__) {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// 将vm实例挂在到self属性后期可以访问
vm._self = vm
// 初始化属性如$root、_watcher等属性,并不是执行生命周期方法
initLifecycle(vm)
// 初始化listeners属性值
initEvents(vm)
// 初始化插槽,给$attrs、$listeners添加响应式
initRender(vm)
// beforeCreate 生命周期
callHook(vm, 'beforeCreate', undefined, false /* setContext */)
// 给注入的inject添加响应式
initInjections(vm) // resolve injections before data/props
// 按照下面顺序进行执行
// 先初始化props
// 初始化setup,在2.7中setup是介于beforeCreate和created之间的
// 初始化methods
// 初始化data
// 初始化computed
// 初始化watch
initState(vm)
// 给provide添加响应式
// 这里的数据来源于data、props和inject,因此要等data、props、inject初始化完之后
initProvide(vm) // resolve provide after data/props
// create生命周期
callHook(vm, 'created')
/* istanbul ignore if */
if (__DEV__ && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
// new Vue是参数是不是配置el,有的话就进行挂在
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
js
// 上面说的initState源码
export function initState(vm: Component) {
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
// Composition API
initSetup(vm)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
const ob = observe((vm._data = {}))
ob && ob.vmCount++
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
上面流程走完一般会进入到$mount方法中,将元素挂在到dom上。目录地址src/platforms/web/runtime/index.ts
js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
// 更新组件
return mountComponent(this, el, hydrating)
}
js
// 这段代码有删减,去掉了dev环境判断
// 目录地址 src/core/instance/lifecycle.ts
export function mountComponent(
vm: Component,
el: Element | null | undefined,
hydrating?: boolean
): Component {
vm.$el = el
// beforeMount生命周期
callHook(vm, 'beforeMount')
let updateComponent = () => {
// 在watcher中注册的更新渲染方法,最后会进_update方法
vm._update(vm._render(), hydrating)
}
const watcherOptions: WatcherOptions = {
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}
// 创建一个渲染watcher
// 我们在watcher的构造函数中设置了这个,因为watcher的初始补丁可能会调用$forceUpdate(例如在子组件的挂载钩子中),
// 这依赖于vm._watcher已经被定义
new Watcher(
vm,
updateComponent,
noop,
watcherOptions,
true /* isRenderWatcher */
)
hydrating = false
// 刷新"pre"watcher队列
// 刷新"pre"watcher队列的原因是,在组件setup函数中创建的watcher,它们的flush属性为"pre",
// 这意味着它们应该在组件渲染之前运行。
// 但是,由于组件渲染是异步的,watcher的运行也可能是异步的,这可能导致watcher在组件渲染之后运行。
// 为了确保watcher在组件渲染之前运行,我们需要刷新"pre"watcher队列。
// flush buffer for flush: "pre" watchers queued in setup()
const preWatchers = vm._preWatchers
if (preWatchers) {
for (let i = 0; i < preWatchers.length; i++) {
preWatchers[i].run()
}
}
// 手动挂载的实例,调用自身的mounted钩子
// mounted钩子在inserted钩子中调用,用于渲染创建的子组件
if (vm.$vnode == null) {
vm._isMounted = true
// mounted生命周期
callHook(vm, 'mounted')
}
return vm
}
js
// 目录地址 src/core/instance/lifecycle.ts
// 这个是执行lifecycleMixin的时候挂载的_update方法,前面第一个代码片段有介绍
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// 当不存在上一个Vnode虚拟节点,执行初始化,直接渲染
// __patch__方法走的就时vnode-diff,更新节点到dom对应的流程,后面单独详情介绍
if (!prevVnode) {
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// 更新节点时走的流程
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
let wrapper: Component | undefined = vm
while (
wrapper &&
wrapper.$vnode &&
wrapper.$parent &&
wrapper.$vnode === wrapper.$parent._vnode
) {
wrapper.$parent.$el = wrapper.$el
wrapper = wrapper.$parent
}
}
到这里页面已经更新出对应的UI,初始化流程基本结束了
vue中响应式如何实现
我们知道在vue中data、props、computed、provided、inject这类数据有响应式,我们就data这种数据的响应式看一下源码中是如何实现的。在上面初始化流程中看到initData是处理data相关的数据。
js
// 路径地址:src/core/instance/state.ts
function initData(vm: Component) {
let data: any = vm.$options.data
data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
if (!isPlainObject(data)) {
data = {}
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (__DEV__) {
// methods和data不能用相同的名称
if (methods && hasOwn(methods, key)) {
warn(`Method "${key}" has already been defined as a data property.`, vm)
}
}
// props和data不能有相同的名称
if (props && hasOwn(props, key)) {
__DEV__ &&
warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) { // 检查是不是以$或_开头的
// 代理实例中_data属性,
// 将vm上的key属性进行操作拦截定义,后面响应式触发会进入到对应拦截
proxy(vm, `_data`, key)
}
}
// 添加响应式拦截
const ob = observe(data)
ob && ob.vmCount++
}
js
// 路径地址:src/core/instance/state.ts
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
}
// 我们日常写的this.xx 或者this.xx = 123 这样的读写操作,就会触发这里的拦截
Object.defineProperty(target, key, sharedPropertyDefinition)
}
js
// 路径地址:src/core/observer/index.ts
export function observe(
value: any,
shallow?: boolean,
ssrMockReactivity?: boolean
): Observer | void {
// 已经添加过响应式直接返回
if (value && hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
return value.__ob__
}
if (
shouldObserve &&
(ssrMockReactivity || !isServerRendering()) &&
(isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value.__v_skip /* ReactiveFlags.SKIP */ &&
!isRef(value) &&
!(value instanceof VNode)
) {
// 创建一个Observer的实例,进行拦截数据的操作
return new Observer(value, shallow, ssrMockReactivity)
}
}
js
// 路径地址:src/core/observer/index.ts
export class Observer {
dep: Dep
vmCount: number // number of vms that have this object as root $data
constructor(public value: any, public shallow = false, public mock = false) {
this.dep = mock ? mockDep : new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (isArray(value)) {
if (!mock) {
if (hasProto) {
// 改写原型中的修改数组的7个方法
;(value as any).__proto__ = arrayMethods
} else {
for (let i = 0, l = arrayKeys.length; i < l; i++) {
const key = arrayKeys[i]
def(value, key, arrayMethods[key])
}
}
}
if (!shallow) {
// 递归数组的响应式绑定
this.observeArray(value)
}
} else {
// 遍历对象上所有的key
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// 给每一个key添加响应式拦截
defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
}
}
}
js
// 路径地址:src/core/observer/index.ts
// 使用Object.defineProperty拦截对象,重写数据原型方法的操作都在这个方法里面
export function defineReactive(
obj: object,
key: string,
val?: any,
customSetter?: Function | null,
shallow?: boolean,
mock?: boolean,
observeEvenIfShallow = false
) {
// 依赖收集实例
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if (
(!getter || setter) &&
(val === NO_INITIAL_VALUE || arguments.length === 2)
) {
val = obj[key]
}
// 基础类型的数据会返回undefined,数组和对象类型会递归操作
let childOb = shallow ? val && val.__ob__ : observe(val, false, mock)
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({
target: obj,
type: TrackOpTypes.GET,
key
})
} else {
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()
}
if (setter) {
setter.call(obj, newVal)
} else if (getter) {
// #7981: for accessor properties without setter
return
} else if (!shallow && isRef(value) && !isRef(newVal)) {
value.value = newVal
return
} else {
val = newVal
}
childOb = shallow ? newVal && newVal.__ob__ : observe(newVal, false, mock)
// 修改数据通知对应的watcher进行视图修改
if (__DEV__) {
dep.notify({
type: TriggerOpTypes.SET,
target: obj,
key,
newValue: newVal,
oldValue: value
})
} else {
dep.notify()
}
}
})
return dep
}
js
observeArray(value: any[]) {
// 数组类型的递归便利绑定响应式
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i], false, this.mock)
}
}
上面这些代码大部门都是建立依赖关系、绑定watcher更新逻辑,以便于修改数据触发对应的watcher进行更新。下面就来看一下修改数据代码是怎么实现的。
js
export function proxy(target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key]
}
// 当我们修改this上的属性值时,会触发这个set方法
// 在给当前的this[sourceKey][key] = val 赋值操作会进一步触发对应的set拦截
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
js
// 上面代码的赋值操作,会触发这里的set操作
export function defineReactive(
obj: object,
key: string,
val?: any,
customSetter?: Function | null,
shallow?: boolean,
mock?: boolean,
observeEvenIfShallow = false
) {
...
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
...
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
if (!hasChanged(value, newVal)) {
return
}
if (__DEV__ && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else if (getter) {
// #7981: for accessor properties without setter
return
} else if (!shallow && isRef(value) && !isRef(newVal)) {
// 兼容vue3中的语法
value.value = newVal
return
} else {
val = newVal
}
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()
}
}
})
return dep
}
js
// 路径: src/core/observer/dep.ts
// Dep类中的通知方法
notify(info?: DebuggerEventExtraInfo) {
const subs = this.subs.filter(s => s) as DepTarget[]
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
})
}
// 依次触发对应的watcher的update方法
sub.update()
}
}
// 路径: src/core/observer/watcher.ts
// watcher类中的更新方法
update() {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
// 放入更新队列中
queueWatcher(this)
}
}
// 路径: src/core/observer/scheduler.ts
export function queueWatcher(watcher: Watcher) {
const id = watcher.id
if (has[id] != null) {
return
}
if (watcher === Dep.target && watcher.noRecurse) {
return
}
has[id] = true
// 表示当前是否正在刷新队列
// 第一会把待更新的watcher放入到队列中
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
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)
}
}
// 路径:src/core/util/next-tick.ts
export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
let _resolve
// 把更新的方法放入到callbacks中
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e: any) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
// nextTick回调方法
// 从callbacks中取出watcher更新方法,依次执行
function flushCallbacks() {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
上面的流程走完,数据的更新对应的更新流程就操作完了,接下来会触发前面初始化流程中说的_update方法会执行,然后模版中依赖的数据会依次触发get拦截获取到最新的数据,渲染到页面。
下面看一下debugger流程
先直接进入initState中





针对数据使用object.defineProperty进行拦截操作


修改数据,先触发vue实例上的拦截

再触发具体数据的拦截

触发通知方法,遍历对应订阅的watcher,执行更新方法,后面会走到初始化时的_update方法,在处理temlate的vnode,最后执行渲染流程

以上就是对vue2.7的初始化和响应式实现进行的梳理和对应代码阅读记录,接下来将完成对vdom-diff流程和vue的模版编译梳理。