Vue 2.0源码分析-实例挂载的实现

news/2024/7/9 23:47:52 标签: 前端开发, JavaScript, Vue

Vue 中我们是通过 $mount 实例方法去挂载 vm 的,$mount 方法在多个文件中都有定义,如 src/platform/web/entry-runtime-with-compiler.js、src/platform/web/runtime/index.js、src/platform/weex/runtime/index.js。因为 $mount 这个方法的实现是和平台、构建方式都相关的。接下来我们重点分析带 compiler 版本的 $mount 实现,因为抛开 webpack 的 vue-loader,我们在纯前端浏览器环境分析 Vue 的工作原理,有助于我们对原理理解的深入。

compiler 版本的 $mount 实现非常有意思,先来看一下 src/platform/web/entry-runtime-with-compiler.js 文件中定义:

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
    el?: string | Element,
    hydrating?: boolean
): Component {
    el = el && query(el)

    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
    }
    const options = this.$options
    if (!options.render) {
        let template = options.template
        if (template) {
            if (typeof template === 'string') {
                if (template.charAt(0) === '#') {
                    template = idToTemplate(template)
                    if (process.env.NODE_ENV !== 'production' && !template) {
                        warn(
                            `Template element not found or is empty: ${options.template}`,
                            this
                        )
                    }
                }
            } else if (template.nodeType) {
                template = template.innerHTML
            } else {
                if (process.env.NODE_ENV !== 'production') {
                    warn('invalid template option:' + template, this)
                }
                return this
            }
        } else if (el) {
            template = getOuterHTML(el)
        }
        if (template) {

            if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
                mark('compile')
            }

            const { render, staticRenderFns } = compileToFunctions(template, {
                shouldDecodeNewlines,
                shouldDecodeNewlinesForHref,
                delimiters: options.delimiters,
                comments: options.comments
            }, this)
            options.render = render
            options.staticRenderFns = staticRenderFns

            if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
                mark('compile end')
                measure(`vue ${this._name} compile`, 'compile', 'compile end')
            }
        }
    }
    return mount.call(this, el, hydrating)
}

这段代码首先缓存了原型上的 $mount 方法,再重新定义该方法,我们先来分析这段代码。首先,它对 el 做了限制,Vue 不能挂载在 body、html 这样的根节点上。接下来的是很关键的逻辑 , 如果没有定义 render 方法,则会把 el 或者 template 字符串转换成 render 方法。这里我们要牢记,在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 render 方法,无论我们是用单文件 .vue 方式开发组件,还是写了 el 或者 template 属性,最终都会转换成 render 方法,那么这个过程是 Vue 的一个“在线编译”的过程,它是调用 compileToFunctions 方法实现的,编译过程我们之后会介绍。最后,调用原先原型上的 $mount 方法挂载。

原先原型上的 $mount 方法在 src/platform/web/runtime/index.js 中定义,之所以这么设计完全是为了复用,因为它是可以被 runtime only 版本的 Vue 直接使用的。

Vue.prototype.$mount = function (
	el?: string | Element,
	hydrating?: boolean
): Component {
	el = el && inBrowser ? query(el) : undefined
	return mountComponent(this, el, hydrating)
}

$mount 方法支持传入 2 个参数,第一个是 el,它表示挂载的元素,可以是字符串,也可以是 DOM 对象,如果是字符串在浏览器环境下会调用 query 方法转换成 DOM 对象的。第二个参数是和服务端渲染相关,在浏览器环境下我们不需要传第二个参数。

$mount 方法实际上会去调用 mountComponent 方法,这个方法定义在 src/core/instance/lifecycle.js 文件中:

export function mountComponent(vm: Component,el: ?Element,hydrating?: boolean): Component {
    vm.$el = el
    if (!vm.$options.render) {
        vm.$options.render = createEmptyVNode
        if (process.env.NODE_ENV !== 'production') {


            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')

    let updateComponent


    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)
        }
    }


    new Watcher(vm, updateComponent, noop, {
        before() {
            if (vm._isMounted) {
                callHook(vm, 'beforeUpdate')
            }
        }
    }, true )

    hydrating = false


    if (vm.$vnode == null) {
        vm._isMounted = true
        callHook(vm, 'mounted')
    }
    return vm
}

