这一篇主要是介绍data里面的数据是如何变成了浏览器里面的dom元素?
依旧是从vue的构造函数入手到init方法里。
只留下了本篇文章介绍的render这条线的代码,别的都舍弃了!
渲染这条线,需要从vm.$mount()入手!
function initMixin (Vue) {
Vue.prototype._init = function (options) {
var vm = this;
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}
$mount方法
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && query(el);
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
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
// options.render方法没有,就去找template
if (!options.render) {
var template = options.template;
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template);
/* istanbul ignore if */
if (!template) {
warn(
("Template element not found or is empty: " + (options.template)),
this
);
}
}
} else if (template.nodeType) {
template = template.innerHTML;
} else {
{
warn('invalid template option:' + template, this);
}
return this
}
} else if (el) {
template = getOuterHTML(el);
}
// 如果template有数据的话
if (template) {
/* istanbul ignore if */
if (config.performance && mark) {
mark('compile');
}
// 创建一个render方法
var ref = compileToFunctions(template, {
outputSourceRange: "development" !== '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 (config.performance && mark) {
mark('compile end');
measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
}
}
}
// 调用mount方法
return mount.call(this, el, hydrating)
};
接下来看一下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;
{
/* 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
);
}
}
}
callHook(vm, 'beforeMount');
var updateComponent;
/* istanbul ignore if */
if (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方法的定义,没有调用
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
}
// 在new watcher的时候,this.get()方法,就会触发。。。。vm._update()
new Watcher(vm, updateComponent, noop, {
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
}
在mountComponent方法里,有用的是,new Watcher(),说明在第一次对这个组件进行渲染的时候,就对应生成了一个渲染watcher
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this);
// options
if (options) {
this.deep = !!options.deep;
this.user = !!options.user;
this.lazy = !!options.lazy;
this.sync = !!options.sync;
this.before = options.before;
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
this.cb = cb;
this.id = ++uid$2; // uid for batching
this.active = true;
this.dirty = this.lazy; // for lazy watchers
this.deps = [];
this.newDeps = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = expOrFn.toString();
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = noop;
warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
);
}
}
this.value = this.lazy
? undefined
: this.get();
};
在这个watcher里面,this.getter = expOrFn;这里实际上getter对应的是updateComponent方法
然后,最后还调用了get()方法
Watcher.prototype.get = function get () {
// pushTarget 是给 Dep类上添加target属性,Dep.target 代表的是当前的watcher
pushTarget(this);
var value;
var vm = this.vm;
try {
// this.getter 其实对应的就是updateComponent方法,vm._update()方法
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
核心是 value = this.getter.call(vm, vm);
即调用了updateComponent方法
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
接下来调用了vm._render()方法,调用render方法生成了vnode对象
Vue.prototype._render = function () {
var vm = this;
var ref = vm.$options;
// 这里其实已经通过complileToFunctions创建好了render方法
var render = ref.render;
var _parentVnode = ref._parentVnode;
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
);
}
vm.$vnode = _parentVnode;
// render self
var vnode;
try {
currentRenderingInstance = vm;
// 这里的render实际上就是我们拿着template生成的render方法,调用render生成vnode
vnode = render.call(vm._renderProxy, vm.$createElement);
} catch (e) {
handleError(e, vm, "render");
/* istanbul ignore else */
if (vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
} catch (e) {
handleError(e, vm, "renderError");
vnode = vm._vnode;
}
} else {
vnode = vm._vnode;
}
} finally {
currentRenderingInstance = null;
}
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0];
}
if (!(vnode instanceof VNode)) {
if (Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
);
}
vnode = createEmptyVNode();
}
// set parent
vnode.parent = _parentVnode;
return vnode
};
}
执行完render,就会执行update方法vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
function lifecycleMixin (Vue) {
// update和render是两个步骤,先是render生成了vnode
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode;
var restoreActiveInstance = setActiveInstance(vm);
vm._vnode = vnode;
if (!prevVnode) {
// patch1----
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode);
}
restoreActiveInstance();
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null;
}
if (vm.$el) {
vm.$el.__vue__ = vm;
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el;
}
};
这里先介绍到patch之前,上面的方法应该是从patch当做入口进去。