VUE 其实就是一个 函数式的类
function Vue (options) { //new vue的时候 把options传入_init方法 _init方法在initMixin函数中 被挂载到了Vue的原型中
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);
}
定义之后 就会有一系列的初始化的函数 进行init 上面的_init 就是初始化函数 把_init方法定义到了vue 的原型上
初始化函数如下
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);
首先看initMixin 我们跳到initMixin 看到这里可以看到 往Vue的原型上定义了一个_init方法 这个_init方法在new Vue的时候就会被调用
function initMixin (Vue) {
Vue.prototype._init = function (options) {
var vm = this;
// a uid
vm._uid = uid$3++; //给这个实例一个uid属性 每new一次 就++
var 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); //设置render
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); //将options中的 el 传入$mount中 执行完这个$mount函数之后 页面的插值表达式就会被渲染出来
}
};
}
先来看这个 initState 函数 这个函数的样子如下
function initState (vm) {
vm._watchers = []; //在vue的构造函数中设置_watchers
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }
if (opts.data) { //初始化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);
}
}
先来看这个 initData 做了哪些事 从字面的意思来讲就是 初始化我们的data
function initData (vm) { //初始化data
var data = vm.$options.data;
console.log("vm.$options.data",vm.$options.data) //这里data是一个函数 返回一个对象
data = vm._data = typeof data === 'function' //判断data是不是函数
? getData(data, vm)
: data || {};
if (!isPlainObject(data)) {
data = {};
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
);
}
// proxy data on instance
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];
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
("Method \"" + key + "\" has already been defined as a data property."),
vm
);
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && 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); //代理 原函数在4648行 将每一个key 代理到_data中
console.log(vm)
}
}
// observe data
observe(data, true /* asRootData */);
}
上面这个函数中 首先判断了 data是不是函数 如果是函数就调用getData函数 把data函数的返回值赋值给 vm.$options.data 和vm._data
重点看最后的这个 proxy(vm, "_data", key); 这个函数的意思是 把data的每一个key 代理到 vm._data中 代理函数如下
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
//意思就是把 vm.key和 vm._data.key进行了一个关联 当你改变vm.key的值的时候 vm._data.key也会跟着改变 你访问例如this.message 实际上访问的是this._data.message 至此 初始化data完毕
我们再回到最初的 initMixin函数中 在最后 有一个 vm.options.el) 在这里 会把组件转为DOM对象
看上去就是把我们定义的el传入进了 $mount方法
而实际上 $mount方法会被先缓存 然后重写 我们刚开始调用的是这个重写后的方法
var mount = Vue.prototype.$mount; 这里是缓存的mount
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && query(el);
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) { //$options.el 不允许是html 或者是body
process.env.NODE_ENV !== 'production' && warn(
"Do not mount Vue to or - mount to normal elements instead."
);
return this
}
var options = this.$options;
// resolve template/el and convert to render function
if (!options.render) { //如果没有定义render
var template = options.template;
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') { //如果template:"#first" 类似这种情况
template = idToTemplate(template); //找到first 返回first的所有innerHTML
/* 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) {
console.log("没有定义template")
template = getOuterHTML(el);
console.log(template)
}
if (template) {
console.log("如果定义了template")
/* istanbul ignore if */ //以下是编译相关 编译为render函数
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile');
}
var ref = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
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) //var mount = Vue.prototype.$mount;
};
可以看出 $mount首先会判断有没有定义 render 如果没有定义 render 但是定义了template 如果这个template是以#开头 就返回这个元素的innerHTML字符串 ,如果没有定义template 就找el的outerHTML
总之不管开始template有没有 最后都会生成template 函数的最后是如果template是有的 就把template编译为rander函数 最后再调用之前缓存起来的mount方法 之前缓存的mount方法是
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
这里的重点是mountComponent函数
function mountComponent (
vm,
el,
hydrating
) {
vm.$el = el;
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode; //创造一个vnode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
);
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
);
}
}
}
// debugger
callHook(vm, 'beforeMount');
var updateComponent;
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) { //性能埋点
updateComponent = function () {
var name = vm._name;
var id = vm._uid;
var startTag = "vue-perf-start:" + id;
var endTag = "vue-perf-end:" + id;
mark(startTag);
var 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 = function () { //updateComponent方法 只要数据一变就会执行
vm._update(vm._render(), hydrating); //把数据挂载到dom的函数 render()方法会生成一个VNODE
};
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, { //渲染 watcher
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
hydrating = false;
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm
}
这里面的重点是updateComponent函数 只要数据一变就会执行 render()函数会生成一个vnode 然后_updata会把数据和vnode关联 渲染到页面上