写在前面的话:
因为源码中包含很多校验或者补丁的方法,这些代码暂时不考虑,以后的文章中只分析主干流程核心代码。
上一篇我们找到的 Vue 的构造函数所在文件 /src/core/instance/index.js,
我们看到这个文件代码非常简单,先是定义vue的构造函数,里边除去校验,只剩下 调用 this._init(options)(_inint来自下边的 initMixin)。接着是5个方法。他们的作用分别是:
这篇我们主要分析 Vue的 initMixin 方法。
initMixin 主体
打开 src/core/instance/init.js,initMixin 中主要是 初始化内部组件initInternalComponent 、 合并参数resolveConstructorOptions,并做了生命周期、事件、渲染等初始化(详见下边代码处注释)
/* @flow */
import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'
let uid = 0
export function function initMixin(Vue) {
Vue.prototype._init = function (options) { //初始化函数
...
// 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), // //解析constructor上的options属性的
options || {},
vm
);
}
/* istanbul ignore else */
//初始化 代理 监听
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self 暴露真实的self
vm._self = vm;
initLifecycle(vm); //初始化生命周期 标志
initEvents(vm); //初始化事件
initRender(vm); // 初始化渲染
callHook(vm, 'beforeCreate'); //触发beforeCreate钩子函数
initInjections(vm); // resolve injections before data/props 在数据/道具之前解决注入问题 //初始化 inject
initState(vm); // //初始化状态
initProvide(vm); // resolve provide after data/props 解决后提供数据/道具 provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性,用于组件之间通信。
callHook(vm, 'created'); //触发created钩子函数
...
if (vm.$options.el) {
// Vue 的$mount()为手动挂载,
// 在项目中可用于延时挂载(例如在挂载之前要进行一些其他操作、判断等),之后要手动挂载上。
// new Vue时,el和$mount并没有本质上的不同。
vm.$mount(vm.$options.el);
}
};
}
// initInternalComponent 初始化内部组件
function initInternalComponent(vm, //vue实例
options //选项参数
) {
var opts = vm.$options = Object.create(vm.constructor.options); //vm的参数
// doing this because it's faster than dynamic enumeration. 这样做是因为它比动态枚举快。
// var options = {
// _isComponent: true, //是否是组件
// parent: parent, //组件的父节点
// _parentVnode: vnode, //组件的 虚拟vonde 父节点
// _parentElm: parentElm || null, //父节点的dom el
// _refElm: refElm || null //当前节点 el
// }
var parentVnode = options._parentVnode;
opts.parent = options.parent; //组件的父节点
opts._parentVnode = parentVnode; //组件的 虚拟vonde 父节点
opts._parentElm = options._parentElm; //父节点的dom el
opts._refElm = options._refElm; //当前节点 el
var vnodeComponentOptions = parentVnode.componentOptions; //组件参数
opts.propsData = vnodeComponentOptions.propsData; //组件数据
opts._parentListeners = vnodeComponentOptions.listeners;//组件 事件
opts._renderChildren = vnodeComponentOptions.children; //组件子节点
opts._componentTag = vnodeComponentOptions.tag; //组件的标签
if (options.render) { //渲染函数
opts.render = options.render; //渲染函数
opts.staticRenderFns = options.staticRenderFns; //静态渲染函数
}
}
// resolveConstructorOptions:解析new Vue constructor上的options拓展参数属性的 合并 过滤去重数据
function resolveConstructorOptions(Ctor) {
var options = Ctor.options;
// 有super属性,说明Ctor是Vue.extend构建的子类 继承的子类
if (Ctor.super) { //超类
var superOptions = resolveConstructorOptions(Ctor.super); //回调超类 表示继承父类
var cachedSuperOptions = Ctor.superOptions; // Vue构造函数上的options,如directives,filters,....
if (superOptions !== cachedSuperOptions) { //判断如果 超类的options不等于子类的options 的时候
// super option changed,
// need to resolve new options.
//超级选项改变,
//需要解决新的选项。
Ctor.superOptions = superOptions; //让他的超类选项赋值Ctor.superOptions
// check if there are any late-modified/attached options (#4976) 检查是否有任何后期修改/附加选项(#4976)
// 解决修改选项 转义数据 合并 数据
var modifiedOptions = resolveModifiedOptions(Ctor);
// update base extend options 更新基本扩展选项
if (modifiedOptions) {
//extendOptions合并拓展参数
extend(Ctor.extendOptions, modifiedOptions);
}
// 优先取Ctor.extendOptions 将两个对象合成一个对象 将父值对象和子值对象合并在一起,并且优先取值子值,如果没有则取子值
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
if (options.name) { //如果参数含有name 组件name
options.components[options.name] = Ctor;
}
}
}
return options //返回参数
}
// resolveModifiedOptions: 解决修改options 转义数据 合并 数据
function resolveModifiedOptions(Ctor) {
var modified;
var latest = Ctor.options; //获取选项
var extended = Ctor.extendOptions; //获取拓展的选项
var sealed = Ctor.sealedOptions; //获取子类选项
for (var key in latest) { //遍历最新选项
if (latest[key] !== sealed[key]) { //如果选项不等于子类选项
if (!modified) {
modified = {};
}
//合并参数
modified[key] = dedupe(latest[key], extended[key], sealed[key]);
}
}
//返回合并后的参数
return modified
}
1. initLifecycle
初始化生命周期
目录: src/core/instance/lifecycle.js
//初始化生命周期
function initLifecycle(vm) {
var options = vm.$options;
// locate first non-abstract parent
//定位第一个非抽象父节点
var parent = options.parent;
if (parent && !options.abstract) {
//判断parent父亲节点是否存在,并且判断抽象节点是否存在
while (parent.$options.abstract && parent.$parent) {
//如果有父亲抽象节点,则把父层或爷爷节点 给当前节点的父亲节点
parent = parent.$parent;
}
//子节点添加 vm
parent.$children.push(vm);
}
//添加$parent 参数
vm.$parent = parent;
//判断parent 是否是顶层 root 如果是 则$root赋值给$root
vm.$root = parent ? parent.$root : vm;
// 情况 $children 节点
vm.$children = [];
//获取节点的key
vm.$refs = {};
vm._watcher = null; //观察者
vm._inactive = null; //禁用的组件状态标志
vm._directInactive = false; // 不活跃 禁用的组件标志
vm._isMounted = false; //标志是否 触发过 钩子Mounted
vm._isDestroyed = false; //是否已经销毁的组件标志
vm._isBeingDestroyed = false; //是否已经销毁的组件标志 如果为true 则不触发 beforeDestroy 钩子函数 和destroyed 钩子函数
}
2.initEvents(vm)
初始化事件
目录:src/core/instance/events.js
function initEvents(vm) {
vm._events = Object.create(null);
vm._hasHookEvent = false;
// init parent attached events 初始化 父亲事件
var listeners = vm.$options._parentListeners;
if (listeners) {
//更新组件事件
updateComponentListeners(vm, listeners);
}
}
3.initRender(vm)
初始化渲染
目录:src/core/instance/render.js
function initRender(vm) {
//vm 是Vue 对象
vm._vnode = null; // the root of the child tree 上一个 vonde
vm._staticTrees = null; // v-once cached trees v-once缓存的树
var options = vm.$options; //获取参数
var parentVnode = vm.$vnode = options._parentVnode; // the placeholder node in parent tree 父树中的占位符节点
var renderContext = parentVnode && parentVnode.context; // this 上下文
//判断children 有没有分发式插槽 并且过滤掉空的插槽,并且收集插槽
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
//将createElement fn绑定到这个实例
//这样我们就得到了合适的渲染上下文。
// args order: tag, data, children, normalizationType, alwaysNormalize
//内部版本由模板编译的呈现函数使用
//创建虚拟dom的数据结构
vm._c = function (a, b, c, d) {
console.log(a)
console.log(b)
console.log(c)
console.log(d)
return createElement(
vm, //vm new Vue 实例化的对象
a, //有可能是vonde或者指令
b,
c,
d,
false
);
};
// normalization is always applied for the public version, used in
//的公共版本总是应用规范化
// user-written render functions.
//用户编写的渲染功能。
vm.$createElement = function (a, b, c, d) {
return 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
// $attrs和$listener将被公开,以便更容易地进行临时创建。
//它们需要是反应性的,以便使用它们的HOCs总是更新的
var parentData = parentVnode && parentVnode.data; //获取父vnode
/* istanbul ignore else */
{
// 通过defineProperty的set方法去通知notify()订阅者subscribers有新的值修改
defineReactive(
vm,
'$attrs',
parentData && parentData.attrs || emptyObject,
function () {
!isUpdatingChildComponent && warn("$attrs is readonly.", vm);
},
true
);
// 通过defineProperty的set方法去通知notify()订阅者subscribers有新的值修改
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, function () {
!isUpdatingChildComponent && warn("$listeners is readonly.", vm);
}, true);
}
}
4. initInjections(vm)
resolve injections before data/props 在数据/道具之前解决注入问题 //初始化 inject
目录:src/core/instance/inject.js
function initInjections(vm) {
//provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
//这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
//更多详情信息https://cn.vuejs.org/v2/api/#provide-inject
var result = resolveInject(vm.$options.inject, vm);
if (result) {
toggleObserving(false);
Object.keys(result).forEach(function (key) { //注入的值不能修改,相当于props属性一样
/* istanbul ignore else */
{
// 通过defineProperty的set方法去通知notify()订阅者subscribers有新的值修改
// * 添加观察者 get set方法
defineReactive(
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);
}
}
5. initState(vm)
初始化状态
目录:src/core/instance/state.js
//初始化状态
function initState(vm) {
vm._watchers = []; //初始化观察者队列
var opts = vm.$options; //初始化参数
//判断是否有props属性,如果有则添加观察者
if (opts.props) {
//初始化props 检验props 数据格式是否是规范的如果是规范的则添加到观察者队列中
initProps(vm, opts.props);
}
if (opts.methods) { //事件
// 初始化事件Methods 把事件 冒泡到 vm[key] 虚拟dom 最外层中
initMethods(vm, opts.methods);
}
if (opts.data) { //初始化数据
// 初始化数据 获取options.data 的数据 将他们添加到 监听者中
console.log(vm)
initData(vm);
console.log(vm)
} else {
console.log('vm._data')
console.log(vm._data)
// 判断value 是否有__ob__ 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性,把vm._data添加到观察者中 返回 new Observer 实例化的对象
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { //计算属性
//初始化计算属性 并且判断属性的key 是否 在 data ,将 计算属性的key 添加入监听者中
initComputed(vm, opts.computed);
}
//options 中的 watch
if (opts.watch && opts.watch !== nativeWatch) {
//初始化Watch
initWatch(vm, opts.watch);
}
}
6. initProvide(vm)
resolve provide after data/props 解决后提供数据/道具 provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性,用于组件之间通信。
目录: src/core/instance/inject.js
/*
provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性,用于组件之间通信。
* */
function initProvide(vm) {
var provide = vm.$options.provide; //provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。
if (provide) { //判断provide 存在么
vm._provided = typeof provide === 'function' //判断是否是函数如果是函数则执行
? provide.call(vm)
: provide;
}
}