vue document获取不到id_每天学点Vue源码: 关于vm.$mount挂载函数

98e0c53329b10807ee496d68865df283.png

$mount 函数执行位置

01431bd44119617ee25291a37a0aaaf8.png

_init 这个私有方法是在执行 initMixin 时候绑定到 Vue 原型上的。

06c689c358ae8126d6b0a42ee441d513.png

$mount 函数是如如何把组件挂在到指定元素

$mount 函数定义位置

$mount 函数定义位置有两个:

第一个是在 src/platforms/web/runtime/index.js

4c2a2661fdc16a0063f15a261ba07f41.png

这里的$mount 是一个 public mount method。之所以这么说是因为 Vue 有很多构建版本, 有些版本会依赖此方法进行有些功能定制, 后续会解释。

// public mount method
// el: 可以是一个字符串或者Dom元素
// hydrating 是Virtual DOM 的补丁算法参数
Vue.prototype.$mount = function(
    el?: string | Element,
    hydrating?: boolean
): Component {
    // 判断el, 以及宿主环境, 然后通过工具函数query重写el。
    el = el && inBrowser ? query(el) : undefined
    // 执行真正的挂载并返回
    return mountComponent(this, el, hydrating)
}

src/platforms/web/runtime/index.js 文件是运行时版 Vue 的入口文件,所以这个方法是运行时版本 Vue 执行的$mount。

关于 Vue 不同构建版本可以看Vue 对不同构建版本的解释。

关于这个作者封装的工具函数 query 也可以学习下:

/**
 * Query an element selector if it's not an element already.
 */
export function query(el: string | Element): Element {
    if (typeof el === "string") {
        const selected = document.querySelector(el)
        if (!selected) {
            // 开发环境下给出错误提示
            process.env.NODE_ENV !== "production" &&
                warn("Cannot find element: " + el)
            // 没有找到的情况下容错处理
            return document.createElement("div")
        }
        return selected
    } else {
        return el
    }
}

第二个定义 $mount 函数的地方是 src/platforms/web/entry-runtime-with-compiler.js 文件,这个文件是完整版 Vue(运行时+编译器)的入口文件。

关于运行时与编译器不清楚的童鞋可以看官网运行时 + 编译器 vs. 只包含运行时。

// 缓存运行时候定义的公共$mount方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function(
    el?: string | Element,
    hydrating?: boolean
): Component {
    // 通过query方法重写el(挂载点: 组件挂载的占位符)
    el = el && query(el)

    /* istanbul ignore if */
    // 提示不能把body/html作为挂载点, 开发环境下给出错误提示
    // 因为挂载点是会被组件模板自身替换点, 显然body/html不能被替换
    if (el === document.body || el === document.documentElement) {
        process.env.NODE_ENV !== "production" &&
            warn(
                `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
            )
        return this
    }
    // $options是在new Vue(options)时候_init方法内执行.
    // $options可以访问到options的所有属性如data, filter, components, directives等
    const options = this.$options
    // resolve template/el and convert to render function

    // 如果包含render函数则执行跳出,直接执行运行时版本的$mount方法
    if (!options.render) {
        // 没有render函数时候优先考虑template属性
        let template = options.template
        if (template) {
            // template存在且template的类型是字符串
            if (typeof template === "string") {
                if (template.charAt(0) === "#") {
                    // template是ID
                    template = idToTemplate(template)
                    /* 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 的类型是元素节点,则使用该元素的 innerHTML 作为模板
                template = template.innerHTML
            } else {
                // 若 template既不是字符串又不是元素节点,那么在开发环境会提示开发者传递的 template 选项无效
                if (process.env.NODE_ENV !== "production") {
                    warn("invalid template option:" + template, this)
                }
                return this
            }
        } else if (el) {
            // 如果template选项不存在,那么使用el元素的outerHTML 作为模板内容
            template = getOuterHTML(el)
        }
        // template: 存储着最终用来生成渲染函数的字符串
        if (template) {
            /* istanbul ignore if */
            if (
                process.env.NODE_ENV !== "production" &&
                config.performance &&
                mark
            ) {
                mark("compile")
            }
            // 获取转换后的render函数与staticRenderFns,并挂在$options上
            const { render, staticRenderFns } = compileToFunctions(
                template,
                {
                    outputSourceRange: process.env.NODE_ENV !== "production",
                    shouldDecodeNewlines,
                    shouldDecodeNewlinesForHref,
                    delimiters: options.delimiters,
                    comments: options.comments
                },
                this
            )
            options.render = render
            options.staticRenderFns = staticRenderFns

            /* istanbul ignore if */
            // 用来统计编译器性能, config是全局配置对象
            if (
                process.env.NODE_ENV !== "production" &&
                config.performance &&
                mark
            ) {
                mark("compile end")
                measure(`vue ${this._name} compile`, "compile", "compile end")
            }
        }
    }
    // 调用之前说的公共mount方法
    // 重写$mount方法是为了添加模板编译的功能
    return mount.call(this, el, hydrating)
}

关于 idToTemplate 方法: 通过 query 获取该 ID 获取 DOM 并把该元素的 innerHTML 作为模板

const idToTemplate = cached(id => {
    const el = query(id)
    return el && el.innerHTML
})

getOuterHTML 方法:

/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */
function getOuterHTML(el: Element): string {
    if (el.outerHTML) {
        return el.outerHTML
    } else {
        // fix IE9-11 中 SVG 标签元素是没有 innerHTML 和 outerHTML 这两个属性
        const container = document.createElement("div")
        container.appendChild(el.cloneNode(true))
        return container.innerHTML
    }
}

关于 compileToFunctions 函数, 在 src/platforms/web/entry-runtime-with-compiler.js 中可以看到会挂载到 Vue 上作为一个全局方法。

63bd3e01bdc9d5e256703754701fd706.png

mountComponent 方法: 真正执行绑定组件

mountComponent 函数中是出现在 src/core/instance/lifecycle.js

export function mountComponent(
    vm: Component, // 组件实例vm
    el: ?Element, // 挂载点
    hydrating?: boolean
): Component {
    // 在组件实例对象上添加$el属性
    // $el的值是组件模板根元素的引用
    vm.$el = el
    if (!vm.$options.render) {
        // 渲染函数不存在, 这时将会创建一个空的vnode对象
        vm.$options.render = createEmptyVNode
        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
                )
            }
        }
    }
    // 触发 beforeMount 生命周期钩子
    callHook(vm, "beforeMount")

    // vm._render 函数的作用是调用 vm.$options.render 函数并返回生成的虚拟节点(vnode)。template => render => vnode

    // vm._update 函数的作用是把 vm._render 函数生成的虚拟节点渲染成真正的 DOM。 vnode => real dom node

    let updateComponent // 把渲染函数生成的虚拟DOM渲染成真正的DOM
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== "production" && config.performance && mark) {
        updateComponent = () => {
            const name = vm._name
            const id = vm._uid
            const startTag = `vue-perf-start:${id}`
            const endTag = `vue-perf-end:${id}`

            mark(startTag)
            const 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 = () => {
            vm._update(vm._render(), hydrating)
        }
    }

    // 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
    // 创建一个Render函数的观察者, 关于watcher后续再讲述.
    new Watcher(
        vm,
        updateComponent,
        noop,
        {
            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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值