从上面的代码可以看到,mountComponent 核心就是先实例化一个渲染Watcher,在它的回调函数中会调用 updateComponent 方法,在此方法中调用 vm._render 方法先生成虚拟 Node,最终调用 vm._update 更新 DOM。

Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数,这块儿我们会在之后的章节中介绍。

函数最后判断为根节点的时候设置 vm._isMounted 为 true, 表示这个实例已经挂载了,同时执行 mounted 钩子函数。 这里注意 vm.$vnode 表示 Vue 实例的父虚拟 Node,所以它为 Null 则表示当前是根 Vue 的实例。

总结

mountComponent 方法的逻辑也是非常清晰的,它会完成整个渲染工作,接下来我们要重点分析其中的细节,也就是最核心的 2 个方法:vm._render 和 vm._update。


http://www.niftyadmin.cn/n/5208646.html

相关文章

Go iota简介

当声明枚举类型或定义一组相关常量时&#xff0c;Go语言中的iota关键字可以帮助我们简化代码并自动生成递增的值。本文档将详细介绍iota的用法和行为。 iota关键字 iota是Go语言中的一个预定义标识符&#xff0c;它用于创建自增的无类型整数常量。iota的行为类似于一个计数器…

结构体指针变量

结构体指针变量&#xff1a;在使用结构体指针变量之前&#xff0c;首先需要定义结构体指针&#xff0c;结构体指针的定义方式与一般指针类似。 当程序定义了一个指向结构体变量的指针后&#xff0c;就可以通过“指针名->成员变量名”的方式来访问结构体变量中的成员。程序示…

Python进行threading多线程编程及高级并发处理机制

threading 模块是 Python 中用于进行多线程编程的标准库之一。通过 threading 模块&#xff0c;你可以创建和管理线程&#xff0c;使得程序能够并发执行多个任务。以下是一些基本的 threading 模块的用法&#xff1a; 1. 创建线程&#xff1a; 使用 threading.Thread 类可以创…

Node.js入门指南(二)

目录 http模块 创建http服务端 浏览器查看 HTTP 报文 获取 HTTP 请求报文 设置响应报文 网页资源的基本加载过程 静态资源服务 hello,大家好&#xff01;上一篇文章我们对Node.js进行了初步的了解&#xff0c;并介绍了Node.js的Buffer、fs模块以及path模块。这一篇文章主…

Vue表单的整体处理

在前端的处理中&#xff0c;表单的处理永远是占高比例的。在BOMDOMjs的时候是这样&#xff0c;在Vue的时候也是这样。Vue的表单处理做了特别的优化&#xff0c;如值绑定、数据验证、错误提示、修饰符等。 表单组件的示例&#xff1a; <script setup lang"ts">…

jupyter notebook 不知道密码,怎么登录解决办法

jupyter notebook 不知道密码&#xff0c;怎么登录解决办法 1、 windows下&#xff0c;打开命令行&#xff0c;输入jupyter notebook list &#xff1a; C:\Users\tom>jupyter notebook list Currently running servers: http://localhost:8888/?tokenee8bb2c28a89c8a24d…

【lua】记录函数名和参数(为了延后执行)

需求背景 一个服务缓存玩家信息到对象里&#xff0c;通过对象的函数定时同步到数据库中&#xff0c;如果玩家掉线 清空对象&#xff0c;但是后续步骤导致对象数据需要变更&#xff0c;对象不存在&#xff0c; 就不方便变更了&#xff0c;怎么处理&#xff1f; 方案思考 1.临…

Python 异步套接字编程

异步套接字编程是异步编程在网络通信中的应用&#xff0c;它使用异步 IO 操作和事件循环来实现高并发的网络应用。Python 中的 asyncio 模块提供了对异步套接字编程的支持&#xff0c;以下是异步套接字编程的一些重要概念和使用方法&#xff1a; 1. 异步套接字服务器&#xff…