Vue.js 3.0 核心源码内参
946_Vue.js 3.0 核心源码内参
已发布: 36
7624 || 已发布 || 开篇词 | 解析 Vue.js 源码,提升编码能力 || 44bc7c75948c49c286638e91f6847a19
开篇词|解析Vue.js源码,提升编码能力
黄轶(常用ID:ustbhuangyi)现任 Zoom 前端架构师,曾先后于百度、滴滴从事前端研发工作喜欢钻研新技术、新框架,关注前端自动化、工程化、前端架构
为什么你要学习 Vue.js 源码?
前端技术日新月异的今天,前端应用的复杂度也在日益提升熟练掌握一门 MVVM 前端开发框架已经成为必然要求
uni-app
Mpx
chameleon
WePY
为什么你要学习Vue.js 源码?
市场需求与人才现状间存在不少现实矛盾:
很多初学者通过简单的培训后便入行,但所学大多是 Demo 级别的项目知识到了真实的工作环境中往往水土不服
工作中只会简单地调用 API,而复杂的组件非常依赖开源的实现如果找不到相关组件甚至难以完成开发需求
为什么你要学习 Vue.js 源码?市场需求与人才现状间存在不少现实矛盾:没有深入研究过,或者根本不懂 Vue.js 底层实现原理开发中遇到 Bug 后不懂得如何分析解决问题,也不懂如何调试;
工作中往往需要通过阅读源码去了解当前项目和一些第三方依赖库的实现方式和原理但是简单的知识填充式的培训并不能教会这些,初学者也很难自己形成这样的能力
面试官考察技术背后的实现原理来判断你对技术的掌握程度
了解技术实现原理是前端工作的必然要求
看源码是了解技术实现原理的最直接手法
是高效提升个人技术能力的有效途径
有助于提升你的JavaScript 功底提升工作效率,形成学习与成长的良性循环借鉴优秀源码的经验,学习高手思路
提升自己解读源码的能力
道理我都懂,就是做不到?
为什么 却很少有人愿意去读源码呢?
为什么却很少有人愿意去读源码呢?
因为学习源码很枯燥,不像开发项目那样能够快速得到反馈、看到立竿见影的效果学习源码相对于开发项目来说更抽象,理解起来也更难,很多人学着学着就放弃了还有很多人想要更深入地学习 Vue.js,希望能够再进阶一个高度,却不得法门
使用Vue.js 重构整个滴滴出行的WebApp负责其中的架构设计和组件库开发,也主导过Vue.js开源组件库 cube-ui的开发
为了配合安全组的CSP安全策略需求通过直接魔改Vue.js源码的方式,开发了Vue.js2.x的CSP兼容版本该版本目前在Zoom内部运行稳定,服务于几十个用Vue.js做增强开发的页面
对Vue.js3.0的源码进行透彻分析,但不会一味地去解释源码而是更加注重解读Vue.js在实现某个feature 的时候它的设计思想是什么以及为什么会这么做
核心模块分析Vue.js 3.0组件的实现原理、响应式原理以及Vue.js 3.0新特性Composition API的实现原理
《Vue.js 3.0核心源码解析》课程大纲开篇词|解析Vue.js源码,提升编码能力导读|一文看懂Vue.js 3.0的优化模块一:直击Vue.js核心组件的实现组件渲染:vnode到真实DOM是如何转变的?组件更新:完整的 DOM diff 流程是怎样的?(上)2组件更新:完整的DOM diff 流程是怎样的?(下)3模块二:学会新设计 Composition APlSetup:组件渲染前的初始化过程是怎样的?响应式:响应式内部的实现原理是怎样的?(上)5响应式:响应式内部的实现原理是怎样的?(下)
互联网人实战大学模块二:学会新设计 Composition APISetup:组件渲染前的初始化过程是怎样的?4响应式:响应式内部的实现原理是怎样的?(上)5响应式:响应式内部的实现原理是怎样的?(下)6计算属性:计算属性比普通函数好在哪里?7侦听器:侦听器的实现原理和使用场景是什么?(上)8侦听器:侦听器的实现原理和使用场景是什么?(下)9生命周期:各个生命周期的执行时机和应用场景是怎样的?10依赖注入:子孙组件如何共享数据?11模块三:编译过程和背后的优化思想模板解析:构造AST的完整流程是怎样的?(上)12
互联网人实战模块三:编译过程和背后的优化思想模板解析:构造AST的完整流程是怎样的?(上)12模板解析:构造AST的完整流程是怎样的?(下)13AST转换:AST节点内部做了哪些转换?(上)14AST转换:AST节点内部做了哪些转换?(下)15国生成代码:AST如何生成可运行的代码?(上)国生成代码:AST如何生成可运行的代码?(下)模块四:探索更多实用特性背后的实现原理Props:Props的初始化和更新流程是怎样的?18插槽:如何实现内容分发?19四指令:指令完整的生命周期是怎样的?
模块四:探索更多实用特性背后的实现原理Props:Props 的初始化和更新流程是怎样的?18插槽:如何实现内容分发?19指令:指令完整的生命周期是怎样的?20v-model:双向绑定到底是怎么实现的?21模块五:学习Vue内置组件的实现原理Teleport 组件:如何脱离当前组件渲染子组件?22Suspence组件:如何优雅地实现组件异步处理流程?23KeepAlive组件:如何让组件在内存中缓存和调度?24Transition 组件:过渡动画的实现原理是怎样的?25特别放送:研究Vue官方生态的实现原理
特别放送:研究Vue官方生态的实现原理Vue Router:如何实现一个前端路由?26Vuex:如何实现前端的状态管理?27
·注重思想层面的解读,以及Vue.js版本更新的新功能解读·展示编译后的JavaScript 代码并注释·精简代码的分支逻辑,方便理解核心流程·结合图例理解难懂的代码功能
需要写编译打包工具Vue.js开发项目阅读了FIS和Gulp的源码开始阅读Vue.js的源码
开源库better-scroll也是在我充分阅读iScroll源码的基础上重构并一点点优化出来的
7625 || 已发布 || 导读 | 一文看懂 Vue.js 3.0 的优化 || f4961a07946e48079660369a2532fcf5
Vue.js 框架演进的过程
Vue.js 3.0 主要做了哪些优化
引入了虚拟DOMVue.js 1.xVue.js 2.×
源码自身的维护性数据量大后带来的渲染和更新的性能问题兼容性想舍弃但为了兼容一直保留的鸡肋APl
更好的编程体验更好的TypeScript 支持更好的逻辑复用实践
源码优化·首先是源码优化,也就是小右对于Vue.js框架本身开发的优化它的目的是让代码更易于开发和维护源码的优化主要体现在使用monorepo和TypeScript 管理和开发源码这样做的目标是提升自身代码可维护性
更好的代码管理方式:monorepo·相对于Vue.js 2.x的源码组织方式,monorepo把这些模块拆分到不同的package中每个 package有各自的API、类型定义和测试这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确开发人员也更容易阅读、理解和更改所有模块源码,提高代码的可维护性
更好的代码管理方式:monorepo拉勾教package(比如reactivity响应式库)是可以独立于Vue.js使用的这样用户如果只想使用Vue.js 3.0的响应式能力,可单独依赖这个响应式库而不用去依赖整个Vue.js减小了引用包的体积大小,而Vue.js2.x是做不到这一点的
有类型的JavaScript:TypeScriptFlow·Flow是Facebook出品的JavaScript静态类型检查工具,它可以以非常小的成本对已有的JavaScript代码迁入,非常灵活,这也是Vue.js 2.0当初选型它时一方面的考量
Flow对于一些复杂场景类型的检查,支持得并不好记得在看Vue.js2.x源码的时候,在某行代码的注释中看到了对Flow的吐槽
性能优化Vue.js 3.0在源码体积的减少方面做了哪些工作呢?移除一些冷门的 feature引入tree-shaking的技术
源码体积优化
引入 tree-shaking 的技术依赖 ES2015 模块语法的静态结构(即 import和 export)
通过编译阶段的静态分析,找到没有引入的模块并打上标记
实现DOM功能,必须劫持数据的访问和更新当数据改变后,为了自动更新DOM,那么就必须劫持数据的更新也就是说当数据发生改变后能自动执行一些代码去更新DOM
Vue.js怎么知道更新哪一片DOM呢?因为在渲染DOM的时候访问了数据,我们可以对它进行访问劫持这样就在内部建立了依赖关系,也就知道数据对应的DOM是什么了
数据劫持优化通过Object.defineProperty这个API劫持数据的getter和setter,具体是这样的:
注意的是,Proxy APl并不能监听到内部深层次的对象变化因此Vue.js 3.0的处理方式是在getter 中去递归响应式
Object.definePropert Proxy APl 都要递归深层
这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归这样无疑也在很大程度上提升了性能
除了数据劫持部分的优化我们可以在耗时相对较多的patch阶段想办法
Vue.js 2.x的数据更新并触发重新渲染的粒度是组件级的:watcher依赖收集
通过编译阶段对静态模板的分析,编译生成了 Block tree
编译优化拉勾Block treeBlock tree 是一个将模版基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的每个区块只需要以一个Array来追踪自身包含的动态节点借助Block tree,Vue.js将vnode更新性能由与模版整体大小相关提升为与动态内容的数量相关
编译阶段还包含了对Slot的编译优化、事件侦听函数的缓存优化并且在运行时重写了diff算法
除了源码和性能方面,Vue.js3.0还在语法方面进行了优化主要是提供了Composition API
Vue.js 1.xVue.js 2.x编写组件本质就是在编写一个“包含了描述组件选项的对象”我们把它称为Options API
优化逻辑组织拉勾Options API·Options API的设计是按照methods、computed、data、props这些不同的选项分类·当组件小的时候,这种分类方式一目了然;但是在大型组件中,一个组件可能有多个逻辑关注点当使用Options APl的时候,每一个关注点都有自己的 Options如果需要修改一个逻辑点关注点,就需要在单个文件中不断上下切换和寻找
优化逻辑组织拉0102处理文件夹导航跟踪当前文件夹状态并显示其内容(比如打开、关闭、刷新等)0304切换显示收藏夹处理新文件夹的创建0506处理当前工作目录的更改切换显示隐藏文件夹
提供了一种新的API:Composition API就是将某个逻辑关注点相关的代码全都放在一个函数里这样当需要修改一个功能时,就不再需要在文件中跳来跳去
高内聚低耦合
优化逻辑复用·首先每个mixin都可以定义自己的 props、data,它们之间是无感的所以很容易定义相同的变量,导致命名冲突
·对组件而言,如果模板中使用不在当前组件中定义的变量,那么就会不太容易知道这些变量在哪里定义的,这就是数据来源不清晰但是Vue.js 3.0设计的Composition API,就很好地帮助我们解决了mixins的这两个问题
·除了在逻辑复用方面有优势,也会有更好的类型支持·因为它们都是一些函数,在调用函数时,自然所有的类型就被推导出来了不像Options API所有的东西使用this·另外,Composition APl对tree-shaking友好,代码也更容易压缩
引入RFC:使每个版本改动可控Vue.js 2.xRFC (Request For Comments)
旨在为新功能进入框架提供一个一致且受控的路径
Vue.js 3.0大规模启用RFC(Request For Comments)
通常major 版本的升级会有很多 breaking change这就意味着想从2.x升级到3.0的项目需要改代码而且不仅仅项目的代码要修改,所依赖的周边生态也需要升级
使用ES2015的语法开发,有些API如Proxy是没有polyfill的这就意味着官方需要单独出一个IE11compat版本来支持IE11
关注它的发展学习它的设计思想为它的生态建设贡献代码
7629 || 已发布 || 模块一导读 | 组件的实现:直击 Vue 核心的实现 || 85a8ea55e2af48bf863a2be2b5db509e
Vue核心的实现拉勾孝互联网人实组件组件系统是Vue.js的一个重要概念它是一种对DOM结构的抽象使用小型、独立和通常可复用的组件构建大型应用
vue核心的实现拉勾组件化组件化也是Vue.js的核心思想之一,它允许我们用模板加对象描述的方式去创建一个组件再加上我们给组件注入不同的数据,就可以完整地渲染出组件
模板对象描述数据组件
7626 || 已发布 || 01 | 组件渲染:vnode 到真实 DOM 是如何转变的? || 078331895874477ebb5e4afd4097d770
组件是一个抽象的概念,它是对一棵DOM树的抽象在页面中写一个组件节点:
应用程序初始化
ensureRenderer()用来创建一个渲染器对象,内部代码//渲染相关的一些配置,比如更新属性的方法,操作 DOM 的方法const rendererOptions ={
patchProp,...nodeOps
//延时创建渲染器,当用户只依赖响应式包的时候,可以通过tree-shaking 移除核心渲染逻辑相关的代码
}
let renderer function ensureRenderer(){
return renderer ||(renderer =
应用程序初始化·在整个app对象创建过程中,Vue.js利用闭包和函数柯里化的技巧,很好地实现了参数保留·比如,在执行app.mount的时候,不需要传入渲染器render因为在执行createAppAPI的时候渲染器render参数已经被保留下来了
因为Vue.js不仅仅是为Web 平台服务,它的目标是支持跨平台渲染createApp函数内部的app.mount方法是一个标准的可跨平台的组件渲染流程:
·重写的目的:既能让用户在使用API时可以更加灵活也兼容了Vue.js 2.x的写法比如app.mount的第一个参数就同时支持选择器字符串和DOM对象两种类型
vnode本质上是用来描述DOM的JavaScript对象,它在Vue.js中可以描述不同类型的节点比如普通元素节点、组件节点等
核心渲染流程:创建vnode和渲染vnode组件vnode对抽象事物的描述
核心渲染流程:创建 vnode和渲染 vnode纯文本vnode注释vnode
核心渲染流程:创建 vnode和渲染 vnode引入 vnode,可以把渲染过程抽象化抽象从而使得组件的抽象能力也得到提升因为 patch vnode的过程不同平台可以有自己的实现跨平台基于vnode再做服务端渲染、weex平台、小程序平台的渲染
核心渲染流程:创建 vnode和渲染 vnode·首先这种基于vnode 实现的MVVM框架,在每次render to vnode 的过程中渲染组件会有一定的 JavaScript 耗时,特别是大组件
核心渲染流程:创建vnode和渲染 vnode·首先这种基于vnode实现的MWWM框架,在每次render to vnode的过程中渲染组件会有一定的JavaScript 耗时,特别是大组件·当我们去更新组件的时候,用户会感觉到明显的卡顿虽然diff算法在减少DOM操作方面足够优秀,但最终还是免不了操作DOM所以说性能并不是vnode的优势
核心渲染流程:创建vnode和渲染 vnode拉勾教互联网人实战7参数n1表示旧的vnode,当n1为null的时候,表示是一次挂载的过程参数n2新的vnode节点,后续会根据这个vnode类型执行不同的处理逻辑参数container表示DOM容器,在vnode渲染生成DOM后,会挂载到container下面
核心渲染流程:创建 vnode和渲染 vnode首先是用来处理组件的processComponent函数的实现:
核心渲染流程:创建 vnode和渲染vnode挂载组件的mountComponent 函数的实现:
创建组件实例内部也通过对象的方式去创建了当前渲染的组件实例instance保留了很多组件相关的数据,维护了组件的上下文包括对props、插槽,以及其他实例的属性的初始化处理设置组件实例
核心渲染流程:创建 vnode和渲染 vnode渲染函数setupRenderEffec的实现:
初始渲染主要做两件事情:渲染组件生成subTree、把subTree挂载到container中
核心渲染流程:创建 vnode和渲染 vnode·如果是其他平台比如Weex,hostCreateElement方法就不再是操作DOM而是平台相关的API了,这些平台相关的方法是在创建渲染器阶段作为参数传入的
·创建完DOM节点后,接下来要做的是判断如果有props的话给这个DOM节点添加相关的class、style、event等属性,并做相关的处理这些逻辑都是在hostPatchProp 函数内部做的
·在mountChildren 的时候递归执行的是patch函数,而不是mountElement函数这是因为子节点可能有其他类型的vnode,比如组件vnode
知识延伸:嵌套组件·在真实开发场景中,App和Hello组件的例子就是嵌套组件的场景组件vnode主要维护着组件的定义对象,组件上的各种props,而组件本身是一个抽象节点它自身的渲染其实是通过执行组件定义的render函数渲染生成的子树vnode来完成然后再patch通过这种递归的方式,无论组件的嵌套层级多深,都可以完成整个组件树的渲染
思考题我们平时开发页面就是把页面拆成一个个组件那么组件的拆分粒度是越细越好吗?为什么呢?
7627 || 已发布 || 02 | 组件更新:完整的 DOM diff 流程是怎样的?(上) || 80b2deca03ac40489a4905b43a676d12
梳理了组件渲染的过程,本质上就是把各种类型的 vnode 渲染成真实 DOM组件是由模板、组件描述对象和数据构成的,数据的变化会影响组件的变化组件的渲染过程中创建了一个带副作用的渲染函数当数据变化的时候就会执行这个渲染函数来触发组件的更新
组件的更新最终还是要转换成内部真实 DOM 的更新
实际上普通元素的处理流程才是真正做DOM的更新
处理组件
虽然Vue.js的更新粒度是组件级别的,组件的数据变化只会影响当前组件的更新但是在组件更新的过程中,也会对子组件做一定的检查,判断子组件是否也要更新并通过某种机制避免子组件重复更新
处理组件
updateComponent 函数
如果 shouldUpdateComponent 返回 true,那么在它的最后先执行 invalidateJob(instance.update)避免子组件由于自身数据变化导致的重复更新然后又执行了子组件的副作用渲染函数instance.update来主动触发子组件的更新拉
处理组件
一个组件重新渲染可能会有两种场景:
一种是组件本身的数据变化,这种情况下 next 是 null另一种是父组件在更新的过程中,遇到子组件节点,先判断子组件是否需要更新如果需要则主动执行子组件的重新渲染方法,这种情况下 next 就是新的子组件 vnode
在父组件重新渲染的过程中
通过 renderComponentRoot 渲染子树 vnode 的时候生成
因为子树 vnode 是个树形结构
通过遍历它的子节点就可以访问到其对应的组件 vnode
本节课的相关代码在源代码中的位置 如下:packages/runtime-core/src/renderer.tspackages/runtime-core/src/componentRenderUtils.ts
7628 || 已发布 || 03 | 组件更新:完整的 DOM diff 流程是怎样的?(下) || 22dd1bf9ca70453eb6045532307753d7
新子节点数组相对于旧子节点数组的变化,无非是通过更新、删除、添加和移动节点来完成核心diff算法,就是在已知旧子节点的DOM结构、vnode和新子节点的vnode情况下以较低的成本完成子节点的更新为目的,求解生成新子节点DOM的系列操作
同步尾部节点拉勾互 联 网处理3种情况:·新子节点有剩余要添加的新节点·旧子节点有剩余要删除的多余节点·未知子序列
处理未知子序列·当两个节点类型相同时,执行更新操作当新子节点中没有旧子节点中的某些节点时,执行删除操作当新子节点中多了旧子节点中没有的节点时,执行添加操作
移动子节点拉勾教育豆联同人实战大学在新旧子节点序列中找出相同节点并更新找出多余的节点删除,找出新的节点添加,找出是否有需要移动的节点,如果有该如何移动
移动子节点拉·对比新旧子序列,则需要遍历某个序列如果在遍历旧子序列的过程中需要判断某个节点是否在新子序列中存在,这就需要双重循环双重循环的复杂度是0(n²),为了优化这个复杂度,建立索引图,把时间复杂度降低到O(n)
建立索引图·在开发过程中,会给v-for生成的列表中的每一项分配唯一key作为项的唯一ID这个key在diff过程中起到很关键的作用·对于新旧子序列中的节点,key相同的就是同一个节点,直接执行patch更新即可
·新旧子序列节点的更新、多余旧节点的删除·建立了一个newlndexToOldlndexMap存储新子序列节点的索引旧子序列节点的索引之间的映射关系,并确定是否有移动
最长递增子序列拉勾教育·求解最长递增子序列是一道经典的算法题,多数解法是使用动态规划的思想,算法的时间复杂度是O(n2)而 Vue.js内部使用的是维基百科提供的一套“贪心+二分查找”的算法贪心算法的时间复杂度是O(n),二分查找的时间复杂度是O(logn),总时间复杂度是O(nlogn)
最长递增子序列拉勾·主要思路:对数组遍历,依次求解长度为i时的最长递增子序列当i元素大于i-1的元素时,添加i元素并更新最长子序列否则往前查找直到找到一个比i小的元素,然后插在该元素后面并更新对应的最长递增子序列
最长递增子序列拉主要目的让递增序列的差尽可能的小,从而可以获得更长的递增子序列,是一种贪心算法的思想
总结整个更新过程还是利用了树的深度遍历,递归执行patch方法最终完成了整个组件树的更新
7636 || 已发布 || 模块二导读 | 逻辑复用最佳实践:Composition API || e1d992af5a2741488c10231e4b709e37
Vue核心的实现拉勾教育Composition API·从语法上看,它提供了一个setup启动函数作为逻辑组织的入口,暴露了响应式APl为用户所用提供了生命周期函数以及依赖注入的接口,OptionsAPl也可以完成一个组件的开发并且更有利于代码逻辑的组织和复用
7630 || 已发布 || 04 | Setup:组件渲染前的初始化过程是怎样的? || 38cbd448b00b4c6aa6d4e567514565bb
创建渲染上下文代理·Vue.js3.0,为了方便维护,把组件中不同状态的数据存储到不同的属性中比如存储到setupState、ctx、data、props中·在执行组件渲染函数的时候,直接访问渲染上下文instance.ctx中的属性,做一层proxy对渲染上下文instance.ctx属性的访问和修改代理到对setupState、ctx、data、props中的数据的访问和修改
创建渲染上下文代理拉勾教育互联间人实战大学·第一次获取key对应的数据后利用 accessCache[key]去缓存数据·下一次再次根据key查找数据直接通过 accessCache[key]获取对应的值不需要依次调用hasOwn去判断
注意,如果我们直接对props中的数据赋值,在非生产环境中会收到一条警告因为直接修改props不符合数据单向流动的设计思想
判断处理setup 函数拉勾教育联网人实战大·在handleSetupResult的最后,会执行finishComponentSetup 函数完成组件实例的设置·当组件没有定义的setup的时候,也会执行finishComponentSetup函数去完成组件实例的设置
标准化模板或者渲染函数·第一种是使用SFC(Single File Components)单文件的开发方式来开发组件通过编写组件的template模板去描述一个组件的 DOM 结构·另外一种开发方式是不借助webpack编译,直接引入Vue.js直接在组件对象template 属性中编写组件的模板
runtime-onlyruntime-compiled
标准化模板或者渲染函数runtime-onlyruntime-compiled主要区别在于是否注册了这个compile方法
compile和组件template属性存在,render方法不存在的情况runtime-compiled 版本会在JavaScript运行时进行模板编译,生成render函数
compile和render方法不存在,组件template属性存在的情况由于没有compile,用的是runtime-only的版本报一个警告来告诉用户,想要运行时编译得使用runtime-compiled 版本的Vue.js
组件既没有写render 函数,也没有写template模板此时要报一个警告,告诉用户组件缺少了 render函数或者template模板
标准化模板或者渲染函数·处理完以上情况后,就要把组件的render函数赋值给instance.render组件渲染的时候,运行 instance.render 函数生成组件的子树 vnode·使用 with块运行时编译的渲染函数,渲染上下文的代理是RuntimeCompiledPubliclnstanceProxyHandlers,在之前渲染上下文代理PubliclnstanceProxyHandlers 的基础上进行的扩展
选项API:兼容Vue.js 2.x通过应用选项方法实现:
在执行设置函数并获取结果的时候使用callWithErrorHandling把设置包装了一层,它有哪些好处?
本节课的相关代码在源代码中的位置如下:packages/runtime-core/src/renderer.tspackages/runtime-core/src/component.tspackages/runtime-core/src/componentProxy.tspackages/runtime-core/src/errorHandling.ts
7631 || 已发布 || 05 | 响应式:响应式内部的实现原理是怎样的?(上) || 0de4796887c64c798245691afc7d3421
·Vue.js另一个核心设计思想就是响应式,本质是当数据变化后会自动执行某个函数映射到组件的实现就是,当数据变化后,会自动触发组件的重新渲染响应式是Vue.js组件化更新渲染的一个核心机制
render watcher
依赖收集流程
派发通知流程
Object.defineProperty API 缺点:·不能监听对象属性新增和删除·初始化阶段递归执行 Object.defineProperty带来的性能负担
解决Object.defineProperty API缺点Proxy APl重写了响应式部分,并独立维护及发布整个reactivity库
响应式对象的实现差异根本原因在created 中定义的this.msg并不是响应式对象
在data 中定义数据最终也是挂载到组件实例this 上直接在 created 钩子函数通过this.xxx定义的数据唯一区别就是,在data中定义的数据是响应式的
仅仅想在组件上下文中共享某个变量,而不必去监测它的变化在 created 钩子函数中去定义这个变量
createReactiveObject 函数函数首先判断target是不是数组或者对象类型,如果不是则直接返回原始数据target 必须是对象或者数组
Reactive API·响应式的实现方式就是劫持数据,Vue.js 3.0的reactive API就是通过Proxy 劫持数据由于Proxy劫持的是整个对象,所以检测到任何对对象的修改弥补了Object.defineProperty API的不足
劫持对observed对象的一些操作,比如:·访问对象属性会触发get函数·设置对象属性会触发set函数·删除对象属性会触发deleteProperty 函数·in操作符会触发has函数·Object.getOwnPropertyNames触发ownKeys函数
依赖收集:get函数依赖收集发生在数据访问的阶段get 函数的实现:
依赖收集:get函数·函数最后会对计算的值res进行判断如果它也是数组或对象,则递归执行reactive把res变成响应式对象·因为Proxy劫持的是对象本身,并不能劫持子对象的变化
7632 || 已发布 || 07 | 计算属性:计算属性比普通函数好在哪里? || c92661cef3394c95ad083a7bea7029d3
·计算属性是Vue.js开发中一个非常实用的API,它允许用户定义一个计算方法然后根据一些依赖的响应式数据计算出新值并返回当依赖发生变化时,计算属性可以自动重新计算获取新值,使用方便
计算属性API:computed在getter函数中,根据响应式对象重新计算出新的值,叫做计算属性这个响应式对象,就是计算属性的依赖
计算属性的运行机制
computed 函数
dirty
value
计算属性的运行机制拉computed 计算属性有两个特点:·延时计算,只有当我们访问计算属性的时候,真正运行computed getter 函数计算·缓存,它的内部会缓存上次的计算结果value,而且只有dirty为true时才会重新计算如果访问计算属性时dirty为false,那么直接返回这个value
计算属性的优势:只要依赖不变化,就可以使用缓存的value而不用每次在渲染组件的时候都执行函数去计算
空时互换 余空换时r
import {effect } from '@vue/reactivity'
理解计算属性的工作机制,能搞明白计算属性嵌套场景代码的执行顺序知道计算属性的两个特点——延时计算和缓存,在组件的开发中合理使用计算属性
7633 || 已发布 || 08 | 侦听器:侦听器的实现原理和使用场景是什么?(上) || 65b1ad9f68b54d388120ec903e153609
通过$watch API去创建一个侦听器:const unwatch = vm.$watch('a', function(newVal, oldVal) {console.log('new: %s, old: %s', newVal, oldVal)リ
api 可手动取消
watch API实现原理侦听器,当侦听的对象或者函数发生了变化则自动执行某个回调函数与副作用函数effect 很像,那它的内部实现是不是依赖了 effect 呢?
标准化source拉source标准化根据source的类型分类:如果source是ref对象,则创建一个访问source.value的getter函数如果source是reactive对象,则创建一个访问source的getter函数,并设置deep为true
source 标准化根据 source的类型分类:如果source是一个函数,则会进一步判断第二个参数cb是否存在,对于watch APl来说cb是一定存在且是一个回调函数,getter就是一个简单的对source函数封装的函数
当侦听一个通过reactive APl创建的响应式对象时,内部执行 traverse 函数如果这个对象非常复杂,如嵌套层级很深,递归traverse 就会有一定的性能耗时如果侦听这个复杂响应式对象内部的某个具体属性,想办法减少traverse 带来的性能损耗
侦听一个getter函数:watch(() => state.count.a.b, (newVal, oldVal) =>{console.log(newVal)りstate.count.a.b = 2
Options 中的flush决定了watcher的执行时机·当flush为sync的时,表示它是一个同步watcher,即当数据变化时同步执行回调函数·当flush为pre的时,回调函数通过queueJob的方式在组件更新之前执行如果组件还没挂载,则同步执行确保回调函数在组件挂载之前执行
如果没设置flush,回调函数通过queuePostRenderEffect 的方式在组件更新之后执行
侦听器的内部设计,侦听响应式数据的变化,内部创建 effect runner首次执行runner做依赖收集,然后在数据发生变化后,以某种调度方式去执行回调函数
7637 || 已发布 || 06 | 响应式:响应式内部的实现原理是怎样的?(下) || f9721939e9c2456b9bc7e5109fd08495
·在Vue.js 3.0中引入reactive API,把对象数据变成响应式着重分析reactive APl的实现原理,并学习了收集依赖的get函数分析 reactive APl中需要关注的另一个内容—一派发通知的过程
trigger 函数就是根据target 和key从targetMap中找到相关的所有副作用函数遍历执行一遍
副作用函数·假设没有 cleanup,在第一次渲染模板的时候,activeEffect是组件的副作用渲染函数因为模板 render的时候访问了 state.msg,所以会执行依赖收集把副作用渲染函数作为state.msg的依赖,称作render effect
readonly APImutableHandlersreadonlyHandlers主要在get、set 和 deleteProperty 三个函数上
不做依赖收集dep 不跟踪变化
ref API·active APl对传入的target类型有限制,必须是对象或者数组类型对于一些基础类型(比如String、Number、Boolean)是不支持的
响应式API的实现原理,知道什么时候收集依赖,什么时候派发更新副作用函数的作用和设计原理reactive、readonly、ref三种API的区别和各自的使用场景
7638 || 已发布 || 09 | 侦听器:侦听器的实现原理和使用场景是什么?(下) || a692a8e8730f4b3683577ad14883c033
异步任务队列的设计
·只输出了一次count的值,最终计算的值3在大多数场景下都是符合预期的·在一个Tick(宏任务执行的生命周期)即使多次修改侦听的值它的回调函数也只执行一次
sFlushPending的控制,即使多次执行queueFlush,也不会多次去执行flushJobsnextTick在Vue.js 3.0中的实现,通过Promise.resolve().then去异步执行flushJobs
异步任务队列的创建·JavaScript是单线程执行的,异步设计在一个Tick内可以多次执行queueJob或者queuePostFlushCb去添加任务可以保证在宏任务执行完毕后的微任务阶段执行一次flushJobs
异步任务队列queue从小到大的排序创建组件的过程是由父到子,创建组件副作用渲染函数先父后子父组件的副作用渲染函数的effect id是小于子组件每次更新组件也是通过queueJob 把 effect 推入异步任务队列 queue 中
注意checkRecursiveUpdates的逻辑用来在非生产环境下检测是否有循环更新的 防止死循环处理
isFlushPending用于判断是否在等待nextTick执行flushJobsisFlushing是判断是否正在执行任务队列
watchEffect API的作用是注册一个副作用函数副作用函数内部可以访问到响应式对象,当内部响应式对象变化后再立即执行这个函数
watchEffect和watch API不同点:·立即执行,watchEffect APl在创建好watcher后,立刻执行它的副作用函数watch API需要配置immediate为true,才会立即执行回调函数
掌握了侦听器内部实现原理,了解侦听器支持的几种配置参数的作用,以及异步任务队列的设计原理掌握侦听器的常见应用场景:如何用watchAPl观测数据的变化去执行一些逻辑如何利用 watchEffect API去注册一些副作用函数,如何去注册无效回调函数以及如何停止一个正在运行的 watcher
侦听器更适合用于在数据变化后执行某段逻辑的场景计算属性用于一个数据依赖另外一些数据计算而来的场景
7634 || 已发布 || 10 | 生命周期:各个生命周期的执行时机和应用场景是怎样的? || 64bfa33a8065452bbaa7d7e76a1aa8fc
beforeCreate->使用setup()created->使用use setup()beforeMount -> onBeforeMountmounted -> onMountedbeforeUpdate -> onBeforeUpdateupdated -> onUpdated映射关系如下:beforeDestroy-> onBeforeUnmountdestroyed -> onUnmountedactivated -> onActivateddeactivated -> onDeactivatederrorCaptured -> onErrorCaptured
Vue.js 3.0新增调试生命周期APIonRenderTrackedonRenderTriggered
注册钩子函数拉勾教育生命周期的钩子函数,是在组件生命周期的各个阶段执行钩子函数必须要保存在当前的组件实例上,通过不同的字符串key找到对应的钩子函数数组并执行
onBeforeMount 和 onMounted·都可以,因为created 和mounted 钩子函数执行的时候都能拿到组件数据执行的顺序虽然有先后,但都会在一个Tick内执行完毕异步请求是有网络耗时的,其耗时远远大于一个Tick的时间
errorCaptured本质上是捕获一个来自子孙组件的错误,返回true阻止错误继续向上传播
7635 || 已发布 || 11 | 依赖注入:子孙组件如何共享数据? || 45557dd0862046248599447c8c471fb3
核心技术 原型连
对比模块化共享数据的方式拉勾教育作用域不同对于依赖注入,它的作用域是局部范围,把数据注入以这个节点为根的后代组件中对于模块化的方式,它的作用域是全局范围的,在任何地方引用它导出的数据
数据来源不同对于依赖注入,后代组件是不需要知道注入的数据来自哪里,注入并使用对于模块化的方式提供的数据,用户必须知道这个数据是在哪个模块定义的,引入它
上下文不同对于依赖注入,根据不同的组件上下文提供不同的数据给后代组件对于模块化提供的数据,从API层面设计做更改
依赖注入的缺陷和应用场景祖先组件不需要知道哪些后代组件在使用它提供的数据后代组件也不需要知道注入的数据来自哪里不推荐在普通应用程序代码中使用依赖注入
依赖注入的缺陷和应用场景this.Sparent是一种强耦合的获取父组件实例方式,不利于代码的重构因为一旦组件层级发生变化,就会产生非预期的后果,在平时的开发工作中慎用
依赖注入,提供组件实例数据,提供任意类型的数据因为入口组件和它的相关子组件关联性是很强
mixin可能会命名冲突....
Composition API 在逻辑复用上有不错的优势
Composition API 属于 API的增强
7639 || 已发布 || 12 | 模板解析:构造 AST 的完整流程是怎样的?(上) || b38118e1525d4824849bbecd777f3cae
baseCompile 的实现:function baseCompile(template, options = {]) {const prefixldentifiers = false/∥解析 template 生成 AST
生成相应的 AST 对象 生成 AST 抽象语法树
描述tmepleate vnode
7645 || 已发布 || 模块三导读 | 编译和优化:了解编译过程和背后的优化思想 || e90727d34e6d4866afe8f8e5710e6fab
编译和优化renderComponentRoot内部通过执行组件实例的render函数,创建生成子树 vnode
使用官方的一个模板导出工具,在线调试模板的实时编译结果,辅助学习在vue-next源码packages/template-explorer/dist/template-explorer.global.js 中的关键流程打debugger 断点,然后在根目录下运行npm run dev-compiler命令接着访问http://localhost:5000/packages/template-explorer 调试即可
7642 || 已发布 || 13 | 模板解析:构造 AST 的完整流程是怎样的?(下) || 613548ab00e9467ab11faf2ad17ef9c7
元素节点的解析HTML的嵌套结构的解析过程一个递归解析元素节点的过程
ancestors的设计就是一个栈的数据结构整个过程是一个不断入栈和出栈的过程
总结掌握Vue.js编译过程的第一步,即把template解析生成AST 对象整个解析过程是一个自顶向下的分析过程,从代码开始,通过语法分析找到对应的解析处理逻辑,创建AST节点,处理的过程中也在不断前进代码更新解析上下文,最终根据生成的AST节点数组创建AST根节点
7640 || 已发布 || 14 | AST 转换:AST 节点内部做了哪些转换?(上) || aba01517a26e411ea2feca0274095386
通过getBaseTransformPreset方法返回节点和指令转换的方法
遍历AST节点Vue.js八种转换函数处理指令、表达式、元素节点、文本节点等
注意:只有在Node.js环境下的编译或者是Web 端的非生产环境下执行transformExpression
transformExpression转换插值和元素指令中的动态表达式内部主要是通过processExpression 函数完成
表达式节点转换函数processExpression 的详细实现因为内部依赖了 @babel/parser 库去解析表达式生成 AST 节点并依赖了 estree-walker库去遍历这个AST节点,然后对节点分析去判断是否需要加前缀接着对AST节点修改,最终转换生成新的表达式对象
表达式节点转换函数·@babel/parser这个库通常是在Node.js端用的,本身体积非常大·如果打包进Vue.js的话会让包体积膨胀4倍,不会在生产环境的Web端引入这个库Web 端生产环境下的运行时编译最终会用 with的方式
7643 || 已发布 || 15 | AST 转换:AST 节点内部做了哪些转换?(下) || 5aee26ed6328440f83094132d9e11f4c
·如果说parse阶段是一个词法分析过程,构造基础的AST节点对象那么 transform 节点就是语法分析阶段,把AST节点做一层转换,构造出语义化更强信息更加丰富的codegenCode,它在后续的代码生成阶段起着非常重要的作用
7641 || 已发布 || 16 | 生成代码:AST 如何生成可运行的代码?(上) || cafa926e3b7741fa9dc74a0a733db9c1
加工代码 优化输出。。。
7644 || 已发布 || 17 | 生成代码:AST 如何生成可运行的代码?(下) || d3564a6c5a854813a837e244914047d2
generate 主要做五件事情:创建代码生成上下文,生成预设代码,生成渲染函数生成资源声明代码,生成创建VNode树的表达式
收据动态block
Block 数组是一维的,但是动态的子节点可能有嵌套关系patchBlockChildren内部也是递归执行了patch 函数那么在整个更新的过程中,出现子节点重复更新的情况吗,为什么?
了解Props是如何被初始化的,如何被校验的,区分开Props配置和Props传值了解 Props是如何更新的以及实例上的props为什么要定义成响应式的
插槽的实现实际上就是一种延时渲染,把父组件中编写的插槽内容保存到一个对象上并且把具体渲染DOM的代码用函数的方式封装,然后在子组件渲染的时候根据插槽名在对象中找到对应的函数,然后执行这些函数做真正的渲染
7650 || 已发布 || 模块四导读 | 实用特性:探索更多实用特性背后的原理 || d0039cd6101e4cc9a40f9a2f04b02f4c
Vue.js的核心思想之一是数据驱动手动操作某个元素节点的DOM
7646 || 已发布 || 18 | Props:Props 的初始化和更新流程是怎样的? || c2fa35e220674bb8a826ccfb4869d5e9
7647 || 已发布 || 19 | 插槽:如何实现内容分发? || 12c0b377f5ef4f95bbc95e80e324e72d
7648 || 已发布 || 20 | 指令:指令完整的生命周期是怎样的? || 4425744f4e724402ab9aa10d26575cc3
注意:在resolve函数实现的过程中,它会先根据name 匹配如果失败则把name变成驼峰格式继续匹配,还匹配不到则把name首字母大写后继续匹配
mounted 钩子函数用queuePostRenderEffect包一层执行
了解指令是如何定义、如何注册,以及如何应用的指令提供了在一个元素的生命周期中注入代码的途径
7649 || 已发布 || 21 | v-model:双向绑定到底是怎么实现的? || 50a9fb18a6e041708c8c91e21b84a93f
Vue.js里有内置的双向绑定的实现吗?有的,v-model指令就是一种双向绑定的实现在平时项目开发中,也经常会使用v-model
v-model 特定的表单标签自定义组件extareaselectinput
const fn = vnode.props['onUpdate:modelValue']
·如果配置了lazy修饰符,那么监听的是input 的change 事件·如果不配置lazy,监听的是input的input事件多监听 compositionstart和compositionend事件
app.component('custom-input',{props: ['modelValue'],template:<input v-model="value">computed:{value:{get(){return this.modelValue1,set(value){this.$emit('update:modelValue', value)
v-model实际上就是一种打通数据双向通讯的语法糖,即外部可以往组件上传递数据组件内部经过某些操作行为修改了数据,然后把更改后的数据再回传到外部
7654 || 已发布 || 模块五导读 | 内置组件:学习 Vue 内置组件的实现原理 || 3750e04f05d54c56a9c357916beb0e24
Vue.js除了核心的组件化和响应式之外提供了很多好用的内置组件辅助我们的开发极大地丰富了Vue.js的能力
7651 || 已发布 || 22 | Teleport 组件:如何脱离当前组件渲染子组件? || e5f5bebcc3ff4e6cab3a85bae6560c1a
7652 || 已发布 || 23 | KeepAlive 组件:如何让组件在内存中缓存和调度? || 5c8b038f85914e47b306a820680b6b5d
组件的递归 patch 过程,主要就是为了渲染 DOM
keys.add(key)
//删除最久不用的 key,符合 LRU 思想
neys.duu(hey)
//删除最久不用的 key,符合 LRU 思想
KeepAlive 实际上是一个抽象节点,渲染的是它的第一个子节点了解它的缓存设计、Props设计和卸载过程
7653 || 已发布 || 24 | Transition 组件:过渡动画的实现原理是怎样的?(上) || 942da3bc5d414a758b7fbd132d504e99
Transition组件的核心思想Transition 组件过渡动画的触发条件:条件渲染(使用v-if)条件展示(使用v-show)动态组件组件根节点
v-enter-fromv-leave-fromv-leave-tov-enter-to
v-leave-activev-enter-active
沉积 kp
组件的渲染拉勾教育互联网人实战大学判断是否是一次更新渲染Transition组件resolveTransitionHookssetTransitionHooks 函数Transition 渲染的是组件嵌套的第一个子元素节点
Transition组件的核心思想当Transition包裹的元素插入删除时,在适当的时机插入这些CSS样式
7655 || 已发布 || 25 | Transition 组件:过渡动画的实现原理是怎样的?(下) || 8470b47e9d85407a9d7d8945220ed550
Transition组件组件的渲染、钩子函数的执行、模式的应用钩子函数的执行
区别在in-out模式下,新元素先进行过渡,完成之后当前元素过渡离开在out-in模式下,当前元素先进行过渡,完成之后新元素过渡进入
7656 || 已发布 || 特别放送导读 | 研究 Vue 官方生态的实现原理 || 790650d6ad2d4e22ac8d5f757916e537
7657 || 已发布 || 26 | Vue Router:如何实现一个前端路由?(上) || 09d59b0301af4fc49d225dfc9f01e06b
Web前端单页应用SPAURL与视图之间的映射关系URL变化会引起视图的更新
前端路由的好处无须刷新页面减轻了服务器的压力提升了用户体验Vue Router的实现原理
路由的实现原理路由的基础结构就是一个路径对应一种视图当我们切换路径的时候对应的视图也会切换对路径的管理
7658 || 已发布 || 27 | Vue Router:如何实现一个前端路由?(下) || 5947af449dee4182b59656b71626d685
RouterView的渲染的路由组件和当前路径currentRoute 的matched对象相关和 RouterView 自身的嵌套层级相关
路径和路由组件的渲染的映射什么情况下会有parent matcher 呢?在创建了 matcher对象后,接着判断 record中是否有children 属性如果有则遍历children,递归执行 addRoute方法添加路径并把创建matcher作为第二个参数parent 传入
经过 Promise 化后添加到guards数组中通过 runGuards 以及 Promise 的方式链式调用依次顺序执行这些导航守卫
7659 || 已发布 || 结束语 | 终点也是起点 || 36bceb740eee4d5ab769fc51e6129b33
我最得意的一个学生
他学习了Vue.js源码,深入了解了Webpack的源码负责BetterScroll 2.0重构,现在已经升级到了D7
终点也是起点
请你不要放弃源码学习!
建议你多看几遍课程,动手去写Demo,去用debugger单步调试,也可以给我留言
组件库zoom-ui是fork了ElementUI在它的基础上做了全面的重构和组件增强Vue-csp版本是fork了Vue.js 2.6.11版本在它的基础上修改了编译过程
调试源码....
Vue.js 3.0参考了很多优秀的开源实现比如reactivity库参考了observer-util,patch的实现参考了 inferno

88633/202412/1388633-20241230222227308-647891456.png)










学习没有捷径!要能够直面困难和挫折,敢于跳出自己的舒适区追求进步能熬得住突破瓶颈长时间的寂寞

浙公网安备 33010602011771号