一切技术都是以寻找问题最优解为目的的,永远不要为了学习某项技术而把问题复杂化
简介
Vue2.x 源码分析 - import Vue 到 new Vue() 实例初始化到页面渲染做了些啥
源码目录
src
├── compiler # 编译相关
├── core # 核心代码
├── platforms # 不同平台的支持
├── server # 服务端渲染
├── sfc # .vue 文件解析
├── shared # 共享代码
core
core 目录包含了 Vue.js 的核心代码,包括内置组件、全局 API 封装,Vue 实例化、观察者、虚拟 DOM、工具函数等等。
core 核心代码入口
目录:/src/core/index.js
主要处理了
// 1. 执行文件 /src/core/instance/index.js
import Vue from './instance/index'
// 2. 初始化全局API
initGlobalAPI(Vue)
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
initGlobalAPI(Vue)
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode && this.$vnode.ssrContext
}
})
// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
})
Vue.version = '__VERSION__'
export default Vue
import Vue 阶段,Vue 构造函数还未实例化
Vue 构造函数
目录:/src/core/instance/index.js
// 1. 初始化 Vue 构造函数
// 2. 执行下列函数、把 Vue 作为参数传入
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// initMixin 方法中将 _init 挂载到了Vue.prototype
// new Vue()/new VueComponent() 调用
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
initMixin(Vue)
目录:/src/core/instance/init.js
1. 挂载 _init 方法到 Vue.prototype (Vue 构造函数调用、VueComponent 构造函数调用)
2. 在 new Vue() 的时候去执行 this._init()
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
...
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
stateMixin(Vue)
目录:/src/core/instance/state.js
1. 给 vm.$data、vm.$props 属性设置只能读取
2. vm.$data === vm._data 等于组件 data 函数里return的数据
3. vm.$props 等于组件props里的数据
4. 挂载 $set、$del 方法到 Vue.prototype
5. 挂载 $watch 方法到 Vue.prototype,里边的 options.user = true 说明是创建 userWatcher 的方法(可以存在多个userWatcher)
export function stateMixin (Vue: Class<Component>) {
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
watcher.teardown()
}
}
}
eventsMixin(Vue)
目录:/src/core/instance/events.js
1. 挂载 $on、$once、$off、$emit 方法到 Vue.prototype
2. Vue 的 event Bus 是利用这些方法处理的,发布订阅模式
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
...
return vm
}
Vue.prototype.$once = function (event: string, fn: Function): Component {
...
return vm
}
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
...
return vm
}
Vue.prototype.$emit = function (event: string): Component {
...
return vm
}
}
lifecycleMixin(Vue)
目录:/src/core/instance/lifecycle.js
1. 挂载 _update 方法到 Vue.prototype,渲染时会调用,里边会去执行 Vue 的 diff 算法
2. 挂载 $forceUpdate 方法到 Vue.prototype,走 vm._watcher.update() 渲染
3. 挂载 $destroy 方法到 Vue.prototype,销毁的时候调用,执行 beforeDestroy、destroyed 钩子
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
...
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
...
}
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
Vue.prototype.$destroy = function () {
...
callHook(vm, 'beforeDestroy')
...
callHook(vm, 'destroyed')
...
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
renderMixin(Vue)
目录:/src/core/instance/render.js
1. 挂载 $nextTick 方法到 Vue.prototype,nextTick 是一个异步处理的微任务,页面更新是异步处理的,执行 $nextTick 就是把 nextTick 的回调函数放到当前事件循环中的微任务最尾端,所以回调函数中能实时拿到数据更新后的最新 dom
2. nextTick:先判断是否支持 Promise,不支持 Promise 则判断是否支持 MutationObserver,不支持 MutationObserver 则判断是否支持 setImmediate,都不支持执行 setTimeout(callback, 0)
3. 挂载 _render 方法到 Vue.prototype,渲染时会调用,里边创建了虚拟DOM(vnode)
export function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode {
...
return vnode
}
}
initGlobalAPI(Vue)
目录:/src/core/global-api/index.js
// 1. 处理了 Vue.use() 方法
initUse(Vue)
// 2. 处理了 Vue.mixin() 方法
initMixin(Vue)
// 3. 处理了 Vue.extend() 方法,里边声明了 VueComponent 构造函数,也就是组件实例的构造函数,用原型式继承绑定了原型链关系,VueComponent.prototype = new Vue()
initExtend(Vue)
// 4. 处理了 Vue.component()、Vue.directive()、Vue.filter(),Vue.component() 内部去调用了 Vue.extend()
initAssetRegisters(Vue)
export function initAssetRegisters (Vue: GlobalAPI) {
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
export function initGlobalAPI (Vue: GlobalAPI) {
// config
const configDef = {}
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {
configDef.set = () => {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
Object.defineProperty(Vue, 'config', configDef)
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}
new Vue() 阶段,执行 this._init()
目录:/src/core/instance/init.js
// 1. 初始化生命周期
initLifecycle(vm)
// 2. 初始化事件
initEvents(vm)
// 3. 初始化渲染
initRender(vm)
// 4. 执行 beforeCreate 钩子
callHook(vm, 'beforeCreate')
// 5. 初始化 inject
initInjections(vm) // resolve injections before data/props
// 6. 初始化状态(数据)【props、methods、data、computed、watch】
initState(vm)
// 7. 初始化 provide
initProvide(vm) // resolve provide after data/props
// 8. 执行 created 钩子
callHook(vm, 'created')
// 9. 执行挂载,和构建方式有关,不同的构建方式有不同的挂载方式
vm.$mount(vm.$options.el)
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
initLifecycle(vm)
目录:/src/core/instance/lifecycle.js
初始化生命周期属性
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
initEvents(vm)
目录:/src/core/instance/events.js
处理事件监听,用发布订阅的模式进行了回调处理,这块理解的不够透彻,有没有大佬给推文指点下
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
export function updateListeners (
on: Object,
oldOn: Object,
add: Function,
remove: Function,
createOnceHandler: Function,
vm: Component
) {
let name, def, cur, old, event
for (name in on) {
def = cur = on[name]
old = oldOn[name]
event = normalizeEvent(name)
/* istanbul ignore if */
if (__WEEX__ && isPlainObject(def)) {
cur = def.handler
event.params = def.params
}
if (isUndef(cur)) {
process.env.NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
vm
)
} else if (isUndef(old)) {
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur, vm)
}
if (isTrue(event.once)) {
cur = on[name] = createOnceHandler(event.name, cur, event.capture)
}
add(event.name, cur, event.capture, event.passive, event.params)
} else if (cur !== old) {
old.fns = cur
on[name] = old
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture)
}
}
}
initRender(vm)
目录:/src/core/instance/render.js
创建了 vm.$createElement 方法
vm.$attrs、vm.$listeners 属性响应式处理
export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
}
callHook(vm, 'beforeCreate')
目录:/src/core/instance/lifecycle.js
vm.$emit('hook:' + hook) 订阅钩子
export function callHook (vm: Component, hook: string) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}
initInjections(vm)
目录:/src/core/instance/inject.js
在处理 data/props 之前处理 inject 的数据并响应式处理
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
initState(vm)
目录:/src/core/instance/state.js
按照顺序处理了,并且验证唯一性
1. props:initProps(vm, opts.props),响应式处理
2. methods:initMethods(vm, opts.methods),里边用了 bind() 使得 this 指向 vm,所以 methods 里边的函 数不能用箭头函数去处理,箭头函数本身没有this,会导致函数偏离原来处理
3. data:initData(vm),没有data会默认一个{},会深层遍历数组和对象,并利用 Object.definedPrototype 响应式处理
4. computed:initComputed(vm, opts.computed),遍历 computed 里的计算属性,并给 vm 实例绑定 _computedWatchers 属性,会创建多个 new Watcher() 实例,lazy: true 表示是 computedWatcher(每个组件可以存在多个computedWatcher)
5. watch:initWatch(vm, opts.watch) 深层遍历 watch 去创建 new Watcher() 实例,user:true 表示是 userWatcher
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initProvide(vm)
目录:/src/core/instance/inject.js
在处理 data/props 之后处理 provide
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
callHook(vm, 'created')
目录:/src/core/instance/lifecycle.js
vm.$emit('hook:' + hook) 订阅钩子
vm.options.el)
render 方式运行
场景:
new Vue({
el: '#app',
router,
i18n,
store,
render: h => h(App)
})
render => vdom => UI
目录:/src/platforms/web/entry-runtime.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
mountComponent(this, el, hydrating)
1. vm.$options.render 不存在时创建虚拟根节点并赋值给 vm.$options.render
2. 执行钩子 callHook(vm, 'beforeMount')
3. 创建渲染 Watcher,每个组件仅有一个渲染 Watcher,并设置了回调钩子 callHook(vm, 'beforeUpdate'),当有数据更新时,会在 scheduler.js 中执行 beforeUpdate 钩子,相应的 updated 钩子也会跟着执行,如果组件中使用了 keepAlive 组件则会循环遍历子组件去执行对应的 activated、deactivated 钩子
4. vm._render()
5. vm._update()
6. 执行钩子 callHook(vm, 'mounted') 首次渲染结束
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
...
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
Watcher类(观察者)
目录:/src/observer/watcher.js
三种类型 Watcher
1. computedWatcher(computed 计算属性),每个组件能有多个,不能执行异步操作,需要return
2. userWatcher(watch 监听),每个组件能有多个,能执行异步操作
3. renderWatcher(页面渲染),每个组件只能有一个
创建顺序和执行顺序都是 computedWatcher > userWatcher > renderWatcher,组件中 this._watchers 数组记录了 watcher 数据
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
this.active = true
this.dirty = this.lazy // for lazy watchers
deep:值为 true userWatcher 深度监听属性
user:值为 true 用于 userWather
lazy:值为 true 用于 computedWatcher;值为 false 执行 this.get()
sync:不太理解,字面意思同步处理
before:用于触发 beforeUpdate 钩子
dirty:用于 computedWatcher 中判断是返回缓存数据还是最新数据,值为 true 获取最新数据并返回,值为 false 返回上一次获取的缓存数据
cb:userWatcher 的回调函数
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} 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
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
computedWatcher
目录:/src/core/instance/state.js
// 怎么理解?假设当前 watcher = b
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 根据 watcher.dirty 标识来决定是获取最新数据还是上一次的缓存数据
if (watcher.dirty) {
watcher.evaluate()
}
// 这里的 Dep.target 一定不是当前 watcher b,是 vm.a 属性的处理的时候需要用到 b 这个计算属性,然后触发此处的getter,并把 b 收集到 vm.a 对应的 watcher 对应的 deps 中
// defineReactive 中的处理也是一样的道理
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
<template>
<div>
{{ test }}
</div>
</template>
<script>
export default {
data() {
return {
testValue: "is test value"
}
},
computed: {
test() {
return this.testValue;
}
}
}
/**
* 怎么理解 computedWatcher?
* 0. 按照步骤依次执行
* 1. 解析组件用 Object.definedPrototype 给 this.testValue 属性绑定 get set 方法做数据劫持
* 2. 解析组件遍历 computed 创建 computedWatcher new Watcher(),并给 computed 计算属性绑定 get set 方法做数据劫持(createComputedGetter)
* 3. 初始化渲染 renderWatcher
* 4. 开始渲染解析模板语法 {{ test }},使用到 test 计算属性,触发 get 方法 createComputedGetter,执行计算属性的回调函数,返回 this.testValue
* 5. 触发 this.testValue 的 get 方法,触发里边的 dep 依赖收集,当前 Dep.target 是 test 计算属性对应的 computedWatcher
* 6. 收集依赖,test 计算属性对应的 computedWatcher 依赖 this.testValue
*/
</script>
0. 假设当前计算属性中 test 使用了 vm.testValue 的数据,并且渲染在页面上 {{ test }}
1. initComputed() 中遍历计算属性初始化 computedWatcher 实例 new Wather()
2. 初始化 this.dirty = this.lazy = true,this.lazy 为 true 不执行 this.get()
3. userDef/userDef.get 用于 Object.defineProperty 里的 get 函数,computed 可以是函数也可以是含有 get、set 的对象
4. 当用到了计算属性就会执行 Object.defineProperty 里的 get 函数触发 computedGetter(),接着执行 watcher.evaluate()
5. 执行 this.get(),并且设置 Dep.target = 当前 computedWatcher
6. this.get() 里边执行了 value = this.getter.call(vm, vm),此时 this.getter 为计算属性的回调函数,执行返回了 vm.testValue,则会触发 vm.testValue 的 get 处理
7. vm.testValue 的 get 处理在 defineReactive 函数中,判断 Dep.target 是否存在进行依赖收集并存入computedWatcher.deps
8. 到此 computedWatcher 单个实例的处理和依赖收集结束
userWatcher
目录:/src/core/instance/state.js
<template></template>
<script>
export default {
data() {
return {
test: "is test",
name: "is name",
arr: [1],
other: [1],
};
},
mounted() {
setTimeout(() => {
this.name = "change name";
this.other.push(2);
this.arr.push(2);
});
// 打印 watch 对象
console.log(this.$options.watch);
},
methods: {
watcherHandle(val) {
console.log(val, "这是 watcherHandle"); // [1,2] "这是 watcherHandle"
},
otherHandler(val) {
console.log(val, "这是 otherHandler"); // [1,2] "这是 otherHandler"
}
},
/**
* watch 的几种写法
* 1. watch 属性是一个对象,immediate 在方法1上有效
* 2. watch 属性是一个函数
* 3. watch 属性是一个字符串(string),绑定在vm.string
* 4. watch 属性是一个数组,里边可以是对象,接下来的处理和方法1相同
* 5. watch 属性是一个数组,里边可以是字符串,接下来的处理和方法3相同
*/
watch: {
test: {
handler(val) {
console.log(val); // is test
},
immediate: true,
},
name: function (val) {
console.log(val); // change name
},
other: "otherHandler",
arr: [
{
handler(val) {
console.log(val, "这是数组里的 handler"); // [1,2] 这是数组里的 handler
},
},
"watcherHandle"
],
},
};
/**
* 怎么理解 userWatcher?这里解释 immediate 为 true 的情况
* 0. 按照步骤依次执行
* 1. 解析组件用 Object.definedPrototype 给 this.test 里的属性绑定 get set 方法做数据劫持
* 2. 解析组件遍历 watch 创建 userWatcher new Watcher(),遇到 watch 里的属性是数组则深层遍历
* 3. new Watcher() 解析 this.getter = parsePath(expOrFn) 返回一个闭包函数 a
* 4. watcher 中执行 this.get() 去相应的执行 value = this.getter.call(vm, vm)
* 5. watcher this.getter.call(vm, vm) 执行本质上是执行 parsePath(expOrFn) 返回的闭包函数 a
* 6. 闭包函数 a() 里面使用 this.test
* 7. 触发 this.test 的 get 方法,触发里边的 dep 依赖收集,当前 Dep.target 是 test 计算属性对应的 computedWatcher
* 8. 收集依赖,test watcher对应的 userWatcher 依赖 this.test
* 9. options.immediate 为 true 的情况下 触发 invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
* 10. invokeWithErrorHandling 里边触发了 test watch 的 handler 处理,并执行后续的程序
*/
</script>
<style>
</style>
// /src/core/instance/state.js
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
watcher.teardown()
}
}
// /src/core/observer/watcher.js
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
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
)
}
}
// /src/core/util/lang.js
export function parsePath (path: string): any {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
// obj[segments[i]] 等价于去访问 vm 实例的属性
obj = obj[segments[i]]
}
return obj
}
}
renderWatcher
目录:/src/core/instance/lifecycle.js
1. 这里讲述页面首次渲染,不包含数据更新
2. new Watcher() 后执行 this.get() 将 Dep.target 设置为当前 renderWatcher
3. 执行 this.getter.call(vm, vm),等价于去执行了 vm._update(vm._render(), hydrating)
4. vm._render() 生成虚拟DOM,使用到数据
5. 触发数据中对应的 get,执行依赖收集
6. vm._update() 渲染到真实DOM上,执行 mounted 钩子,挂载结束
7. 有个地方不是很确认,第一次渲染在没有数据更新的情况下到底是同步还是异步,源码中 renderWatcher 到 mounted 钩子这里的操作一直是同步的,是在 vm._update() 中把虚拟DOM挂载到真实DOM上的;但是数据更新是会去触发 dep.notify(),然后通过 nextTick 异步去执行 watcher.run() 再回到 watcher.get() 重新去更新依赖。
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
总结
第一次写文章,源码中有很多不理解的地方,希望大佬们多多指导!!!
Vue2.x 源码分析 - import Vue 到 new Vue() 实例初始化到页面渲染做了些啥
src
├── compiler # 编译相关
├── core # 核心代码
├── platforms # 不同平台的支持
├── server # 服务端渲染
├── sfc # .vue 文件解析
├── shared # 共享代码
core
core 目录包含了 Vue.js 的核心代码,包括内置组件、全局 API 封装,Vue 实例化、观察者、虚拟 DOM、工具函数等等。
core 核心代码入口
目录:/src/core/index.js
主要处理了
// 1. 执行文件 /src/core/instance/index.js
import Vue from './instance/index'
// 2. 初始化全局API
initGlobalAPI(Vue)
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
initGlobalAPI(Vue)
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode && this.$vnode.ssrContext
}
})
// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
})
Vue.version = '__VERSION__'
export default Vue
import Vue 阶段,Vue 构造函数还未实例化
Vue 构造函数
目录:/src/core/instance/index.js
// 1. 初始化 Vue 构造函数
// 2. 执行下列函数、把 Vue 作为参数传入
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// initMixin 方法中将 _init 挂载到了Vue.prototype
// new Vue()/new VueComponent() 调用
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
initMixin(Vue)
目录:/src/core/instance/init.js
1. 挂载 _init 方法到 Vue.prototype (Vue 构造函数调用、VueComponent 构造函数调用)
2. 在 new Vue() 的时候去执行 this._init()
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
...
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
stateMixin(Vue)
目录:/src/core/instance/state.js
1. 给 vm.$data、vm.$props 属性设置只能读取
2. vm.$data === vm._data 等于组件 data 函数里return的数据
3. vm.$props 等于组件props里的数据
4. 挂载 $set、$del 方法到 Vue.prototype
5. 挂载 $watch 方法到 Vue.prototype,里边的 options.user = true 说明是创建 userWatcher 的方法(可以存在多个userWatcher)
export function stateMixin (Vue: Class<Component>) {
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
watcher.teardown()
}
}
}
eventsMixin(Vue)
目录:/src/core/instance/events.js
1. 挂载 $on、$once、$off、$emit 方法到 Vue.prototype
2. Vue 的 event Bus 是利用这些方法处理的,发布订阅模式
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
...
return vm
}
Vue.prototype.$once = function (event: string, fn: Function): Component {
...
return vm
}
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
...
return vm
}
Vue.prototype.$emit = function (event: string): Component {
...
return vm
}
}
lifecycleMixin(Vue)
目录:/src/core/instance/lifecycle.js
1. 挂载 _update 方法到 Vue.prototype,渲染时会调用,里边会去执行 Vue 的 diff 算法
2. 挂载 $forceUpdate 方法到 Vue.prototype,走 vm._watcher.update() 渲染
3. 挂载 $destroy 方法到 Vue.prototype,销毁的时候调用,执行 beforeDestroy、destroyed 钩子
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
...
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
...
}
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
Vue.prototype.$destroy = function () {
...
callHook(vm, 'beforeDestroy')
...
callHook(vm, 'destroyed')
...
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
renderMixin(Vue)
目录:/src/core/instance/render.js
1. 挂载 $nextTick 方法到 Vue.prototype,nextTick 是一个异步处理的微任务,页面更新是异步处理的,执行 $nextTick 就是把 nextTick 的回调函数放到当前事件循环中的微任务最尾端,所以回调函数中能实时拿到数据更新后的最新 dom
2. nextTick:先判断是否支持 Promise,不支持 Promise 则判断是否支持 MutationObserver,不支持 MutationObserver 则判断是否支持 setImmediate,都不支持执行 setTimeout(callback, 0)
3. 挂载 _render 方法到 Vue.prototype,渲染时会调用,里边创建了虚拟DOM(vnode)
export function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode {
...
return vnode
}
}
initGlobalAPI(Vue)
目录:/src/core/global-api/index.js
// 1. 处理了 Vue.use() 方法
initUse(Vue)
// 2. 处理了 Vue.mixin() 方法
initMixin(Vue)
// 3. 处理了 Vue.extend() 方法,里边声明了 VueComponent 构造函数,也就是组件实例的构造函数,用原型式继承绑定了原型链关系,VueComponent.prototype = new Vue()
initExtend(Vue)
// 4. 处理了 Vue.component()、Vue.directive()、Vue.filter(),Vue.component() 内部去调用了 Vue.extend()
initAssetRegisters(Vue)
export function initAssetRegisters (Vue: GlobalAPI) {
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
export function initGlobalAPI (Vue: GlobalAPI) {
// config
const configDef = {}
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {
configDef.set = () => {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
Object.defineProperty(Vue, 'config', configDef)
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}
new Vue() 阶段,执行 this._init()
目录:/src/core/instance/init.js
// 1. 初始化生命周期
initLifecycle(vm)
// 2. 初始化事件
initEvents(vm)
// 3. 初始化渲染
initRender(vm)
// 4. 执行 beforeCreate 钩子
callHook(vm, 'beforeCreate')
// 5. 初始化 inject
initInjections(vm) // resolve injections before data/props
// 6. 初始化状态(数据)【props、methods、data、computed、watch】
initState(vm)
// 7. 初始化 provide
initProvide(vm) // resolve provide after data/props
// 8. 执行 created 钩子
callHook(vm, 'created')
// 9. 执行挂载,和构建方式有关,不同的构建方式有不同的挂载方式
vm.$mount(vm.$options.el)
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
initLifecycle(vm)
目录:/src/core/instance/lifecycle.js
初始化生命周期属性
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
initEvents(vm)
目录:/src/core/instance/events.js
处理事件监听,用发布订阅的模式进行了回调处理,这块理解的不够透彻,有没有大佬给推文指点下
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
export function updateListeners (
on: Object,
oldOn: Object,
add: Function,
remove: Function,
createOnceHandler: Function,
vm: Component
) {
let name, def, cur, old, event
for (name in on) {
def = cur = on[name]
old = oldOn[name]
event = normalizeEvent(name)
/* istanbul ignore if */
if (__WEEX__ && isPlainObject(def)) {
cur = def.handler
event.params = def.params
}
if (isUndef(cur)) {
process.env.NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
vm
)
} else if (isUndef(old)) {
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur, vm)
}
if (isTrue(event.once)) {
cur = on[name] = createOnceHandler(event.name, cur, event.capture)
}
add(event.name, cur, event.capture, event.passive, event.params)
} else if (cur !== old) {
old.fns = cur
on[name] = old
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture)
}
}
}
initRender(vm)
目录:/src/core/instance/render.js
创建了 vm.$createElement 方法
vm.$attrs、vm.$listeners 属性响应式处理
export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
}
callHook(vm, 'beforeCreate')
目录:/src/core/instance/lifecycle.js
vm.$emit('hook:' + hook) 订阅钩子
export function callHook (vm: Component, hook: string) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}
initInjections(vm)
目录:/src/core/instance/inject.js
在处理 data/props 之前处理 inject 的数据并响应式处理
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
initState(vm)
目录:/src/core/instance/state.js
按照顺序处理了,并且验证唯一性
1. props:initProps(vm, opts.props),响应式处理
2. methods:initMethods(vm, opts.methods),里边用了 bind() 使得 this 指向 vm,所以 methods 里边的函 数不能用箭头函数去处理,箭头函数本身没有this,会导致函数偏离原来处理
3. data:initData(vm),没有data会默认一个{},会深层遍历数组和对象,并利用 Object.definedPrototype 响应式处理
4. computed:initComputed(vm, opts.computed),遍历 computed 里的计算属性,并给 vm 实例绑定 _computedWatchers 属性,会创建多个 new Watcher() 实例,lazy: true 表示是 computedWatcher(每个组件可以存在多个computedWatcher)
5. watch:initWatch(vm, opts.watch) 深层遍历 watch 去创建 new Watcher() 实例,user:true 表示是 userWatcher
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initProvide(vm)
目录:/src/core/instance/inject.js
在处理 data/props 之后处理 provide
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
callHook(vm, 'created')
目录:/src/core/instance/lifecycle.js
vm.$emit('hook:' + hook) 订阅钩子
vm.options.el)
render 方式运行
场景:
new Vue({
el: '#app',
router,
i18n,
store,
render: h => h(App)
})
render => vdom => UI
目录:/src/platforms/web/entry-runtime.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
mountComponent(this, el, hydrating)
1. vm.$options.render 不存在时创建虚拟根节点并赋值给 vm.$options.render
2. 执行钩子 callHook(vm, 'beforeMount')
3. 创建渲染 Watcher,每个组件仅有一个渲染 Watcher,并设置了回调钩子 callHook(vm, 'beforeUpdate'),当有数据更新时,会在 scheduler.js 中执行 beforeUpdate 钩子,相应的 updated 钩子也会跟着执行,如果组件中使用了 keepAlive 组件则会循环遍历子组件去执行对应的 activated、deactivated 钩子
4. vm._render()
5. vm._update()
6. 执行钩子 callHook(vm, 'mounted') 首次渲染结束
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
...
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
Watcher类(观察者)
目录:/src/observer/watcher.js
三种类型 Watcher
1. computedWatcher(computed 计算属性),每个组件能有多个,不能执行异步操作,需要return
2. userWatcher(watch 监听),每个组件能有多个,能执行异步操作
3. renderWatcher(页面渲染),每个组件只能有一个
创建顺序和执行顺序都是 computedWatcher > userWatcher > renderWatcher,组件中 this._watchers 数组记录了 watcher 数据
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
this.active = true
this.dirty = this.lazy // for lazy watchers
deep:值为 true userWatcher 深度监听属性
user:值为 true 用于 userWather
lazy:值为 true 用于 computedWatcher;值为 false 执行 this.get()
sync:不太理解,字面意思同步处理
before:用于触发 beforeUpdate 钩子
dirty:用于 computedWatcher 中判断是返回缓存数据还是最新数据,值为 true 获取最新数据并返回,值为 false 返回上一次获取的缓存数据
cb:userWatcher 的回调函数
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} 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
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
computedWatcher
目录:/src/core/instance/state.js
// 怎么理解?假设当前 watcher = b
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 根据 watcher.dirty 标识来决定是获取最新数据还是上一次的缓存数据
if (watcher.dirty) {
watcher.evaluate()
}
// 这里的 Dep.target 一定不是当前 watcher b,是 vm.a 属性的处理的时候需要用到 b 这个计算属性,然后触发此处的getter,并把 b 收集到 vm.a 对应的 watcher 对应的 deps 中
// defineReactive 中的处理也是一样的道理
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
<template>
<div>
{{ test }}
</div>
</template>
<script>
export default {
data() {
return {
testValue: "is test value"
}
},
computed: {
test() {
return this.testValue;
}
}
}
/**
* 怎么理解 computedWatcher?
* 0. 按照步骤依次执行
* 1. 解析组件用 Object.definedPrototype 给 this.testValue 属性绑定 get set 方法做数据劫持
* 2. 解析组件遍历 computed 创建 computedWatcher new Watcher(),并给 computed 计算属性绑定 get set 方法做数据劫持(createComputedGetter)
* 3. 初始化渲染 renderWatcher
* 4. 开始渲染解析模板语法 {{ test }},使用到 test 计算属性,触发 get 方法 createComputedGetter,执行计算属性的回调函数,返回 this.testValue
* 5. 触发 this.testValue 的 get 方法,触发里边的 dep 依赖收集,当前 Dep.target 是 test 计算属性对应的 computedWatcher
* 6. 收集依赖,test 计算属性对应的 computedWatcher 依赖 this.testValue
*/
</script>
0. 假设当前计算属性中 test 使用了 vm.testValue 的数据,并且渲染在页面上 {{ test }}
1. initComputed() 中遍历计算属性初始化 computedWatcher 实例 new Wather()
2. 初始化 this.dirty = this.lazy = true,this.lazy 为 true 不执行 this.get()
3. userDef/userDef.get 用于 Object.defineProperty 里的 get 函数,computed 可以是函数也可以是含有 get、set 的对象
4. 当用到了计算属性就会执行 Object.defineProperty 里的 get 函数触发 computedGetter(),接着执行 watcher.evaluate()
5. 执行 this.get(),并且设置 Dep.target = 当前 computedWatcher
6. this.get() 里边执行了 value = this.getter.call(vm, vm),此时 this.getter 为计算属性的回调函数,执行返回了 vm.testValue,则会触发 vm.testValue 的 get 处理
7. vm.testValue 的 get 处理在 defineReactive 函数中,判断 Dep.target 是否存在进行依赖收集并存入computedWatcher.deps
8. 到此 computedWatcher 单个实例的处理和依赖收集结束
userWatcher
目录:/src/core/instance/state.js
<template></template>
<script>
export default {
data() {
return {
test: "is test",
name: "is name",
arr: [1],
other: [1],
};
},
mounted() {
setTimeout(() => {
this.name = "change name";
this.other.push(2);
this.arr.push(2);
});
// 打印 watch 对象
console.log(this.$options.watch);
},
methods: {
watcherHandle(val) {
console.log(val, "这是 watcherHandle"); // [1,2] "这是 watcherHandle"
},
otherHandler(val) {
console.log(val, "这是 otherHandler"); // [1,2] "这是 otherHandler"
}
},
/**
* watch 的几种写法
* 1. watch 属性是一个对象,immediate 在方法1上有效
* 2. watch 属性是一个函数
* 3. watch 属性是一个字符串(string),绑定在vm.string
* 4. watch 属性是一个数组,里边可以是对象,接下来的处理和方法1相同
* 5. watch 属性是一个数组,里边可以是字符串,接下来的处理和方法3相同
*/
watch: {
test: {
handler(val) {
console.log(val); // is test
},
immediate: true,
},
name: function (val) {
console.log(val); // change name
},
other: "otherHandler",
arr: [
{
handler(val) {
console.log(val, "这是数组里的 handler"); // [1,2] 这是数组里的 handler
},
},
"watcherHandle"
],
},
};
/**
* 怎么理解 userWatcher?这里解释 immediate 为 true 的情况
* 0. 按照步骤依次执行
* 1. 解析组件用 Object.definedPrototype 给 this.test 里的属性绑定 get set 方法做数据劫持
* 2. 解析组件遍历 watch 创建 userWatcher new Watcher(),遇到 watch 里的属性是数组则深层遍历
* 3. new Watcher() 解析 this.getter = parsePath(expOrFn) 返回一个闭包函数 a
* 4. watcher 中执行 this.get() 去相应的执行 value = this.getter.call(vm, vm)
* 5. watcher this.getter.call(vm, vm) 执行本质上是执行 parsePath(expOrFn) 返回的闭包函数 a
* 6. 闭包函数 a() 里面使用 this.test
* 7. 触发 this.test 的 get 方法,触发里边的 dep 依赖收集,当前 Dep.target 是 test 计算属性对应的 computedWatcher
* 8. 收集依赖,test watcher对应的 userWatcher 依赖 this.test
* 9. options.immediate 为 true 的情况下 触发 invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
* 10. invokeWithErrorHandling 里边触发了 test watch 的 handler 处理,并执行后续的程序
*/
</script>
<style>
</style>
// /src/core/instance/state.js
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
watcher.teardown()
}
}
// /src/core/observer/watcher.js
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
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
)
}
}
// /src/core/util/lang.js
export function parsePath (path: string): any {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
// obj[segments[i]] 等价于去访问 vm 实例的属性
obj = obj[segments[i]]
}
return obj
}
}
renderWatcher
目录:/src/core/instance/lifecycle.js
1. 这里讲述页面首次渲染,不包含数据更新
2. new Watcher() 后执行 this.get() 将 Dep.target 设置为当前 renderWatcher
3. 执行 this.getter.call(vm, vm),等价于去执行了 vm._update(vm._render(), hydrating)
4. vm._render() 生成虚拟DOM,使用到数据
5. 触发数据中对应的 get,执行依赖收集
6. vm._update() 渲染到真实DOM上,执行 mounted 钩子,挂载结束
7. 有个地方不是很确认,第一次渲染在没有数据更新的情况下到底是同步还是异步,源码中 renderWatcher 到 mounted 钩子这里的操作一直是同步的,是在 vm._update() 中把虚拟DOM挂载到真实DOM上的;但是数据更新是会去触发 dep.notify(),然后通过 nextTick 异步去执行 watcher.run() 再回到 watcher.get() 重新去更新依赖。
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
总结
第一次写文章,源码中有很多不理解的地方,希望大佬们多多指导!!!
core 目录包含了 Vue.js 的核心代码,包括内置组件、全局 API 封装,Vue 实例化、观察者、虚拟 DOM、工具函数等等。
目录:/src/core/index.js
主要处理了
// 1. 执行文件 /src/core/instance/index.js
import Vue from './instance/index'
// 2. 初始化全局API
initGlobalAPI(Vue)
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
initGlobalAPI(Vue)
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode && this.$vnode.ssrContext
}
})
// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
})
Vue.version = '__VERSION__'
export default Vue
import Vue 阶段,Vue 构造函数还未实例化
Vue 构造函数
目录:/src/core/instance/index.js
// 1. 初始化 Vue 构造函数
// 2. 执行下列函数、把 Vue 作为参数传入
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// initMixin 方法中将 _init 挂载到了Vue.prototype
// new Vue()/new VueComponent() 调用
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
initMixin(Vue)
目录:/src/core/instance/init.js
1. 挂载 _init 方法到 Vue.prototype (Vue 构造函数调用、VueComponent 构造函数调用)
2. 在 new Vue() 的时候去执行 this._init()
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
...
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
stateMixin(Vue)
目录:/src/core/instance/state.js
1. 给 vm.$data、vm.$props 属性设置只能读取
2. vm.$data === vm._data 等于组件 data 函数里return的数据
3. vm.$props 等于组件props里的数据
4. 挂载 $set、$del 方法到 Vue.prototype
5. 挂载 $watch 方法到 Vue.prototype,里边的 options.user = true 说明是创建 userWatcher 的方法(可以存在多个userWatcher)
export function stateMixin (Vue: Class<Component>) {
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
watcher.teardown()
}
}
}
eventsMixin(Vue)
目录:/src/core/instance/events.js
1. 挂载 $on、$once、$off、$emit 方法到 Vue.prototype
2. Vue 的 event Bus 是利用这些方法处理的,发布订阅模式
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
...
return vm
}
Vue.prototype.$once = function (event: string, fn: Function): Component {
...
return vm
}
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
...
return vm
}
Vue.prototype.$emit = function (event: string): Component {
...
return vm
}
}
lifecycleMixin(Vue)
目录:/src/core/instance/lifecycle.js
1. 挂载 _update 方法到 Vue.prototype,渲染时会调用,里边会去执行 Vue 的 diff 算法
2. 挂载 $forceUpdate 方法到 Vue.prototype,走 vm._watcher.update() 渲染
3. 挂载 $destroy 方法到 Vue.prototype,销毁的时候调用,执行 beforeDestroy、destroyed 钩子
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
...
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
...
}
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
Vue.prototype.$destroy = function () {
...
callHook(vm, 'beforeDestroy')
...
callHook(vm, 'destroyed')
...
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
renderMixin(Vue)
目录:/src/core/instance/render.js
1. 挂载 $nextTick 方法到 Vue.prototype,nextTick 是一个异步处理的微任务,页面更新是异步处理的,执行 $nextTick 就是把 nextTick 的回调函数放到当前事件循环中的微任务最尾端,所以回调函数中能实时拿到数据更新后的最新 dom
2. nextTick:先判断是否支持 Promise,不支持 Promise 则判断是否支持 MutationObserver,不支持 MutationObserver 则判断是否支持 setImmediate,都不支持执行 setTimeout(callback, 0)
3. 挂载 _render 方法到 Vue.prototype,渲染时会调用,里边创建了虚拟DOM(vnode)
export function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode {
...
return vnode
}
}
initGlobalAPI(Vue)
目录:/src/core/global-api/index.js
// 1. 处理了 Vue.use() 方法
initUse(Vue)
// 2. 处理了 Vue.mixin() 方法
initMixin(Vue)
// 3. 处理了 Vue.extend() 方法,里边声明了 VueComponent 构造函数,也就是组件实例的构造函数,用原型式继承绑定了原型链关系,VueComponent.prototype = new Vue()
initExtend(Vue)
// 4. 处理了 Vue.component()、Vue.directive()、Vue.filter(),Vue.component() 内部去调用了 Vue.extend()
initAssetRegisters(Vue)
export function initAssetRegisters (Vue: GlobalAPI) {
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
export function initGlobalAPI (Vue: GlobalAPI) {
// config
const configDef = {}
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {
configDef.set = () => {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
Object.defineProperty(Vue, 'config', configDef)
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}
new Vue() 阶段,执行 this._init()
目录:/src/core/instance/init.js
// 1. 初始化生命周期
initLifecycle(vm)
// 2. 初始化事件
initEvents(vm)
// 3. 初始化渲染
initRender(vm)
// 4. 执行 beforeCreate 钩子
callHook(vm, 'beforeCreate')
// 5. 初始化 inject
initInjections(vm) // resolve injections before data/props
// 6. 初始化状态(数据)【props、methods、data、computed、watch】
initState(vm)
// 7. 初始化 provide
initProvide(vm) // resolve provide after data/props
// 8. 执行 created 钩子
callHook(vm, 'created')
// 9. 执行挂载,和构建方式有关,不同的构建方式有不同的挂载方式
vm.$mount(vm.$options.el)
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
initLifecycle(vm)
目录:/src/core/instance/lifecycle.js
初始化生命周期属性
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
initEvents(vm)
目录:/src/core/instance/events.js
处理事件监听,用发布订阅的模式进行了回调处理,这块理解的不够透彻,有没有大佬给推文指点下
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
export function updateListeners (
on: Object,
oldOn: Object,
add: Function,
remove: Function,
createOnceHandler: Function,
vm: Component
) {
let name, def, cur, old, event
for (name in on) {
def = cur = on[name]
old = oldOn[name]
event = normalizeEvent(name)
/* istanbul ignore if */
if (__WEEX__ && isPlainObject(def)) {
cur = def.handler
event.params = def.params
}
if (isUndef(cur)) {
process.env.NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
vm
)
} else if (isUndef(old)) {
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur, vm)
}
if (isTrue(event.once)) {
cur = on[name] = createOnceHandler(event.name, cur, event.capture)
}
add(event.name, cur, event.capture, event.passive, event.params)
} else if (cur !== old) {
old.fns = cur
on[name] = old
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture)
}
}
}
initRender(vm)
目录:/src/core/instance/render.js
创建了 vm.$createElement 方法
vm.$attrs、vm.$listeners 属性响应式处理
export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
}
callHook(vm, 'beforeCreate')
目录:/src/core/instance/lifecycle.js
vm.$emit('hook:' + hook) 订阅钩子
export function callHook (vm: Component, hook: string) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}
initInjections(vm)
目录:/src/core/instance/inject.js
在处理 data/props 之前处理 inject 的数据并响应式处理
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
initState(vm)
目录:/src/core/instance/state.js
按照顺序处理了,并且验证唯一性
1. props:initProps(vm, opts.props),响应式处理
2. methods:initMethods(vm, opts.methods),里边用了 bind() 使得 this 指向 vm,所以 methods 里边的函 数不能用箭头函数去处理,箭头函数本身没有this,会导致函数偏离原来处理
3. data:initData(vm),没有data会默认一个{},会深层遍历数组和对象,并利用 Object.definedPrototype 响应式处理
4. computed:initComputed(vm, opts.computed),遍历 computed 里的计算属性,并给 vm 实例绑定 _computedWatchers 属性,会创建多个 new Watcher() 实例,lazy: true 表示是 computedWatcher(每个组件可以存在多个computedWatcher)
5. watch:initWatch(vm, opts.watch) 深层遍历 watch 去创建 new Watcher() 实例,user:true 表示是 userWatcher
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initProvide(vm)
目录:/src/core/instance/inject.js
在处理 data/props 之后处理 provide
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
callHook(vm, 'created')
目录:/src/core/instance/lifecycle.js
vm.$emit('hook:' + hook) 订阅钩子
vm.options.el)
render 方式运行
场景:
new Vue({
el: '#app',
router,
i18n,
store,
render: h => h(App)
})
render => vdom => UI
目录:/src/platforms/web/entry-runtime.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
mountComponent(this, el, hydrating)
1. vm.$options.render 不存在时创建虚拟根节点并赋值给 vm.$options.render
2. 执行钩子 callHook(vm, 'beforeMount')
3. 创建渲染 Watcher,每个组件仅有一个渲染 Watcher,并设置了回调钩子 callHook(vm, 'beforeUpdate'),当有数据更新时,会在 scheduler.js 中执行 beforeUpdate 钩子,相应的 updated 钩子也会跟着执行,如果组件中使用了 keepAlive 组件则会循环遍历子组件去执行对应的 activated、deactivated 钩子
4. vm._render()
5. vm._update()
6. 执行钩子 callHook(vm, 'mounted') 首次渲染结束
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
...
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
Watcher类(观察者)
目录:/src/observer/watcher.js
三种类型 Watcher
1. computedWatcher(computed 计算属性),每个组件能有多个,不能执行异步操作,需要return
2. userWatcher(watch 监听),每个组件能有多个,能执行异步操作
3. renderWatcher(页面渲染),每个组件只能有一个
创建顺序和执行顺序都是 computedWatcher > userWatcher > renderWatcher,组件中 this._watchers 数组记录了 watcher 数据
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
this.active = true
this.dirty = this.lazy // for lazy watchers
deep:值为 true userWatcher 深度监听属性
user:值为 true 用于 userWather
lazy:值为 true 用于 computedWatcher;值为 false 执行 this.get()
sync:不太理解,字面意思同步处理
before:用于触发 beforeUpdate 钩子
dirty:用于 computedWatcher 中判断是返回缓存数据还是最新数据,值为 true 获取最新数据并返回,值为 false 返回上一次获取的缓存数据
cb:userWatcher 的回调函数
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} 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
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
computedWatcher
目录:/src/core/instance/state.js
// 怎么理解?假设当前 watcher = b
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 根据 watcher.dirty 标识来决定是获取最新数据还是上一次的缓存数据
if (watcher.dirty) {
watcher.evaluate()
}
// 这里的 Dep.target 一定不是当前 watcher b,是 vm.a 属性的处理的时候需要用到 b 这个计算属性,然后触发此处的getter,并把 b 收集到 vm.a 对应的 watcher 对应的 deps 中
// defineReactive 中的处理也是一样的道理
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
<template>
<div>
{{ test }}
</div>
</template>
<script>
export default {
data() {
return {
testValue: "is test value"
}
},
computed: {
test() {
return this.testValue;
}
}
}
/**
* 怎么理解 computedWatcher?
* 0. 按照步骤依次执行
* 1. 解析组件用 Object.definedPrototype 给 this.testValue 属性绑定 get set 方法做数据劫持
* 2. 解析组件遍历 computed 创建 computedWatcher new Watcher(),并给 computed 计算属性绑定 get set 方法做数据劫持(createComputedGetter)
* 3. 初始化渲染 renderWatcher
* 4. 开始渲染解析模板语法 {{ test }},使用到 test 计算属性,触发 get 方法 createComputedGetter,执行计算属性的回调函数,返回 this.testValue
* 5. 触发 this.testValue 的 get 方法,触发里边的 dep 依赖收集,当前 Dep.target 是 test 计算属性对应的 computedWatcher
* 6. 收集依赖,test 计算属性对应的 computedWatcher 依赖 this.testValue
*/
</script>
0. 假设当前计算属性中 test 使用了 vm.testValue 的数据,并且渲染在页面上 {{ test }}
1. initComputed() 中遍历计算属性初始化 computedWatcher 实例 new Wather()
2. 初始化 this.dirty = this.lazy = true,this.lazy 为 true 不执行 this.get()
3. userDef/userDef.get 用于 Object.defineProperty 里的 get 函数,computed 可以是函数也可以是含有 get、set 的对象
4. 当用到了计算属性就会执行 Object.defineProperty 里的 get 函数触发 computedGetter(),接着执行 watcher.evaluate()
5. 执行 this.get(),并且设置 Dep.target = 当前 computedWatcher
6. this.get() 里边执行了 value = this.getter.call(vm, vm),此时 this.getter 为计算属性的回调函数,执行返回了 vm.testValue,则会触发 vm.testValue 的 get 处理
7. vm.testValue 的 get 处理在 defineReactive 函数中,判断 Dep.target 是否存在进行依赖收集并存入computedWatcher.deps
8. 到此 computedWatcher 单个实例的处理和依赖收集结束
userWatcher
目录:/src/core/instance/state.js
<template></template>
<script>
export default {
data() {
return {
test: "is test",
name: "is name",
arr: [1],
other: [1],
};
},
mounted() {
setTimeout(() => {
this.name = "change name";
this.other.push(2);
this.arr.push(2);
});
// 打印 watch 对象
console.log(this.$options.watch);
},
methods: {
watcherHandle(val) {
console.log(val, "这是 watcherHandle"); // [1,2] "这是 watcherHandle"
},
otherHandler(val) {
console.log(val, "这是 otherHandler"); // [1,2] "这是 otherHandler"
}
},
/**
* watch 的几种写法
* 1. watch 属性是一个对象,immediate 在方法1上有效
* 2. watch 属性是一个函数
* 3. watch 属性是一个字符串(string),绑定在vm.string
* 4. watch 属性是一个数组,里边可以是对象,接下来的处理和方法1相同
* 5. watch 属性是一个数组,里边可以是字符串,接下来的处理和方法3相同
*/
watch: {
test: {
handler(val) {
console.log(val); // is test
},
immediate: true,
},
name: function (val) {
console.log(val); // change name
},
other: "otherHandler",
arr: [
{
handler(val) {
console.log(val, "这是数组里的 handler"); // [1,2] 这是数组里的 handler
},
},
"watcherHandle"
],
},
};
/**
* 怎么理解 userWatcher?这里解释 immediate 为 true 的情况
* 0. 按照步骤依次执行
* 1. 解析组件用 Object.definedPrototype 给 this.test 里的属性绑定 get set 方法做数据劫持
* 2. 解析组件遍历 watch 创建 userWatcher new Watcher(),遇到 watch 里的属性是数组则深层遍历
* 3. new Watcher() 解析 this.getter = parsePath(expOrFn) 返回一个闭包函数 a
* 4. watcher 中执行 this.get() 去相应的执行 value = this.getter.call(vm, vm)
* 5. watcher this.getter.call(vm, vm) 执行本质上是执行 parsePath(expOrFn) 返回的闭包函数 a
* 6. 闭包函数 a() 里面使用 this.test
* 7. 触发 this.test 的 get 方法,触发里边的 dep 依赖收集,当前 Dep.target 是 test 计算属性对应的 computedWatcher
* 8. 收集依赖,test watcher对应的 userWatcher 依赖 this.test
* 9. options.immediate 为 true 的情况下 触发 invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
* 10. invokeWithErrorHandling 里边触发了 test watch 的 handler 处理,并执行后续的程序
*/
</script>
<style>
</style>
// /src/core/instance/state.js
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
watcher.teardown()
}
}
// /src/core/observer/watcher.js
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
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
)
}
}
// /src/core/util/lang.js
export function parsePath (path: string): any {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
// obj[segments[i]] 等价于去访问 vm 实例的属性
obj = obj[segments[i]]
}
return obj
}
}
renderWatcher
目录:/src/core/instance/lifecycle.js
1. 这里讲述页面首次渲染,不包含数据更新
2. new Watcher() 后执行 this.get() 将 Dep.target 设置为当前 renderWatcher
3. 执行 this.getter.call(vm, vm),等价于去执行了 vm._update(vm._render(), hydrating)
4. vm._render() 生成虚拟DOM,使用到数据
5. 触发数据中对应的 get,执行依赖收集
6. vm._update() 渲染到真实DOM上,执行 mounted 钩子,挂载结束
7. 有个地方不是很确认,第一次渲染在没有数据更新的情况下到底是同步还是异步,源码中 renderWatcher 到 mounted 钩子这里的操作一直是同步的,是在 vm._update() 中把虚拟DOM挂载到真实DOM上的;但是数据更新是会去触发 dep.notify(),然后通过 nextTick 异步去执行 watcher.run() 再回到 watcher.get() 重新去更新依赖。
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
总结
第一次写文章,源码中有很多不理解的地方,希望大佬们多多指导!!!
目录:/src/core/instance/index.js
// 1. 初始化 Vue 构造函数
// 2. 执行下列函数、把 Vue 作为参数传入
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// initMixin 方法中将 _init 挂载到了Vue.prototype
// new Vue()/new VueComponent() 调用
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
目录:/src/core/instance/init.js
1. 挂载 _init 方法到 Vue.prototype (Vue 构造函数调用、VueComponent 构造函数调用)
2. 在 new Vue() 的时候去执行 this._init()
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
...
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
目录:/src/core/instance/state.js
1. 给 vm.$data、vm.$props 属性设置只能读取
2. vm.$data === vm._data 等于组件 data 函数里return的数据
3. vm.$props 等于组件props里的数据
4. 挂载 $set、$del 方法到 Vue.prototype
5. 挂载 $watch 方法到 Vue.prototype,里边的 options.user = true 说明是创建 userWatcher 的方法(可以存在多个userWatcher)
export function stateMixin (Vue: Class<Component>) {
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
watcher.teardown()
}
}
}
目录:/src/core/instance/events.js
1. 挂载 $on、$once、$off、$emit 方法到 Vue.prototype
2. Vue 的 event Bus 是利用这些方法处理的,发布订阅模式
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
...
return vm
}
Vue.prototype.$once = function (event: string, fn: Function): Component {
...
return vm
}
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
...
return vm
}
Vue.prototype.$emit = function (event: string): Component {
...
return vm
}
}
目录:/src/core/instance/lifecycle.js
1. 挂载 _update 方法到 Vue.prototype,渲染时会调用,里边会去执行 Vue 的 diff 算法
2. 挂载 $forceUpdate 方法到 Vue.prototype,走 vm._watcher.update() 渲染
3. 挂载 $destroy 方法到 Vue.prototype,销毁的时候调用,执行 beforeDestroy、destroyed 钩子
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
...
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
...
}
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
Vue.prototype.$destroy = function () {
...
callHook(vm, 'beforeDestroy')
...
callHook(vm, 'destroyed')
...
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
目录:/src/core/instance/render.js
1. 挂载 $nextTick 方法到 Vue.prototype,nextTick 是一个异步处理的微任务,页面更新是异步处理的,执行 $nextTick 就是把 nextTick 的回调函数放到当前事件循环中的微任务最尾端,所以回调函数中能实时拿到数据更新后的最新 dom
2. nextTick:先判断是否支持 Promise,不支持 Promise 则判断是否支持 MutationObserver,不支持 MutationObserver 则判断是否支持 setImmediate,都不支持执行 setTimeout(callback, 0)
3. 挂载 _render 方法到 Vue.prototype,渲染时会调用,里边创建了虚拟DOM(vnode)
export function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode {
...
return vnode
}
}
目录:/src/core/global-api/index.js
// 1. 处理了 Vue.use() 方法
initUse(Vue)
// 2. 处理了 Vue.mixin() 方法
initMixin(Vue)
// 3. 处理了 Vue.extend() 方法,里边声明了 VueComponent 构造函数,也就是组件实例的构造函数,用原型式继承绑定了原型链关系,VueComponent.prototype = new Vue()
initExtend(Vue)
// 4. 处理了 Vue.component()、Vue.directive()、Vue.filter(),Vue.component() 内部去调用了 Vue.extend()
initAssetRegisters(Vue)
export function initAssetRegisters (Vue: GlobalAPI) {
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
export function initGlobalAPI (Vue: GlobalAPI) {
// config
const configDef = {}
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {
configDef.set = () => {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
Object.defineProperty(Vue, 'config', configDef)
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}
目录:/src/core/instance/init.js
// 1. 初始化生命周期
initLifecycle(vm)
// 2. 初始化事件
initEvents(vm)
// 3. 初始化渲染
initRender(vm)
// 4. 执行 beforeCreate 钩子
callHook(vm, 'beforeCreate')
// 5. 初始化 inject
initInjections(vm) // resolve injections before data/props
// 6. 初始化状态(数据)【props、methods、data、computed、watch】
initState(vm)
// 7. 初始化 provide
initProvide(vm) // resolve provide after data/props
// 8. 执行 created 钩子
callHook(vm, 'created')
// 9. 执行挂载,和构建方式有关,不同的构建方式有不同的挂载方式
vm.$mount(vm.$options.el)
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
initLifecycle(vm)
目录:/src/core/instance/lifecycle.js
初始化生命周期属性
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
initEvents(vm)
目录:/src/core/instance/events.js
处理事件监听,用发布订阅的模式进行了回调处理,这块理解的不够透彻,有没有大佬给推文指点下
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
export function updateListeners (
on: Object,
oldOn: Object,
add: Function,
remove: Function,
createOnceHandler: Function,
vm: Component
) {
let name, def, cur, old, event
for (name in on) {
def = cur = on[name]
old = oldOn[name]
event = normalizeEvent(name)
/* istanbul ignore if */
if (__WEEX__ && isPlainObject(def)) {
cur = def.handler
event.params = def.params
}
if (isUndef(cur)) {
process.env.NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
vm
)
} else if (isUndef(old)) {
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur, vm)
}
if (isTrue(event.once)) {
cur = on[name] = createOnceHandler(event.name, cur, event.capture)
}
add(event.name, cur, event.capture, event.passive, event.params)
} else if (cur !== old) {
old.fns = cur
on[name] = old
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture)
}
}
}
initRender(vm)
目录:/src/core/instance/render.js
创建了 vm.$createElement 方法
vm.$attrs、vm.$listeners 属性响应式处理
export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
}
callHook(vm, 'beforeCreate')
目录:/src/core/instance/lifecycle.js
vm.$emit('hook:' + hook) 订阅钩子
export function callHook (vm: Component, hook: string) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}
initInjections(vm)
目录:/src/core/instance/inject.js
在处理 data/props 之前处理 inject 的数据并响应式处理
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
initState(vm)
目录:/src/core/instance/state.js
按照顺序处理了,并且验证唯一性
1. props:initProps(vm, opts.props),响应式处理
2. methods:initMethods(vm, opts.methods),里边用了 bind() 使得 this 指向 vm,所以 methods 里边的函 数不能用箭头函数去处理,箭头函数本身没有this,会导致函数偏离原来处理
3. data:initData(vm),没有data会默认一个{},会深层遍历数组和对象,并利用 Object.definedPrototype 响应式处理
4. computed:initComputed(vm, opts.computed),遍历 computed 里的计算属性,并给 vm 实例绑定 _computedWatchers 属性,会创建多个 new Watcher() 实例,lazy: true 表示是 computedWatcher(每个组件可以存在多个computedWatcher)
5. watch:initWatch(vm, opts.watch) 深层遍历 watch 去创建 new Watcher() 实例,user:true 表示是 userWatcher
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initProvide(vm)
目录:/src/core/instance/inject.js
在处理 data/props 之后处理 provide
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
callHook(vm, 'created')
目录:/src/core/instance/lifecycle.js
vm.$emit('hook:' + hook) 订阅钩子
vm.options.el)
render 方式运行
场景:
new Vue({
el: '#app',
router,
i18n,
store,
render: h => h(App)
})
render => vdom => UI
目录:/src/platforms/web/entry-runtime.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
mountComponent(this, el, hydrating)
1. vm.$options.render 不存在时创建虚拟根节点并赋值给 vm.$options.render
2. 执行钩子 callHook(vm, 'beforeMount')
3. 创建渲染 Watcher,每个组件仅有一个渲染 Watcher,并设置了回调钩子 callHook(vm, 'beforeUpdate'),当有数据更新时,会在 scheduler.js 中执行 beforeUpdate 钩子,相应的 updated 钩子也会跟着执行,如果组件中使用了 keepAlive 组件则会循环遍历子组件去执行对应的 activated、deactivated 钩子
4. vm._render()
5. vm._update()
6. 执行钩子 callHook(vm, 'mounted') 首次渲染结束
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
...
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
Watcher类(观察者)
目录:/src/observer/watcher.js
三种类型 Watcher
1. computedWatcher(computed 计算属性),每个组件能有多个,不能执行异步操作,需要return
2. userWatcher(watch 监听),每个组件能有多个,能执行异步操作
3. renderWatcher(页面渲染),每个组件只能有一个
创建顺序和执行顺序都是 computedWatcher > userWatcher > renderWatcher,组件中 this._watchers 数组记录了 watcher 数据
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
this.active = true
this.dirty = this.lazy // for lazy watchers
deep:值为 true userWatcher 深度监听属性
user:值为 true 用于 userWather
lazy:值为 true 用于 computedWatcher;值为 false 执行 this.get()
sync:不太理解,字面意思同步处理
before:用于触发 beforeUpdate 钩子
dirty:用于 computedWatcher 中判断是返回缓存数据还是最新数据,值为 true 获取最新数据并返回,值为 false 返回上一次获取的缓存数据
cb:userWatcher 的回调函数
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} 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
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
computedWatcher
目录:/src/core/instance/state.js
// 怎么理解?假设当前 watcher = b
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 根据 watcher.dirty 标识来决定是获取最新数据还是上一次的缓存数据
if (watcher.dirty) {
watcher.evaluate()
}
// 这里的 Dep.target 一定不是当前 watcher b,是 vm.a 属性的处理的时候需要用到 b 这个计算属性,然后触发此处的getter,并把 b 收集到 vm.a 对应的 watcher 对应的 deps 中
// defineReactive 中的处理也是一样的道理
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
<template>
<div>
{{ test }}
</div>
</template>
<script>
export default {
data() {
return {
testValue: "is test value"
}
},
computed: {
test() {
return this.testValue;
}
}
}
/**
* 怎么理解 computedWatcher?
* 0. 按照步骤依次执行
* 1. 解析组件用 Object.definedPrototype 给 this.testValue 属性绑定 get set 方法做数据劫持
* 2. 解析组件遍历 computed 创建 computedWatcher new Watcher(),并给 computed 计算属性绑定 get set 方法做数据劫持(createComputedGetter)
* 3. 初始化渲染 renderWatcher
* 4. 开始渲染解析模板语法 {{ test }},使用到 test 计算属性,触发 get 方法 createComputedGetter,执行计算属性的回调函数,返回 this.testValue
* 5. 触发 this.testValue 的 get 方法,触发里边的 dep 依赖收集,当前 Dep.target 是 test 计算属性对应的 computedWatcher
* 6. 收集依赖,test 计算属性对应的 computedWatcher 依赖 this.testValue
*/
</script>
0. 假设当前计算属性中 test 使用了 vm.testValue 的数据,并且渲染在页面上 {{ test }}
1. initComputed() 中遍历计算属性初始化 computedWatcher 实例 new Wather()
2. 初始化 this.dirty = this.lazy = true,this.lazy 为 true 不执行 this.get()
3. userDef/userDef.get 用于 Object.defineProperty 里的 get 函数,computed 可以是函数也可以是含有 get、set 的对象
4. 当用到了计算属性就会执行 Object.defineProperty 里的 get 函数触发 computedGetter(),接着执行 watcher.evaluate()
5. 执行 this.get(),并且设置 Dep.target = 当前 computedWatcher
6. this.get() 里边执行了 value = this.getter.call(vm, vm),此时 this.getter 为计算属性的回调函数,执行返回了 vm.testValue,则会触发 vm.testValue 的 get 处理
7. vm.testValue 的 get 处理在 defineReactive 函数中,判断 Dep.target 是否存在进行依赖收集并存入computedWatcher.deps
8. 到此 computedWatcher 单个实例的处理和依赖收集结束
userWatcher
目录:/src/core/instance/state.js
<template></template>
<script>
export default {
data() {
return {
test: "is test",
name: "is name",
arr: [1],
other: [1],
};
},
mounted() {
setTimeout(() => {
this.name = "change name";
this.other.push(2);
this.arr.push(2);
});
// 打印 watch 对象
console.log(this.$options.watch);
},
methods: {
watcherHandle(val) {
console.log(val, "这是 watcherHandle"); // [1,2] "这是 watcherHandle"
},
otherHandler(val) {
console.log(val, "这是 otherHandler"); // [1,2] "这是 otherHandler"
}
},
/**
* watch 的几种写法
* 1. watch 属性是一个对象,immediate 在方法1上有效
* 2. watch 属性是一个函数
* 3. watch 属性是一个字符串(string),绑定在vm.string
* 4. watch 属性是一个数组,里边可以是对象,接下来的处理和方法1相同
* 5. watch 属性是一个数组,里边可以是字符串,接下来的处理和方法3相同
*/
watch: {
test: {
handler(val) {
console.log(val); // is test
},
immediate: true,
},
name: function (val) {
console.log(val); // change name
},
other: "otherHandler",
arr: [
{
handler(val) {
console.log(val, "这是数组里的 handler"); // [1,2] 这是数组里的 handler
},
},
"watcherHandle"
],
},
};
/**
* 怎么理解 userWatcher?这里解释 immediate 为 true 的情况
* 0. 按照步骤依次执行
* 1. 解析组件用 Object.definedPrototype 给 this.test 里的属性绑定 get set 方法做数据劫持
* 2. 解析组件遍历 watch 创建 userWatcher new Watcher(),遇到 watch 里的属性是数组则深层遍历
* 3. new Watcher() 解析 this.getter = parsePath(expOrFn) 返回一个闭包函数 a
* 4. watcher 中执行 this.get() 去相应的执行 value = this.getter.call(vm, vm)
* 5. watcher this.getter.call(vm, vm) 执行本质上是执行 parsePath(expOrFn) 返回的闭包函数 a
* 6. 闭包函数 a() 里面使用 this.test
* 7. 触发 this.test 的 get 方法,触发里边的 dep 依赖收集,当前 Dep.target 是 test 计算属性对应的 computedWatcher
* 8. 收集依赖,test watcher对应的 userWatcher 依赖 this.test
* 9. options.immediate 为 true 的情况下 触发 invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
* 10. invokeWithErrorHandling 里边触发了 test watch 的 handler 处理,并执行后续的程序
*/
</script>
<style>
</style>
// /src/core/instance/state.js
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
watcher.teardown()
}
}
// /src/core/observer/watcher.js
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
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
)
}
}
// /src/core/util/lang.js
export function parsePath (path: string): any {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
// obj[segments[i]] 等价于去访问 vm 实例的属性
obj = obj[segments[i]]
}
return obj
}
}
renderWatcher
目录:/src/core/instance/lifecycle.js
1. 这里讲述页面首次渲染,不包含数据更新
2. new Watcher() 后执行 this.get() 将 Dep.target 设置为当前 renderWatcher
3. 执行 this.getter.call(vm, vm),等价于去执行了 vm._update(vm._render(), hydrating)
4. vm._render() 生成虚拟DOM,使用到数据
5. 触发数据中对应的 get,执行依赖收集
6. vm._update() 渲染到真实DOM上,执行 mounted 钩子,挂载结束
7. 有个地方不是很确认,第一次渲染在没有数据更新的情况下到底是同步还是异步,源码中 renderWatcher 到 mounted 钩子这里的操作一直是同步的,是在 vm._update() 中把虚拟DOM挂载到真实DOM上的;但是数据更新是会去触发 dep.notify(),然后通过 nextTick 异步去执行 watcher.run() 再回到 watcher.get() 重新去更新依赖。
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
总结
第一次写文章,源码中有很多不理解的地方,希望大佬们多多指导!!!
第一次写文章,源码中有很多不理解的地方,希望大佬们多多指导!!!