1.Vue3 源码中实例挂载(mount)过程
2.mount系统调用(vfs_kern_mount->mount_fs->fill_super)
3.mount 挂载实现原理
4.linux mount 流程详解
5.Vue源码解析(2)-$mount实现
6.每天学点Vue源码之vm.$mount挂载函数
Vue3 源码中实例挂载(mount)过程
上篇文章介绍了如何创建Vue3组件实例,函函数创建实例后,数源需调用mount方法将其挂载到页面上。详解整个组件挂载流程分为开始安装与结束安装两个阶段。函函数
核心函数setupComponent将上述流程集成,数源它包含开始安装与结束安装两部分。详解微信裂变源码开始安装阶段,函函数主要任务是数源初始化props与slots。当组件具有状态时,详解执行setupStatefulComponent,函函数调用setup函数配置组件状态与行为。数源
在Vue3中,详解setup函数负责定义组件的函函数状态与行为。对于状态组件,数源setup函数返回包含state、详解props与context等属性的对象。
setupStatefulComponent函数设置组件实例,调用setCurrentInstance,并在实例回溯前暂停依赖收集,创建Proxy对象,随后恢复依赖收集。此举旨在避免setup函数内产生不必要的依赖收集。
通常,setup函数返回对象,执行handleSetupResult函数验证返回值是否符合规范。
开始安装阶段,先初始化props与slots,随后处理状态组件。结束安装阶段,初始化computed、data、watch、单页网站的源码mixin与生命周期等。
handleSetupResult确保setup返回值有效。applyOptions函数处理配置选项与初始化工作,确保组件初始化阶段具备有效的渲染函数,支持选项API,并在开发环境下提供警告信息。
总结,组件挂载流程分为开始与结束两个阶段,分别处理初始化与配置工作,确保组件在页面上正确显示。
mount系统调用(vfs_kern_mount->mount_fs->fill_super)
回顾上文,我们深入探讨了mount系统调用的流程,从mount系统调用的启动,到do_new_mount步骤的解析,包括mount和vfsmount结构的讲解。接下来,我们将聚焦于do_new_mount()函数的执行,其中包含vfsmount结构的初始化。
do_new_mount()的开启步骤指向了vfsmount结构的填充。vfsmount是整个挂载操作的核心结构,vfsmount填充主要由三个关键步骤组成:vfsmount结构的创建、调用特定文件系统的mount回调函数构建root dentry并包含super block信息、最终完成vfsmount结构的构建。
vfs_kern_mount()函数执行这三个关键步骤,具体来说:
1. 通过alloc_vfsmnt创建一个新的struct mount结构。
2. 在mount_fs函数中调用特定文件系统的mount回调函数,为root dentry构造,并包含特定文件系统的super block信息。
3. 使用上述步骤的结果,完成struct mount的构建并返回vfsmount结构。
这一步骤的卡乐卡卡盟源码焦点转移到了mount_fs()函数,它负责调用文件系统类型的mount回调函数。mount_fs函数执行的主要任务是调用type->mount回调函数,该函数在每个文件系统类型注册到内核时实现,旨在针对特定文件系统特性进行个性化操作。
除了type->mount之外,mount_fs()还包含了安全检查等步骤,但细节将在后续内容中涉及。让我们先深入了解一下mount_fs函数的运作。
接下来,我们以xfs文件系统为例,具体探讨mount回调函数的实现。在fs/xfs目录下能找到xfs文件系统的实现代码,xfs_super.c中包含了init_xfs_fs模块的初始化函数,它调用了register_filesystem(&xfs_fs_type)函数,其中xfs_fs_type是xfs文件系统实现的file_system_type结构。
重点在于分析xfs_fs_mount()函数,它是xfs文件系统实现的mount回调函数。xfs_fs_mount调用mount_bdev()函数,最后传入xfs_fs_fill_super作为参数,这是xfs文件系统特有的fill_super处理函数。
让我们先了解mount_bdev函数的定义,它是针对块设备挂载的通用函数。mount_bdev的实现逻辑主要涉及fill_super函数的调用,这标志着mount操作的关键步骤。接下来,我们将深入探讨xfs_fs_fill_super函数的实现。
xfs_fs_fill_super是与xfs文件系统实现紧密相关的函数,其详细实现涉及到xfs的特定on-disk知识。函数代码庞大,特别是在xfs支持V5以及增加reflink、rmapbt特性后,h5 游戏了源码代码长度显著增加。
xfs_fs_fill_super函数实际上涉及了xfs文件系统在磁盘上的具体知识,包括设备和内存等相关的细节。对于xfs文件系统实现的具体细节,将在后续的分析中进一步探讨,本文将重点阐述mount的基本流程和具体文件系统实现的概览,以保持内容的清晰性和完整性。
完成vfsmount结构的构建后,我们将其添加到全局文件系统树中,完成整个挂载流程。对于更深入的技术细节和实现,请参考醉卧沙场专栏文章总索引。
mount 挂载实现原理
在深入探讨 Vue 实例化背后的机制时,我们关注了挂载实现的核心逻辑。当在实例化 Vue 时传入非空参数 el,Vue 会自动调用内部函数 $mount 进行挂载。否则,会进行手动挂载。本文旨在解析 Vue 源码中实现挂载的详细过程。
挂载过程可简化为以下步骤:查询元素、检查挂载位置、编译模板或使用默认函数、调用 mount 函数。
在函数 mount 的实现中,主要分为四个关键环节:查询 el 元素、确认实例未挂载在 html 或 body、根据是否有 render 函数决定处理方式、最终调用 mount 实现。
一旦完成这些准备,程序将调用定义在 src/platforms/web/runtime/index.js 文件内的 mount 函数,核心步骤是请柬 源码调用 mountComponent(this, el, hydrating)。
在 mountComponent 内部,核心逻辑包括:检查是否有 render 函数、执行 beforeMount 生命周期、定义 updateComponent 函数、实例化 Watcher。
Watcher 的作用在于:在初始化时执行 updateComponent 函数,响应式地在数据变化时重新渲染组件。其构造函数接收五个参数,其中 updateComponent 作为回调执行。解析 expOrFn 参数,处理函数或路径,并最终通过 get 方法调用 updateComponent,生成虚拟 DOM。
最后,完成挂载,将 Vue 实例标记为已挂载,执行 mounted 生命周期方法,至此挂载过程完成。
本文详细解析了 Vue 实例化挂载的核心机制,包括模板编译、虚拟 DOM 渲染与 Watcher 的实现,为理解 Vue 的工作原理提供了深入的洞察。
linux mount 流程详解
Linux的mount操作主要依赖于mount命令或内核mount API,本文将深入剖析其在内核中的具体实现流程。关键数据结构fs_context在mount过程中扮演着至关重要的角色,它作为file_system_type到super_block间的桥梁,控制着整个mount过程。 fs_context包含fs_type结构体,以及指向fs_context_operations的ops,通常在文件系统的init_fs_context回调中设置。接下来,我们通过一步步分析mount的调用流程来了解其细节:入口函数:do_mount
从Linux的系统调用mount开始,主要通过do_mount进行后续操作,它会调用path_mount,设置sb_flags和mnt_flags,然后进一步执行do_new_mount。fs_context分配与super_block申请
在do_new_mount中,首先进行fs_context的分配。随后,get_tree_bdev函数负责申请super_block,涉及以下步骤:动态分配super_block结构体
通过fill_super函数,执行文件系统特定的初始化操作,如解析元数据并填充至私有结构体。
回调函数填充super_block:exfat示例
以exfat为例,fill_super函数分为两部分:读取并解析exfat的文件系统信息,保存在exfat_sb_info中
设置super_block的必要字段,如s_op、s_root和s_d_op
装载到全局文件系统树:do_new_mount_fc
最后,mount操作完成后,do_new_mount_fc会创建新的挂载实例并将其关联到系统中,这个过程涉及到复杂的数据结构,但具体细节仍需进一步探索。Vue源码解析(2)-$mount实现
在上一节中,我们了解到Vue实例的创建过程中,构造函数会执行_init()函数,其中关键步骤是调用vm.$mount(vm.$options.el),这标志着实例已开始挂载到DOM。$mount是Vue渲染的核心函数。
本章节我们将深入探讨Vue的渲染过程,但会跳过一些细节,以便在后续章节中详细剖析。首先,理解Vue的两种构建方式是关键:独立构建(包含template编译器)和运行时构建(不包含模板编译器)。独立构建支持服务端渲染,而运行时构建体积更小。
接下来,我们开始分析Vue源码。$mount方法的实现与平台和构建方式相关,这里我们关注运行时版本。在src/platforms/web/entry-runtime-with-compiler.js中,$mount被添加到Vue原型上,它接收el参数,可能是字符串或DOM元素。
当el为字符串时,会通过query方法将其转换为DOM节点。然后判断el不能为body或html,以防止意外覆盖。如果没有render函数,会根据template生成render,同时处理多模板形式。getOuterHTML函数获取el的内容和DOM。
$mount最终调用mount函数,这个过程涉及核心的mountComponent方法,生成虚拟Node并实例化渲染Watcher,其回调中调用updateComponent更新DOM。这部分在core/instance/lifecycle.js中,会检查render函数并处理特殊情况,如未定义或使用template语法的runtime-only版本。
updateComponent是渲染和更新的核心函数,由Watcher(在'src/core/observer/watch.js'定义)在数据变化时调用。Watcher在初始化时执行回调,当数据更新时也执行。整个过程体现了观察者模式,$mount中调用updateComponent的过程涉及template到render的转换,以及初次渲染或数据变更时的调用。
虽然我们已经概述了$mount的流程,但关于render函数的编译步骤并未深入讲解。编译过程包括添加web平台特性、解析template为AST、优化节点、生成render函数字符串并缓存。下一节将详细剖析这五个步骤的源码实现,敬请期待。
每天学点Vue源码之vm.$mount挂载函数
在vue实例中,通过$mount()实现实例的挂载,下面来分析一下$mount()函数都实现了什么功能。
$mount函数执行位置
_init这个私有方法是在执行initMixin时候绑定到Vue原型上的。
$mount函数是如如何把组件挂在到指定元素
$mount函数定义位置
$mount函数定义位置有两个:
第一个是在src/platforms/web/runtime/index.js
这里的$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.$mountVue.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- 中 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上作为一个全局方法。
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}
mount系统调用(ksys_mount->do_mount->do_new_mount)
回顾前文,我们详细探讨了mount系统调用的基本用法以及每个参数的具体含义。mount调用涉及五个关键参数,分别是dev_name, dirname, type, flags和data。调用过程从内核执行阶段开始,经过mount系统调用的初始处理,参数传递至ksys_mount函数。此函数主要负责拷贝用户层内容至内核层,并进一步转交给do_mount进行后续处理。
在do_mount函数内部,执行逻辑主要围绕进行一次普通的mount操作,即do_new_mount,其他复杂操作留待后续时间进行深入讨论。do_new_mount函数接收的参数相较于初始阶段增加了path结构,同时将flags参数拆分,使得传递给do_new_mount的参数总计六个。
在这个阶段,引入了两个关键概念:vfs_kern_mount和vfsmount结构体。vfs_kern_mount和vfsmount结构的解释为接下来的do_add_mount函数提供了背景知识。vfsmount结构体是mount实例的核心,它在其中定义了mnt成员。过去,mount和vfsmount的成员共存于vfsmount内,而现在,Linux将mount结构体与vfsmount分离,将mnt_root, mnt_sb, mnt_flags等成员移至vfsmount结构体中,使得结构体更加精简。
具体地,struct vfsmount包含三个核心成员,它们通过vfs_kern_mount函数获取。下一篇文章将详细解释通过vfs_kern_mount函数如何得到一个vfsmount实例以及一个半初始化的struct mount实例。需要指出的是,即使在最普通的mount操作中,也不必完全理解struct mount中的所有内容,我们将在后续文章中单独讨论mount实例在挂载操作中的具体应用。
深入结构体定义,对于struct mount来说,其内部结构相对复杂,包含了一系列成员,理解所有成员的含义、用途以及细节对于Al Viro(VFS的maintainer)级别的开发者而言是必需的。然而,在处理普通mount操作时,我们并不需要深入了解所有内容。
在struct vfsmount中,成员数量相对较少,仅包含三个核心参数。这些参数通过vfs_kern_mount函数获取,为后续的do_add_mount函数提供了基础数据。值得注意的是,全局文件系统树上的一个文件位置不再仅由dentry唯一确定,挂载关系的引入使得情况变得更加复杂。因此,一个文件系统可以挂载到不同的挂载点,文件系统树的一个位置需要由二元组(路径名)来唯一确定。
对于更深入的内容和详细解释,请参考相关文章或资源,以获得更全面的知识体系。本文旨在提供一个概览,帮助理解mount系统调用的内部处理流程及其关键组件。