vm.$mount([elementOrSelector])
用法:如果vus.js实例在实例化时没有收到el选项,则它处于“未挂载”状态,没有关联的DOM元素。我们可以使用vm.$mount手动挂载一个未挂载的实例。如果没有提供elementOrSelector参数,模板将被渲染为文档之外的元素,并且必须使用原生DOM的API把它插入文档中。这个方法返回实例自身,因而可以链式调用其他实例方法。
注:在使用vue-cli搭建vue项目的时候,其已经帮我们挂载好了,可能很少会接触到这个方法。但我们仍需要了解其挂载过程中干了什么,知道其是怎样实现视图渲染与数据双向绑定的。
完整版和运行时版
完整版:构建后的文件同事包含编译器和运行时。
编译器:负责将模板字符串编译成javascript渲染函数。
运行时:负责创建Vue.js实例,渲染视图和使用虚拟DOM实现重新渲染,基本上包含除编译器外的所有部分。
在完整版的构建版本中,vm.$mount会先检查template或el选项所提供的模板是否已经转换成渲染函数(render)。如果没有,即立刻进入编译过程,将模板编译成渲染函数,完成之后再进入挂载和渲染流程中。而只包含运行时版本的vm.$mount则没有编译的过程,默认已经存在渲染函数,如果没有,则会设置一个,并在其执行后返回一个空节点的VNode,保证执行不会因为函数不存在而报错。
其主要核心实现就是运行时的vm.$mount的实现。
完整版vm.$mount的实现原理
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el) {
el = el && query(el)
return mount.call(this, el)
}
function query(el) {
if (typeof el === 'string') {
const selected = document.querySelector(el)
if (!selected) {
return document.createElement('div')
}
return selected
} else {
return el
}
}
解析:我们现在Vue原型上的$mount方法保存在一个变量中,然后用一个新的方法将其原方法覆盖掉。新方法中再调用原来的方法,这种做法通常叫函数劫持。
通过函数劫持,可以在原始功能上新增了一些其他功能,在完整版本中,会先对el进行判断,调用query方法来获取其DOM元素。
------获取到dom之后我们还需要其一步步进行模板解析
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el) {
el = el && query(el)
// 模板解析开始------
const options = this.$options
if (!options.render) {
// 获取模板
let template = options.template
if (template) {
// 模板解析相关逻辑
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
}
} else if (template.nodeType) {
template = template.innerHtml
} else if {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
} else (el) {
template = getOuterHTML(el)
}
}
}
/// 模板解析结束----
return mount.call(this, el)
}
function getOuterHTML(el) {
if (el.getOuterHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
function idToTemplate(id) {
const el = query(id)
return el && el.innerHTML
}
解析:1.首先this.$option是实例化Vue.js的一个初始化流程,它可以访问到实例化中设置的一些参数,例如template和render。
2.接着判断实例化Vue.js是否有render渲染函数,如果有则跳过解析template,没有的话则执行相关解析流程。
3.解析template前先获取template是否存在,如果不存在,则从el选项中获取HTML字符串当作模板。如果提供了template,则对其进行进一步解析。
4.如果template是字符串,但不是以#开头,说明tempalte是用户设置的模板,不需要进行处理,直接使用。
5.如果template不是字符串,则判断它是否是一个DOM元素,如果是,则使用DOM元素的innerHTML作为模板,如果不是,只需要判断它是否具备nodeType属性即可。
6.如果template即不是字符串也不是DOM元素,那么Vue.js会触发警告,提示用户template选项失效。
------当获取完模板之后,下一步就是将模板编译成渲染函数了!
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el) {
el = el && query(el)
// 模板解析开始------
......
/// 模板解析结束----
// 编译成渲染函数
if (template) {
const { render } = compileToFunctions(
template, {...}, this
)
options.render = render
}
return mount.call(this, el)
}
function compileToFunctions(template, options, vm) {
options = extend({}, options)
// 检查缓存
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
// 编译
const compiled = compiled(template, options)
// 将代码字符串转为函数
const res = {}
res.render = cretaFunction(compiled.render)
return (cache[key] = res)
}
function createFunction(code) {
return new Function(code)
}
解析:其主要通过compileToFunctions函数将模板编译成渲染函数再设置到this.$options上
1.将option属性混合到空对象中,目的就是让options函数成为可选参数。
2.检查缓存中是否已经存在编译后的模板,反正重复编译,保证不做无用功提示性能。
3.进行编译,这部分比较长,不详说,可以自己去看编译原理。
4.最后通过createFunction转为字符串,然后使用newFunction将diamagnetic字符串转为函数。
运行时vm.$mount实现原理
Vue.prototype.$mount = function (el) {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el)
}
export function mountComponent(vm, el) {
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
// 警告
}
}
// 触发生命周期钩子
callHook(vm, 'beforeMount')
// 挂载
vm._watcher = new Watcher(vm, () => {
vm._update(vm.render())
}, noop)
// 触发生命周期钩子
callHook(vm, 'mounted')
return vm
}
解析:其主要将ID转为DOM后,调用mountComponent函数将Vue.js实例挂载到DOM元素上。
将实例挂载到DOM元素上指的是将模板渲染指定的DOM元素中,而且是持续性的,以后当数据(状态)发生变化时,依然可以渲染到指定DOM元素中。
其实现就是开启了watcher,在挂载钱会先触发beforeMount生命钩子,之后才开启真正的挂载(其实就是添加watcher)。
挂载和渲染雷士,不同的是渲染指定是渲染一次,而挂载指的是持续性渲染。
_updata和_render:前者作用是调用虚拟DOM中的patch方法来执行节点的比对与渲染操作,而后者的作用是执行渲染函数,得到一份最新的VNode节点树。
该代码就是先得到一份最新的VNode节点树,然后通过_update方法对比更新DOM节点,然后进行渲染操作。
前面在侦测变化的时候已经说过watcher。
在watcher的第二参数是函数的时候,函数中读取的所有数据都将被watcher观察。这些数据中任何一个发生变化,watcher都将得到通知。