目录
一、框架设计概览
第一章:权衡的艺术
①命令式和声明式
②性能与可维护性的权衡
③运行时和编译时
第二章:框架设计的核心要素
①控制代码体积
②输出不同的构建产物
③错误提示与处理
④提升用户的开发体验
⑤TypeScript良好的可维护性
第三章:Vuejs3的设计思路
①描述UI的形式
②初始渲染器
③组件的本质
④模板 (template)的工作原理
二、响应式系统
第四章:响应系统的作用与实现
(1)副作用函数与响应式数据
(2)响应式数据的实现
(3)构建完善的响应式系统
(4)调度系统 (scheduler)
(5)计算属性 (computed)
(6)watch 监听器的实现原理
(7)惰性执行(lazy)
(8)过期的副作用
第五章:非原始值 (对象)的响应性方案
第六章:原始值(非对象) 的响应性方案
三、渲染器
第七章:渲染器的设计
渲染器的基本概念
第八章:挂载与更新
①DOM节点操作
②属性节点操作
第九、十、十一章: Diff 算法
①本质
②步骤
四、组件化
第十二章:组件的实现原理
第十三章:异步组件与函数式组件
1️⃣异步组件
2️⃣函数式组件
第十四章:内建组件和模块
1️⃣KeepAlive
2️⃣Teleport
3️⃣Transition
五、编译器
第十五章:编译器核心技术概述
1️⃣模板 DSL的编译器
2️⃣Vue 编译流程三大步
第十六章:解析器 (parse)
第十七章:编译优化
六、服务端渲染
第十八章:同构渲染
CSR、SSR以及同构渲染
学习内容:
这篇文章为观看这个博主笔记,详情请点击链接查看!
一小时读完《Vue.js 设计与实现》_哔哩哔哩_bilibili这是一本没有带你阅读一行源码,却可以让你在阅读完成之后,对 vue 3 所有的核心逻辑 了如指掌 的书籍。无论是 响应性、调度系统、惰性执行 ,还是 渲染器、diff 算法、编辑器三大步 ,甚至是 有限自动状态机 等所有你能想到知识,本书都可以给你答案。它就是 尤雨溪亲自做序 ,Vue 官方团队成员:霍春阳 编写的 Vue.js 设计与实现。, 视频播放量 84470、弹幕量 313、点赞数 3435、投硬币枚数 2250、收藏人数 10668、转发人数 531, 视频作者 LGD_Sunday, 作者简介 以每周一本的速度,产出《程序员专业技术书籍》讲解视频。前端专业技术知识、发展方向、面试等内容(不定期更新)。@黑马程序员-济南校区,相关视频:当你的隔壁住了一对情侣,北大韦神的发防爬虫技巧,公司月初进的实习生,面试时工资喊道12K,今天让他写个注册页,结果看到他直接打开网站抄,这我该怎么办啊,假如:a===1 && a===2 && a===3; 那么 a 是什么?,尤雨溪教你写vue 高级vue教程 源码分析 中文字幕翻译完毕,当你把js学到极致……,中国开发者海外受追捧,走近Vue作者尤雨溪的开挂人生,尤雨溪分享Vue的2023发展计划 --,【拷问】Vue和React对比,为什么大公司国外都好像更喜欢React?,10分钟写一个vue登陆页面https://www.bilibili.com/video/BV1K24y1q7eJ/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=4298755c4e2edc1fe805c41cc2d7379a
框架的设计,本身就是一种权衡的艺术。 —— 尤雨溪
命令式:关注过程
const div = document.querySelector('#app') // 获取 div
div.innerText = 'hello world' // 设置文本内容
div.addEventListener('click',() =>{ alert(ok') }) // 定点击事
声明式:关注结果
alert('ok')">hello world
vue封装了命令式的过程,对外暴露出了声明式的结果
1️⃣命令式与声明式
虽然命令式的性能>声明式的性能,但是声明式的可维护性 > 命令式的可维护性。
2️⃣原生 JavaScript、innerHTML、虚拟 DOM
运行时: runtime:
利用 render函数 直接把 虚拟 DOM 转化为,真实 DOM 元素。
没有编译过程,无法分析用户提供的内容。
编译时: compiler:
直接把 template 模板中的内容,转化为 真实DOM 元素。
可以分析用户提供的内容,没有运行时理论上性能会更好。(如:Svelte)
运行时+编译时:
(1)先把 template 模板转化为 render 函数;
(2)再利用 render 函数,把 虚拟 DOM 转化为真实 DOM
编译时:分析用户提供的内容。运行时:提供足够的灵活性(如:Vue)
环境变量 DEV
DEV是一个常见的环境变量,在开发过程中经常被用到。该环境变量通常表示“development”(开发)的缩写,用来指定当前运行的环境是开发环境。通过在系统中设置DEV环境变量,可以让我们的程序知道当前运行的是开发环境,从而进行相应的处理,例如输出更详细的日志信息、打开调试模式等。在实际开发中,我们可以根据需要自定义DEV环境变量的值,比如将其设置为“test”表示当前运行的是测试环境。
Tree-Shaking ESM (ES Module)
Tree-shaking 是一种通过静态分析去除未使用代码的优化技术,一般用于 JavaScript 应用程序中,以减少应用程序的体积和加载时间。它是通过分析代码中哪些函数、变量被真正使用了来实现的,并将未使用的代码从最终打包好的文件中剔除掉,从而减小文件大小,提高网页加载性能。
ESM(ES Modules)则是 ECMAScript 6 规范中新增的模块化方案,它允许 JavaScript 代码以模块的方式组织并导出、导入变量、函数等,可以有效地解决传统 JavaScript 中命名冲突、模块依赖等问题。在使用 ESM 的过程中,由于每个模块都可以单独解析和运行,可以使用 Tree-Shaking 技术去除未使用的代码,从而使得最终生成的代码更加轻量级。这也是 ESM 和传统的 CommonJS 等模块化方案相比的一个重要优势。大多数现代 JavaScript 库和框架都已经支持使用 ESM 进行模块化开发。
packages/vue/dist
统一的错误处理接口: callWithErrorHandling
callWithErrorHandling 函数是在 Vue.js 源码中的一个重要函数,主要用于执行一个函数并捕获其中可能抛出的异常或错误,以便更好地处理异常和错误情况。
该函数通常接收两个参数:第一个参数是要执行的函数,第二个参数为可选项的上下文对象,即函数执行上下文。在调用传入的函数时,callWithErrorHandling 函数会尝试捕获其中抛出的任何异常,如果有异常则会将其交给全局的 Vue 错误处理函数进行处理,同时会在控制台打印出错误信息以便开发者参考。如果执行函数过程中没有异常,则该函数会返回函数执行的结果。
在 Vue.js 中,callWithErrorHandling 函数被广泛地用于处理各种可能抛出异常的情况,例如组件的生命周期函数、指令的 bind 和 update 函数、渲染器的 render 函数等,都是通过 callWithErrorHandling 函数进行调用和异常处理的。该函数提供了非常方便的机制来统一处理异常情况,并且可以极大地提高代码的容错性和可维护性。
声明式的模板描述
命令式的 render 函数
import { h } from 'vue'
export default {
render() {
return h('h1', onClick: handler }) // 虚拟 DOM
}
}
本质:函数 createRenderer 的返回值 (renderer 对象)
renderer 对象:包含 render 渲染函数
一组 DOM 元素的封装:一个JavaScript 对象 (vnode) ,内部封装了DOM 元素。
1️⃣副作用函数:会产生副作用的函数
//全局变量
let val = 1
function effect() {
val = 2 // 修改全局变量,产生副作
}
2️⃣响应式数据:会导致视图变化的数据
1️⃣核心逻辑
数据读取: getter 行为
document .body.innerText = obj.text
数据修改: setter 行为
obj.text ='hello vue3'
核心API
2️⃣实现逻辑图示
2.setter 行为
1️⃣响应性的可调度性
当数据更新的动作,触发副作用函数重新执行时,有能力决定:副作用函数 (effet) 执行的时机、次数以及方式。
调度系统:有能力调整输出顺序
基于 Set 构建了队列jobQueue,利用Promise 的异步特性,控制执行顺序。
本质:一个属性值,当依赖的响应式数据发生变化时重新计算。
计算属性的实现原理 <——依赖调度系统。
本质:观测一个响应式数据,当数据发生变化时,通知并执行相应的回调函数。
实现原理<—— 调度系统 (scheduler)、惰性执行 (lazy)。
if (!lazy) {
// 执行副作用函数
}
原理:boolean 型的值,可以被添加到 effect 函数中,用来控制副作用的执行。
1️⃣竞态问题 <—— 大量的异步操作
概念:在描述一个系统或者进程的输出,依赖于不受控制的事件出现顺序或者出现时机
let finalData
watch(obj,async () => {
// 发送并等待网络请求
const res = await fetch('/path/to/request')
// 将请求结果赋值给 data
finalData = res
})
2️⃣解决方式:onInvalidate: 该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。
watch(obj,async (newValue, oldValue,onInvalidatel)=>{
//定义一个标志,代表当前副作用函数是否过期,代表没有过期默认为 false
let expired = false
// 调用 onInvalidateC) 函数注册一个过期回调
onInvalidate(() => {
// 当过期时,将 expired 设置为 true
expired = true
})
// 发送网络请求
const res = await fetch('/path/to/request')
//只有当该副作用函数的执行没有过期时,才会执行后续操作
if (!expired) {
finalData = res
}
})
3️⃣onInvalidate 原理
副作用函数 (effct) 重新执行前,先触发onInvalidate。
1️⃣Proxy
代理一个对象(被代理对象)的 getter 和setter 行为,得到一个 proxy 实例 (代理对象)。
2️⃣Reflect
在Proxy 中使用 this 时,保证 this 指向proxy,从而正确执行次数的副作用。
1️⃣为什么会有 ref
1.reactive 方法基于Proxy 实现,所以只能完成对象的响应性
2.针对非对象的响应性,则需要 ref 构建
2️⃣实现原理
注意:针对于最新的 vue 3.2 而言,书中在《6.1引入ref的概念》中所讲解的 ref 实现原理存在落后性”。 vue 3.2 已经修改了 ref 的实现,这得益于@basvanmeurs 的贡献。
通过 get 、set 函数标记符,让函数以属性调用的形式被触发。
class RefImpl {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly _v_isShallow: boolean){}
get value(){}
set value(newVal){}
}
packages/reactivity/src/ref.ts
当访问 ref.value 属性时,本质上是 value()函数 的执行。
(1)渲染器与渲染函数
function createRenderer(){
function render(vnode,container){
//···
}
function hydrate(vnode,container) {
//···
}
return{ //渲染器
render, //渲染函数
hydrate
}
}
(2)自定义渲染器核心思路
// implementation
function baseCreateRenderer{···}
(3)vnode
普通的JavaScript 对象,代表了渲染的内容。
// 该 vnode 用来描述普通标签
const vnode = {
type:'div'
//···
}
// 该 vnode 用来描述片段
const vnode = {
type: Fragment
//···
}
//该 vnode 用来描述文本节点
const vnode = {
type: Text
//···
}
挂载:DOM的初次渲染
更新:当响应性数据发生变化时,可能会涉及到 DOM更新
属性更新 <—— 属性节点操作
卸载:该节点不在被需要了
通过 parentEl.removeChild 完成。
1.属性
1️⃣HTML Attributes:定义在HTML标签上的属性
2️⃣DOM Properties:DOM 对象下的属性 ——> 正确的设置元素属性
const el = document.querySelector('#my-input')
2.正确的设置元素属性
1️⃣el.setAttribute('属性名','属性值')
2️⃣ .属性赋值
// 初始状态:
// 获取 dom 实例
const el = document.querySelector('textarea')
// 1: 修改 class
el.setAttribute('class','m-class') // 成功
el['cLass'] ='m-class' // 失败
el.className ='m-class' // 成功
// 2: 修改 type
el.setAttribute('type','input') // 成功
el['type'] = 'input' // 失败
// 3: 修改 value
el.setAttribute('value','你好 世界') // 失败
el['value'] ='你好 世界'// 成功
3️⃣事件
- 为 addEventListener 回调函数,设置了一个value的属性方法,在回调函数中触发这个方法。通过更新该属性方法的形式,达到更新事件的目的。
一个对比的方法
“|日DOM组”更新为“新 DOM组”时,如何更新才能效率更高。
packages/runtime-core/src/renderer.ts
1️⃣组件对象
// MyComponent 是一个组件,它的值是一个选项对象
const MyComponent = {
name: 'MyComponent',
data() {
return { foo: 1 }
}
}
2️⃣组件的 vnode:type 为组件对象的 vnode
//该 vnode 用来描述组件,type 属性存储组件的选项对象
const vnode = [
type: MyCoponent
//···
}
3️⃣组件的渲染:组件对象中会包含一个 render 函数,render函数返回值时一个 vnode。渲染组件就是渲染该vnode
const MyComponent ={
//组件名称,可选
name: 'MyComponent',
// 组件的渲染函数,其返回值必须为虚拟 DOM
render(){
//返回虚拟 DOM
return {
type:'div',
children:我是文本内容
//通过渲染器实现
}
}
}
4️⃣setup函数:
作用:页面性能、拆包、服务端下发组件。
没有状态的组件。本质上是一个函数,通过静态属性的形式添加props 属性。
作用:缓存一个组件,避免不断地销毁和创建
核心原理:
(1)组件被卸载时:把组件保存在一个容器中
(2)组件被挂载时:从容器中把组件取出来
作用:将插槽的内容渲染到其他位置
核心原理:
(1)把Teleport 组件的染逻辑,从渲染器中抽离
(2)在指定的位置进行独立渲染
作用:实现动画逻辑。
核心原理:
(1)DOM 元素被挂载时,将动效附加到该 DOM元素上。
(2)DOM 元素被卸载时,等在 DOM 元素动效执行完成后,执行卸载DOM 操作
DSL:一种领域下,特定语言的编译器
本质:一段程序,可以把A 语言翻译成 B 语言。把 tempalte 模板,编译成 render 渲染函数
编译流程:
2.Vue的编译流程
1.parse:通过parse函数,把模板编译成AST对象
2.transform:通过transform 函数,把AST转化为 JavaScript AST
3.generate:通过 generate 函数,把JavaScript AST转化为 染函数(render)
概念:通过编译的手段提取关键信息,并以此知道生成最优代码的过程
核心:
Block 树
本质:虚拟节点树对象
核心:dynamicChildren 收集所有的动态子节点。
其他优化:
静态提升
预字符串化
缓存内联事件处理函数
V-once 指令
CSR:客户端渲染:
SSR:服务端渲染
同构渲染
服务端渲染,将虚拟 DOM 染为 HTML 字符串:解析 vnode,进行字符串拼接,然后返回给客户端
服务端渲染,将组件 渲染为 HTML字符串
客户端激活的原理
renderer.hydrate()