首先进入到src/core/instance/index.js
,可以看到定义了一个Vue
构造函数,内容很简单,如果不是生产环境并且不是通过new
关键字创建对象的话,就在控制台打印一个warn
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)
}
后面调用了几个函数,用来在Vue
对象上创建各种属性或者方法
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
1.initMixin
初始化方法,首先为当前vm
设置一个_uid
,然后添加属性_isVue
,其目的在于监听数据变化时过滤vm
,_isComponent
是内部创建子组件时才会添加为true
的属性,然后走到else
分支,调用resolveConstructorOptions
会获取构造器父级的options
,然后调用mergeOptions
合并父级的options
以及本身传入的options
,最后生成的options
包含components、directives
等属性
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)
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)
}
}
}
1.1initLifecycle
这个方法首先会设置vm
的一个父子节点、根节点信息,然后会定义一些生命周期相关的属性,比如_isMounted、_isDestroyed、_isBeingDestroyed
等
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
}
1.2initEvents
通过名字可以看出是一个初始化事件相关的方法
export function initEvents (vm: Component) {
// events表示父组件绑定在当前组件上的事件
vm._events = Object.create(null)
// 属性表示父组件是否通过"@hook:"把钩子函数绑定在当前组件上
vm._hasHookEvent = false
// init parent attached events
// 同样是来表示父组件绑定在当前组件上的事件
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
如果listeners
有值,则调用updateComponentListeners
这个方法,其内部调用了updateListeners
,其中的add
方法会调用vm.$on
,vm.$on
会监听当前实例上的自定义事件,remove
会调用vm.$off
,移出这个事件监听
function add (event, fn) {
target.$on(event, fn)
}
function remove (event, fn) {
target.$off(event, fn)
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
// 保存当前的vm引用
target = vm
// 传入listeners,父组件绑定在当前组件上的事件,oldListeners同理,不过在第一次初始化的时候为空
// 其内部会遍历listeners调用add方法添加监听事件,同时移出oldListeners
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
1.3initRender
主要是添加了一些虚拟dom、slot
等相关的属性和方法
export function initRender (vm: Component) {
// 表示虚拟dom节点
vm._vnode = null
// 表示当前实例render得到的Vnode
vm._staticTrees = null
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode tree
const renderContext = parentVnode && parentVnode.context
// 生成插槽内容
vm.$slots = resolveSlots(options._renderChildren, renderContext)
// 作用域插槽,此时为空
vm.$scopedSlots = emptyObject
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
......
}
resolveSlots
方法
export function resolveSlots (
children: ?Array<VNode>,
context: ?Component
): { [key: string]: Array<VNode> } {
if (!children || !children.length) {
return {}
}
const slots = {}
// 遍历当前节点chilren
for (let i = 0, l = children.length; i < l; i++) {
const child = children[i]
// 获取data
const data = child.data
if (data && data.attrs && data.attrs.slot) {
delete data.attrs.slot
}
if ((child.context === context || child.fnContext === context) &&
data && data.slot != null
) {
// 拿到插槽名称
const name = data.slot
const slot = (slots[name] || (slots[name] = []))
if (child.tag === 'template') {
slot.push.apply(slot, child.children || [])
} else {
slot.push(child)
}
} else {
// 如果 data.slot 不存在,则是默认插槽的内容,则把对应的 child 添加到 slots.defaults 中
(slots.default || (slots.default = [])).push(child)
}
}
// 最后过滤一些空内容
for (const name in slots) {
if (slots[name].every(isWhitespace)) {
delete slots[name]
}
}
return slots
}
调用钩子函数beforeCreate
1.4initProvide和initInjection
function initProvide (vm) {
var provide = vm.$options.provide;
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide;
}
}
function initInjections (vm) {
// 获取当前节点上的inject属性
var result = resolveInject(vm.$options.inject, vm);
if (result) {
toggleObserving(false);
// 遍历这些属性,defineReactive,将这些属性变成响应式的
Object.keys(result).forEach(function (key) {
/* istanbul ignore else */
{
defineReactive$$1(vm, key, result[key], function () {
warn(
"Avoid mutating an injected value directly since the changes will be " +
"overwritten whenever the provided component re-renders. " +
"injection being mutated: \"" + key + "\"",
vm
);
});
}
});
toggleObserving(true);
}
}
1.5initState
主要是初始化一些数据和属性,比如props
、methods
、data
、computed
、watch
,
function initState (vm) {
vm._watchers = [];
var 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);
}
}
初始化props
属性
function initProps (vm, propsOptions) {
// 获取当前节点的props属性值
var propsData = vm.$options.propsData || {};
var props = vm._props = {};
var isRoot = !vm.$parent;
// 用于保存当前组件的props里的key,以便之后在父组件更新props时可以直接使用数组迭代
var keys = vm.$options._propKeys = [];
var isRoot = !vm.$parent;
if (!isRoot) {
toggleObserving(false);
}
var loop = function ( key ) {
keys.push(key);
// validateProp验证当前prop[key]是否propsOptions定义的要求
var value = validateProp(key, propsOptions, propsData, vm);
{
var hyphenatedKey = hyphenate(key);
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
vm
);
}
// 添加响应式属性,这样props里面的值改变之后,组件会自动更新视图
defineReactive$$1(props, key, value, function () {
if (!isRoot && !isUpdatingChildComponent) {
warn(
"Avoid mutating a prop directly since the value will be " +
"overwritten whenever the parent component re-renders. " +
"Instead, use a data or computed property based on the prop's " +
"value. Prop being mutated: \"" + key + "\"",
vm
);
}
});
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, "_props", key);
}
};
// 循环遍历key
for (var key in propsOptions) loop( key );
toggleObserving(true);
}
初始化methods
属性
function initMethods (vm, methods) {
// props属性用于判断methods中的方法名是否和props的属性重名
var props = vm.$options.props;
for (var key in methods) {
{
if (typeof methods[key] !== 'function') {
warn(
"Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +
"Did you reference the function correctly?",
vm
);
}
// 如果props中有同名属性,则报错
if (props && hasOwn(props, key)) {
warn(
("Method \"" + key + "\" has already been defined as a prop."),
vm
);
}
// 如果key是以$或_开头则,也报错
if ((key in vm) && isReserved(key)) {
warn(
"Method \"" + key + "\" conflicts with an existing Vue instance method. " +
"Avoid defining component methods that start with _ or $."
);
}
}
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
}
}
初始化data
属性
function initData (vm) {
// 获取data属性
var data = vm.$options.data;
// 如果data是一个function,则调用getData返回里面的值,否则直接将data属性赋给_data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
// 如果data属性不是一个对象,设置data为空对象,并打印一个warn
if (!isPlainObject(data)) {
data = {};
warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
);
}
// 获取data keys,props属性,methods属性
var keys = Object.keys(data);
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
while (i--) {
var key = keys[i];
{
// 当前data的key如果和methods中的方法重名,抛出警告
if (methods && hasOwn(methods, key)) {
warn(
("Method \"" + key + "\" has already been defined as a data property."),
vm
);
}
}
// 当前data的key如果和props中的属性重名,抛出警告
if (props && hasOwn(props, key)) {
warn(
"The data property \"" + key + "\" is already declared as a prop. " +
"Use prop default value instead.",
vm
);
} else if (!isReserved(key)) {
proxy(vm, "_data", key);
}
}
// 双向数据绑定data
observe(data, true /* asRootData */);
}
初始化computed
属性
function initComputed (vm, computed) {
// $flow-disable-line
var watchers = vm._computedWatchers = Object.create(null);
// computed properties are just getters during SSR
var isSSR = isServerRendering();
for (var key in computed) {
// 每个computed key所定义的方法
var userDef = computed[key];
// 将该方法赋值给getter变量
var getter = typeof userDef === 'function' ? userDef : userDef.get;
if (getter == null) {
// 如果为空,打印错误
warn(
("Getter is missing for computed property \"" + key + "\"."),
vm
);
}
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
// 如果当前节点computed上未有key这个方法
if (!(key in vm)) {
// defineComputed方法主要是为当前节点的computed属性添加响应式更新方法
// 可以在defineComputed定义最后看到 Object.defineProperty(target, key, sharedPropertyDefinition)
defineComputed(vm, key, userDef);
} else {
if (key in vm.$data) {
warn(("The computed property \"" + key + "\" is already defined in data."), vm);
} else if (vm.$options.props && key in vm.$options.props) {
warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
}
}
}
}
初始化watch
属性比较简单,在做了相应的判断后直接添加一个响应式更新属性,这里不多赘述,调用完initState
之后,会调用created
钩子函数,此时vm
上的属性
// _init
vm._uid = 0
vm._isVue = true
vm.$options = {
components: {
KeepAlive,
Transition,
TransitionGroup
},
directives: {
model,
show
},
methods: {},
computed: {},
watch: {},
filters: {},
_base: Vue,
el: '#app',
data: function mergedInstanceDataFn(){}
}
vm._renderProxy = vm
vm._self = vm
// initLifecycle
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._events = Object.create(null)
vm._hasHookEvent = false
// initRender
vm.$vnode = null
vm._vnode = null
vm._staticTrees = null
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
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)
// 在 initState 中添加的属性
vm._watchers = []
vm._data
vm.message
执行完上面步骤后,会调用vm.$mount
// 保存之前定义的$mount方法,然后重写Vue.prototype.$mount
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// 查找当前节点
el = el && query(el)
// 如果事body元素或者documentElement元素则抛错
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
}
// 拿到options
const options = this.$options
// 如果节点上没有render函数
if (!options.render) {
// 获取template,template可以是#id、模板字符串、dom元素
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
// 获取templatge innerHTML
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) {
// 如果不是#id、模板字符串,直接获取其innerHTML
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
// 如果没有template,则获取el以及其子内容作为模板
template = getOuterHTML(el)
}
if (template) {
......
}
}
// 有render函数,直接执行mount.call(this, el, hydrating)
return mount.call(this, el, hydrating)
}
function getOuterHTML (el: Element): string {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
上面最后调用了mount.call(this, el, hydrating)
,其方法对应于
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
......
// 调用beforeMount钩子函数
callHook(vm, 'beforeMount')
let updateComponent
......
// 更新当前节点的方法
updateComponent = () => {
// vm._render会返回一个render字符串,_update其内部会调用patch方法来进行节点的增删改
vm._update(vm._render(), hydrating)
}
// 创建一个watch对象,在调用updateComponent之前会先调用before方法
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
// 如果是更新节点,调用beforeUpdate钩子函数
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// 当页面渲染完成后,调用钩子函数mounted
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}