vue源码解析之实例挂载

11 篇文章 0 订阅
10 篇文章 0 订阅

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都将得到通知。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值