前言
面试中很容易问你聊聊Vue的流程,我们从源码分析new Vue过程做了哪些事情,一个大体的流程。然后,我个人根据源码画了一个思维导图。
代码分析
我们clone一份vue源码,根据package.json找到入口文件。
- 脚本命令"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",
- 全局搜索web-full-dev, 找到entry: resolve('web/entry-runtime-with-compiler.js')
入口文件
src/platforms/web/entry-runtime-with-compiler.js
这个文件主要是解析组件里手写的template,如:Vue.component('test', {template: "<div>test</div>"}),options上不存在render函数,template模板会被编译成render函数,我们平时写的xxx.vue文件是webpack工程通过vue-loader插件解析的。
import Vue from './runtime/index'
const mount = Vue.prototype.$mount
// 覆盖默认的$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
const options = this.$options
if (!options.render) {
let template = options.template
if (template) {
// 编译开始
const { render, staticRenderFns } = compileToFunctions(template, {...}, this)
options.render = render
options.staticRenderFns = staticRenderFns
}
}
return mount.call(this, el, hydrating)
}
$mount函数
这个文件下主要是实现了挂载方法,传入一个回调函数updateComponent给Watcher,数据发生变化时执行dom的挂载或更新。核心就是执行vm._update(vm._render()) vm._render执行的就是options.render函数,获取虚拟node vm._update主要就是通过vm.__patch__将虚拟dom转化成真实dom src/platforms/web/runtime/index.js
import Vue from 'core/index'
Vue.prototype.__patch__ = inBrowser ? patch : noop
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
src/core/instance/lifecycle.js
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
callHook(vm, 'beforeMount')
let 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
}
初始化全局API
这个文件下面主要实现:
- Vue上挂载静态方法,如set、delete、nextTick、use、mixin、extend等
- Vue静态options属性上,添加components、directives、filters等属性
- 注册全局组件,KeepAlive组件
src/core/index.js
import Vue from './instance/index'
initGlobalAPI(Vue)
function initGlobalAPI(Vue: GlobalAPI) {
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive,
};
Vue.set = set;
Vue.delete = del;
Vue.nextTick = nextTick;
Vue.observable = (obj: T): T => {
observe(obj);
return obj;
};
Vue.options = Object.create(null);
ASSET_TYPES.forEach((type) => {
Vue.options[type + "s"] = Object.create(null);
});
Vue.options._base = Vue;
extend(Vue.options.components, builtInComponents);
initUse(Vue);
initMixin(Vue);
initExtend(Vue);
initAssetRegisters(Vue);
}
Vue构造函数
这个文件实现的是Vue的构造函数,原型上添加对应的属性和方法
- 添加_init方法
- 添加props
- 添加自定义事件,once、emit
- 添加渲染相关的事件_update、_render src/core/instance/index.js
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')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
initMixin(Vue)
添加_init方法,new Vue时执行
// new Vue核心就是执行这些方法
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
vm._isVue = true
// 合并options
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._renderProxy = vm
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate') // 执行beforeCreate生命周期
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') // 执行created生命周期
// 挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
// 设置父子关系
function initLifecycle (vm: Component) {
const options = vm.$options
let parent = options.parent // activeInstance
// 将自身实例添加到父实例的$children中
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
...
}
// 父组件使用子组件时注册的事件,在渲染子组件时,会通过$on('xxx'),在子组件内部进行注册,实现父子通信
function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
}
function initRender (vm: Component) {
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode
const renderContext = parentVnode && parentVnode.context
// 获取插槽的信息
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// 此处的$createElement就是h,创建虚拟dom
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
const parentData = parentVnode && parentVnode.data
// 获取父组件传入的未定义到props中的属性$attrs和注册的事件$listeners
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
// inject
export function initInjections (vm: Component) {
const result = Object.create(null)
const keys = Object.keys(vm.$options.inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const provideKey = inject[key].from
let source = vm
// 递归的中父组件中找到_provided
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
}
if (result) {
Object.keys(result).forEach(key => {
defineReactive(vm, key, result[key])
})
}
}
// provide
function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
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);
}
}
stateMixin(Vue)
添加数据相关的属性和方法
function stateMixin(Vue: Class<Component>) {
const dataDef = {};
dataDef.get = function () {
return this._data;
};
const propsDef = {};
propsDef.get = function () {
return this._props;
};
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 {
// ...
}
}
lifecycleMixin(Vue)
处理视图,添加三种类型的方法:dom的渲染、dom的更新、dom的销毁
function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode // 渲染vnode赋值给_vnode
if (!prevVnode) {
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
Vue.prototype.$destroy = function () {
// ...
}
renderMixin(Vue)
添加模板渲染的多种辅助方法、$nextTick方法、_render方法(获取虚拟dom)
renderMixin (Vue) {
installRenderHelpers(Vue.prototype);
Vue.prototype.$nextTick = function (fn) {
return nextTick(fn, this)
};
Vue.prototype._render = function () {
var vm = this;
var ref = vm.$options;
var render = ref.render;
var _parentVnode = ref._parentVnode;
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
);
}
vm.$vnode = _parentVnode;
var vnode;
try {
currentRenderingInstance = vm;
vnode = render.call(vm._renderProxy, vm.$createElement);
} catch (e) {
} finally {
currentRenderingInstance = null;
}
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0];
}
if (!(vnode instanceof VNode)) {
vnode = createEmptyVNode();
}
vnode.parent = _parentVnode;
return vnode
};
}
总结
new Vue的时候,执行Vue的构造函数,执行_init方法,
- 1.例添加的父实例的$children中,
- 2.组件上有父组件的$on方法,则监听该事件
- 3.实例上添加render函数
- 4.执行beforeCreate声明周期函数
- 5.将数据变成响应式
- 6.执行$mounted函数,首先执行编译后的render函数,然后再将返回的vnode通过patch方法转化成真实dom挂载到页面
最后
最后,祝大家周末快乐,面向薪资编程,在2021年的最后一个月加油冲冲冲~