1.vue3.2 源码浅析:watch、函函数watchEffect、数源式编computed区别
2.vue定义全局方法(vue全局函数)
3.Vue3源码系列 (四) ref
4.Vue源码解析(2)-$mount实现
5.Vue源码解析:Vue编译过程的函函数设计思路
6.Vue2.6x源码解析(一):Vue初始化过程
vue3.2 源码浅析:watch、watchEffect、数源式编computed区别
要理解Vue 3.2中watch、函函数watchEffect、数源式编游戏源码方块1010computed三者的函函数区别,首先需要明确依赖收集和回调函数的数源式编概念。Vue应用启动时,函函数会进行初始化操作,数源式编期间进行依赖收集;初始化结束后,函函数用户修改响应式数据时,数源式编会触发回调函数。函函数
watchEffect()参数中的数源式编effect函数在应用启动期间会执行一次,进行依赖收集。函函数watch()参数中的cb函数在应用启动期间默认不会执行,除非更改watch参数中的option值,使得两者等效。
在初始化期间,computed()返回值被引用时,参数中的{ get}函数会执行;未被引用则不执行。这体现了computed()与watch、watchEffect的另一个区别。
从执行时机上看,当被监听数据的值发生改变,computed()的回调函数会立即执行。watch()和watchEffect()的回调函数执行时机由option参数中的{ flush?: 'pre' | 'post' | 'sync' }决定。
此外,computed()有返回值,并且返回值也会被监听;而watch()和watchEffect()没有返回值。
接着,从源码的角度分析,无论是watch()还是watchEffect(),其实现都是通过调用doWatch()函数完成的。doWatch()函数创建依赖收集函数getter,创建调度函数scheduler,然后创建ReactiveEffect,并进行依赖收集。当修改被监听数据时,会触发schedule函数,根据{ flush}决定回调函数的执行时机。
对于computed(),源码网源码其源码从参数的option中获取getter和setter函数,返回一个ComputedRefImpl类型的对象。在构造函数中创建effect对象,但不进行依赖收集。依赖收集工作在调用get value()时完成。首次调用get value()后,修改被监听数据,会触发triggerRefValue(this),进而通过get value()计算返回值。
综上所述,了解Vue 3.2中watch、watchEffect、computed的区别,需要从原理和源码两方面入手。掌握这些知识点,有助于更深入地理解Vue的响应式系统和数据监听机制。
vue定义全局方法(vue全局函数)
vue定义全局变量,以及方法的调用
1、vue0设置全局变量的源码如下:VUE介绍:DisplayOptions显示选项:包括线框显示选项框,填充框,线框,平面阴影和平滑阴影。每个对象及显示窗口均可以对这些选项进行单独设置。
2、Vuex也可以设置全局变量定义全局函数原理新建一个模块文件,然后在main.js里面通过Vue.prototype将函数挂载到Vue实例上面,通过this.函数名,来运行函数。
3、}然后在调用全局变量的vue的html中加入:{ { this.defines.name}}就可以显示全局变量了。修改的方法为:在调用全局变量的vue的script中加入:this.defines.setname(小强)console.log(this.defines.name)就能修改了。
4、自定义一个全局变量js文件,命名为g_data在main.js中通过prototype挂载到vue上至此已经可以使用this.g_data来访问全局对象了。
VUE中如何构建js调用的全局组件1、全局组件:只需要在main.js中导入一次,整个项目都可以直接使用。
2、定义全局插件pluginHaha.jsVue.js的插件应当有一个公开方法install。
3、定义Vue全局方法。main.js中定义:Vue.prototype.myfunction=function(){ /*你的图源码自定义Vue方法*/}子组件调用:this.myfunction();定义Window对象全局方法。
Vue自定义指令
自定义指令需要注册后才能使用,Vue提供了两种注册方式:全局注册和局部注册。
Vue自定义指令有全局注册和局部注册两种方式。先来看看注册全局指令的方式,通过Vue.directive(id,[definition])方式注册全局指令。然后在入口文件中进行Vue.use()调用。
除了核心功能默认内置的指令,Vue也允许注册自定义指令,在你需要对普通DOM元素进行底层操作的情况下,这时候就会用到自定义指令directive。
Vue提供了自定义指令的5个钩子函数:钩子函数的参数(即el、binding、vnode和oldVnode)。
Vue3源码系列 (四) ref
一般而言,reactive用于定义响应式对象,而ref则用于定义响应式原始值。前文已介绍reactive,了解到通过Proxy对目标对象进行代理实现响应式,非对象原始值的响应式问题则由ref解决。
ref和shallowRef各有三种重载,参数不同,都返回Ref/ShallowRef类型的值。createRef函数用于创建响应式值,类似reactive,createRef也是通过createReactiveObject创建响应式对象。而createRef返回RefImpl实例。
RefImpl是ref的核心内容,构造函数接收两个参数,value是传入的原始值,__v_isShallow用于区分深层/浅层响应式,isShallow()函数利用这个属性做判断。在Ref中,_value属性存储实际值,dep属性存储依赖,在class的getter中通过trackRefValue(this)收集依赖,在setter中调用triggerRefValue(this, newVal)。
trackRefValue用于收集Ref依赖,接收RefBase类型值,在ref函数中接收RefImpl实例。shouldTrack用于暂停和恢复捕获依赖的源码图标志,activeEffect标记当前活跃的effect。内部调用trackEffects函数收集依赖,该函数来自effect模块。
triggerRefValue函数用于触发Ref的响应式更新,triggerEffects函数来自effect模块。
Vue3还提供了自定义的Ref,可以传入getter和setter,自由选择track和trigger时机。
在setup函数中返回参数时,使用toRef创建ObjectRefImpl实例对响应式对象的某个属性进行解构。
ObjectRefImpl通过_object属性引用原始响应式对象,在getter中通过_object访问值,依赖收集由_object完成;在setter中,通过引用_object达到赋值操作,从而在_object中触发更新。toRef判断入参是否是Ref,是则直接返回,否则返回ObjectRefImpl。toRefs对传入的对象/数组进行遍历并执行toRef解构。
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为字符串时,app源码源码会通过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源码解析:Vue编译过程的设计思路
知识要点:
概览
在实例化Vue时,首先经过选项合并和数据初始化,最后进入挂载阶段。此阶段分为编译阶段和更新阶段。编译阶段将template编译为生成Vnode的render函数,核心是compile过程。更新阶段则将生成的虚拟Dom映射至真实Dom。接下来重点解析编译阶段。
编译原理
了解Vue编译过程前,先学习编译原理。编译器结构通常包含词法分析、语法分析、语义分析、中间代码生成、代码优化和目标代码生成。这些步骤对Vue的编译过程至关重要,如页面渲染、代码转换、Vue代码编译等。
编译过程
Vue编译过程由parse、optimize和generate三个阶段组成。parse生成抽象语法树(ast),optimize进行语法树优化,generate将语法树转化为生成Vnode的代码。实际操作以解析简单模板为例,通过ast表示模板字符串,便于后续操作。
编译入口
编译入口在$mount函数中,其定义在多个文件中。$mount进行不同处理以适应template的多种写法。编译模板的核心方法compileToFunctions在platforms文件夹下的src/compiler/index.js中。
函数科里化
Vue通过函数科里化将代码复用,将baseCompile和baseOptions分离传入,实现不同平台或端的代码封装。这样无需更改内部内容,便于平台间代码适应。
细节解析
baseOptions在platforms/web/compiler/options.js文件中定义,包含平台相关方法和属性。baseCompile是编译流程核心实现,compile函数在src/complier/create-compiler.js最内层完成。
创建编译函数
createCompileToFunctionFn将编译后的代码缓存,用于下次使用,同时将代码字符串转换为函数形式,生成render函数和静态渲染函数集合。
总结
本章从整体上介绍了Vue挂载过程和编译原理,解析了多次回调处理编译函数的原因。下章将结合源码深入学习Vue内部编译过程,了解template如何转换为生成Vnode的render函数。欲了解更多解析,点击这里查看。
Vue2.6x源码解析(一):Vue初始化过程
Vue2.6x源码解析(一):Vue初始化过程
Vue.js的核心代码在src/core目录,它在任何环境都能运行。项目入口通常在src/main.js,引入的Vue构造函数来自dist/vue.runtime.esm.js,这个文件导出了Vue构造函数,允许我们在创建Vue实例前预置全局API和原型方法。
初始化前,Vue构造函数在src/core/instance/index.js中定义,它预先挂载了全局API如set、delete等。即使不通过new Vue初始化,Vue本身已具备所需功能。
当执行new Vue时,实际上是调用了_init方法,这个过程会在src/core/index.js的initGlobalAPI(Vue)中初始化全局API和原型方法。接着,组件实例的初始化与根实例基本一致,包括组件构造函数的定义,以及组件的生命周期、渲染和挂载。
组件初始化过程中,关键步骤包括数据转换为响应式、事件注册和watcher的创建。例如,组件的渲染函数会触发渲染方法,而watcher的更新则通过异步更新队列机制确保性能。
在开发环境,Vue-template-compiler插件负责模板编译,然后runtime中的$mount方法负责实际的渲染和挂载。整个过程涉及组件的构建、渲染函数生成、依赖响应式数据的更新和异步调度。
vue 源码详解(三): 渲染初始化 initRender 、生命周期的调用 callHook 、异常处理机制
在Vue的源码解析中,本文着重于三个关键点:渲染初始化、生命周期调用及其异常处理机制。这些要素构成了Vue实例构建过程的核心,确保了应用在运行时的流畅性和稳定性。渲染初始化
在Vue实例初始化阶段,一系列关键属性和方法被设置,为后续的渲染工作做好准备。其中,$attrs和$listeners的使用虽然在普通开发场景中可能较少涉及,但在高阶组件中却发挥着重要作用。未来,将专门撰写一篇文章详细阐述其使用方法和场景。生命周期调用与callHook
在完成渲染初始化后,Vue实例开始执行生命周期钩子函数,以执行特定的初始化任务。这些生命周期函数以数组形式存储,形成“任务队列”,确保了函数按照预设顺序执行。调用callHook函数触发beforeCreate生命周期,该函数会遍历队列中的每个任务,并以当前组件实例为上下文执行这些函数。值得一提的是,在调用生命周期钩子时,Vue会暂时禁用依赖收集,以避免不必要的渲染操作。这一机制通过pushTarget和popTarget函数实现,确保在执行钩子函数后,状态能正确恢复。异常处理机制
Vue具有完善的异常处理机制,能够确保在遇到错误时,能够优雅地控制和处理。当组件内出现异常时,异常信息会沿组件链向上层组件传播,直至根组件。这一过程能够确保错误信息被妥善处理,避免了错误对应用整体性能的影响。通过配置组件上的errorCaptured属性,开发者可以选择阻止异常向上层组件传播,从而实现更精细的错误管理。 在Vue的生命周期管理和异常处理方面,callHook函数作为触发器,通过遍历生命周期队列执行相应任务。而invokeWithErrorHandling函数则负责处理每个任务函数的执行,确保即使在执行过程中出现异常,也能通过适当的错误处理机制进行统一管理和控制。 综上所述,Vue的渲染初始化、生命周期调用和异常处理机制构成了其高效、灵活且安全的运行基础,为开发者提供了强大的工具集,以构建复杂的应用程序。通过深入理解这些核心部分,开发者能够更有效地利用Vue的特点,实现高效、稳定的应用开发。每天学点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}
Vue3源码细读——ref
深入解析Vue3中ref的实现细节
在Vue3源码中,ref相关功能主要集中在'packages/reactivity/src/ref.ts'文件里。
在该文件中,ref的使用与处理主要依赖于最后一个函数的调用:`createRef(value, false)`。通过此函数,可以创建或更新ref实例。
接下来,让我们深入探讨`createRef`函数。它首先判断传入的参数是否已经是一个ref实例,如果是,则直接返回;否则,将返回一个`RefImpl`实例。进一步了解`RefImpl`构造函数,我们发现它包含了`isShallow`和`isReadonly`两个关键属性,它们负责判断ref实例的浅度和是否为只读。
通过阅读源码,我们了解到在控制台log中出现的`_value`和`_rawValue`函数。这些函数用于方便进行判断和对比,尤其是`_rawValue`记录了ref的原始值,以避免不必要的更新,比如在值未发生变化时,节省了性能损耗。实践一下,例如页面上的button点击修改值,然后使用watch监控ref,你会发现watch并未执行。
在源码中,还隐含了`trackRefValue`和`triggerRefValue`两个函数。它们分别在`ref.ts`文件内声明,分别负责跟踪和触发ref值的变化。
让我们继续深入到`trackRefValue`函数,它主要负责跟踪ref值的变化。`activeEffect`的概念在这里出现,它在ref的读取操作中扮演关键角色。当值发生变化时,Vue通过关联`activeEffect`实现响应式更新。具体来说,当我们进行第一次读取时,会将这种关联关系存储起来(通常使用Set数据结构)。改变值时,通过这些关联进行更新(响应式),达到响应式效果。
至此,关于Vue3中ref源码的解读暂时告一段落。如果有任何错误或需要进一步讨论的地方,欢迎大神们指出,同时,我也期待着自己的进步。
2024-11-30 10:48
2024-11-30 10:24
2024-11-30 10:24
2024-11-30 10:23
2024-11-30 10:15
2024-11-30 10:08