vue 实例 初始化 完成以后,接下来就要进行 挂载。
vue 实例挂载,即为将 vue 实例对应的 template 模板,渲染成 Dom 节点。
原型方法 – $mount
通过原型方法 $mount 方法 来挂载 vue 实例。
挂载 vue 实例时,经历一下几个重要步骤:
生成 render 函数;
生成 vue 实例的监听器 watcher;
执行 render 函数,将 vue 实例的 template 模板转化为 VNode 节点树;
执行 update 函数,将 VNode 节点树转化为 dom 节点树;
// 挂载 Vue 实例
Vue$3.prototype.$mount = function(el, hydrating) {
// el 为 dom 元素对应的选择器表达式,根据选择器表达式,获取 dom 元素
el = el && query(el);
…
// this->Vue 实例,或者是组件实例对象
var options = this.$options;
// 解析模板,将模板转换为 render 渲染函数
if(!options.render) {
// 一般是组件实例的构造函数的 options 中会直接有 template 这个属性
var template = options.template;
if(template) {
if(typeof template === ‘string’) {
if(template.charAt(0) === ‘#’) {
template = idToTemplate(template);
/* istanbul ignore if */
if(“development” !== ‘production’ && !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) {
// 获取 dom 节点的 outerHTML
template = getOuterHTML(el);
}
// template,模板字符串
if(template) {
/* istanbul ignore if */
if(“development” !== ‘production’ && config.performance && mark) {
mark(‘compile’);
}
// 将 html 模板字符串编译为渲染函数
var ref = compileToFunctions(template, {
// 换行符
shouldDecodeNewlines : shouldDecodeNewlines,
// 分割符
delimiters : options.delimiters,
// 注释
comments : options.comments
}, this);
// 获取渲染函数
var render = ref.render;
// 获取静态渲染函数
var staticRenderFns = ref.staticRenderFns;
// 将渲染函数添加到 Vue 实例对象的配置项 options 中
options.render = render;
// 将静态渲染函数添加到 Vue 实例对象的配置项 options 中
options.staticRenderFns = staticRenderFns;
/* istanbul ignore if */
if(“development” !== ‘production’ && config.performance && mark) {
mark(‘compile end’);
measure(((this._name) + ” compile”), ‘compile’, ‘compile end’);
}
}
}
return mountComponent.call(this, el, hydrating)
};
// 挂载 vue 实例
function mountComponent(vm, el, hydrating) {
vm.$el = el;
…
updateComponent = function() {
vm._update(vm._render(), hydrating);
};
// 给 Vue 实例或者是组件实例创建一个监听器,监听 updateComponent 方法
vm._watcher = new Watcher(vm, updateComponent, noop);
…
return vm;
}
watcher 对象在构建过程中,会作为观察者模式中的 Observer,会被添加到 响应式属性的 dep 对象的依赖列表 中。
当响应式属性发生变化时,会通知依赖列表中的 watcher 对象进行更新。
此时,watcher 对象执行 updateComponent 方法,重新渲染 dom 节点。
watcher
在 挂载 vue 实例 时,会为 vue 实例 构建一个 监听者 watcher。
// vm => 当前监听者对应的 vue 实例
// expOfFn => 需要监听的表达式
// cb => 监听回调方法
// options => 选项,比如 deep、immediate
// vm.$watch(‘message’, function(newValue) {…})
var Watcher = function Watcher(vm, expOrFn, cb, options) {
this.vm = vm;
vm._watchers.push(this);
…
// 监听器订阅的 Dep 对象实例
this.deps = [];
this.newDeps = [];
// 存储监听器已经订阅的 Dep 对象实例的 id,防止重复订阅。
// 每一个 Dep 对象实例都有一个 ID
this.depIds = new _Set();
this.newDepIds = new _Set();
// 监听器监听的表达式
this.expression = expOrFn.toString();
// 初始化 getter,getter 用于收集依赖关系
if(typeof expOrFn === ‘function’) {
// expOfFn 为 函数
// 对应:给 vue 实例建立 watcher
this.getter = expOrFn;
} else {
// epOfFn 为 字符串
// 对应:在 vue 实例中建立监听
// 比如 vm.$watch(‘message’, function() {…})
this.getter = parsePath(expOrFn);
}
this.value = this.lazy
? undefined
: this.get();
};
// 执行 watcher 的 getter 方法,用于收集响应式属性 dep 对象 和 watcher 的依赖关系
// 在 vue 实例挂载过程中,getter = updateComponent
Watcher.prototype.get = function get() {
// 将 Dep.target 设置为当前 Watcher 对象实例
pushTarget(this);
var value;
var vm = this.vm;
…
value = this.getter.call(vm, vm);
…
return value
};
// watcher 更新
Watcher.prototype.update = function update() {
…
this.get()
…
};
// 将 watcher 添加到 dep 属性的依赖列表中
Watcher.prototype.addDep = function addDep(dep) {
var id = dep.id;
if(!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if(!this.depIds.has(id)) {
dep.addSub(this);
}
}
};
在挂载 vue 实例时,watcher 对象会在构建过程中会执行 updateComponent 方法。
执行 updateComponent 方法分为两个过程:
执行 render 方法,返回 VNode 节点树;
执行 update 方法,将 VNode 节点树 渲染为 dom 节点树。
执行 render 方法时,会触发响应式属性的 getter 方法,将 watcher 添加到 dep 对象的依赖列表中。
render
render 方法是 vue 实例 在挂载时由 template 编译成的一个 渲染函数。
tempalte:
<div id=”app”>
{{message}}
</div>
render:
// 执行 render,需要读取响应式属性 message,触发 message 的 getter 方法
(function anonymous() {
with(this){return _c(‘div’,{attrs:{“id”:”app”}},[_v(_s(message))])}
})
// _s, 将 this.message 转化为字符串
// _v, 生成文本节点对应的 VNode
// _c, 生成 ’div’ 元素节点对应的 Vnode
render 函数返回 VNode 节点,用于 渲染 Dom 节点。
在执行过程中,如果需要读取 响应式属性,则会触发 响应式属性 的 getter。
在 getter 方法中,将 watcher 对象添加到 响应式属性 dep 对象的依赖列表 中。
修改响应式属性
修改 响应式属性时,会触发响应式属性的 setter 方法。此时,响应式属性的 dep 对象执行 notify 方法,遍历自己的 依赖列表 subs,逐个通知 subs 中的 watcher 去更新。
watcher 对象执行 update 方法,重新调用 render 方法生成 Vnode 节点树,然后 对比新旧 Vnode 节点树 的不同,更新 dom 树。
总结
响应式属性 的原理: