继上一篇“如何开始阅读VUE源码”,我们现在正式开始阅读VUE源码。
首先在代码中执行npm run dev,我们看到package.json后面,这个命令对应的target为web-full-dev
1,进入到配置文件config.js中,找到web-full-dev,找到entry入口文件为web/entry-runtime-with-compiler.js
一百来行代码,主要是扩展了$mount的方法,用来处理el或template
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
return mount.call(this, el, hydrating)
}
2,在上个文件中可以发现Vue实例,来源于platforms/web/runtime/index.js
// install platform patch function
// 安装web平台特有指令和组件
// __patch__:patch就是补丁的意思,补丁函数,执行patch算法进行更新
Vue.prototype.__patch__ = inBrowser ? patch : noop
// public mount method
// $mount方法 将vue组件实例挂载到指定的dom结构上
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
3,vue/src/core/index.js
初始化全局API
initGlobalAPI(Vue)
这个方法下面具体的表现为:global-api/index.js
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
initUse(Vue) // 实现vue的use方法
initMixin(Vue) // 实现vue的Mixin方法
initExtend(Vue) // 实现vue的extend方法
initAssetRegisters(Vue) //注册实现
4,vue/src/core/instance/index.js
Vue构造函数定义
Vue实例API
那其实到这里我们就找到定义Vue实例的地方,接下来我们就看看在这个实例里面又做了那些操作,以及外围又引入了什么方法?
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) // 实现init方法
stateMixin(Vue) // 状态相关api $data,$peops,$set,$delete,$watch
eventsMixin(Vue) //事件相关api $on,$once,$off,$emit
lifecycleMixin(Vue) // 生命周期api _update,$forceUpdate,$destory
renderMixin(Vue) // 渲染api _render,$nextTick
5,接下来看一上面这个文件的_init方法:vue/src/core/instance/init.js
创建组件实例,初始化其数据、属性、事件等
export function initMixin (Vue: Class<Component>) {
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) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
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) // $parent,$root,$children,
initEvents(vm) // 处理父组件传递的事件和回调
initRender(vm) // $slot,$scopedSlots,_c,$createElement
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props 获取注入数据
initState(vm) // 初始化props,methods,data,computed.watch
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)
}
}
}
6,mountComponent (在runtime/index.js里面调用)core/instance/lifecycle.js
执行挂载,获取vdom并转换为dom
7,_initRender() src\core\instance\render.js
渲染组件,获取vdom
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)
8,update() src\core\instance\lifecycle.js
执行更新,将传入vdom转换为dom,初始化时执行的是dom创建操作
我们可以自己写一个Vue实例,去检测他的整个的过程,我们会发现他的整体流程
new Vue() => _init() => $mount() => _render() => _update()