《Vue.js 设计与实现》阅读笔记

目录

一、框架设计概览

第一章:权衡的艺术

①命令式和声明式

②性能与可维护性的权衡

③运行时和编译时

第二章:框架设计的核心要素

①控制代码体积

②输出不同的构建产物

③错误提示与处理

④提升用户的开发体验

⑤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以及同构渲染



学习内容:

  1. 框架设计概览
  2. 响应式系统
  3. 渲染器
  4. 组件化
  5. 编译器
  6. 服务端渲染

这篇文章为观看这个博主笔记,详情请点击链接查看!

一小时读完《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') }) // 定点击事

 声明式:关注结果

<div @click=" => alert('ok')">hello world</div>

vue封装了命令式的过程,对外暴露出了声明式的结果


②性能与可维护性的权衡

1️⃣命令式与声明式

  • 命令式:直接修改的性能消耗
  • 声明式:找出差异 +直接修改

虽然命令式的性能>声明式的性能,但是声明式的可维护性 > 命令式的可维护性。

2️⃣原生 JavaScript、innerHTML、虚拟 DOM

  • 心智负担:虚拟 DOM < innerHTML < 原生JavaScript
  • 性能:innerHTML < 虚拟 DOM < 原生JavaScript
  • 可维护性:原生 JavaScript  < innerHTM < 虚拟 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 函数进行调用和异常处理的。该函数提供了非常方便的机制来统一处理异常情况,并且可以极大地提高代码的容错性和可维护性。

④提升用户的开发体验

  • 心智负担低
  • 便于调试

⑤TypeScript良好的可维护性

  • 框架本身的可维护性: 使用 TS编写
  • 对TS的友好支持:大量的类型判断与处理


第三章:Vuejs3的设计思路

①描述UI的形式

声明式的模板描述

<div :id="tId" :class="{ tClass: true}" @click="onTClick"></div>


命令式的 render 函数

import { h } from 'vue'
export default {
    render() {
        return h('h1', onClick: handler }) // 虚拟 DOM
    }
}


②初始渲染器

本质:函数 createRenderer 的返回值 (renderer 对象)

renderer 对象:包含 render 渲染函数

  • 参数:
    • vnode: 虚拟 DOM 对象
    • container: 挂载的容器,真实 DOM 元素
  • 作用:
    • 把 vnode挂载到 container上

③组件的本质

一组 DOM 元素的封装:一个JavaScript 对象 (vnode) ,内部封装了DOM 元素。


④模板 (template)的工作原理


二、响应式系统

第四章:响应系统的作用与实现

(1)副作用函数与响应式数据

1️⃣副作用函数:会产生副作用的函数

//全局变量
let val = 1
function effect() {
    val = 2 // 修改全局变量,产生副作
}


2️⃣响应式数据:会导致视图变化的数据


(2)响应式数据的实现

1️⃣核心逻辑

数据读取: getter 行为

document .body.innerText = obj.text

数据修改: setter 行为

obj.text ='hello vue3'

核心API

  • vue2:Object.defineProperty
  • vue3:Proxy

2️⃣实现逻辑图示 

1.getter 行为

 2.setter 行为

 (3)构建完善的响应式系统

(4)调度系统 (scheduler)

1️⃣响应性的可调度性

当数据更新的动作,触发副作用函数重新执行时,有能力决定:副作用函数 (effet) 执行的时机、次数以及方式。

调度系统:有能力调整输出顺序

 2️⃣实现原理

  • 异步: Promise
  • 队列: jobQueue

基于 Set 构建了队列jobQueue,利用Promise 的异步特性,控制执行顺序。


(5)计算属性 (computed)

本质:一个属性值,当依赖的响应式数据发生变化时重新计算。

计算属性的实现原理 <——依赖调度系统。

(6)watch 监听器的实现原理

本质:观测一个响应式数据,当数据发生变化时,通知并执行相应的回调函数。

实现原理<—— 调度系统 (scheduler)、惰性执行 (lazy)。


(7)惰性执行(lazy)

if (!lazy) {
    // 执行副作用函数
}

原理:boolean 型的值,可以被添加到 effect 函数中,用来控制副作用的执行。


(8)过期的副作用

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<T> {
    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)渲染器与渲染函数

  • 渲染器: renderer
  • 渲染函数: render
function createRenderer(){
    function render(vnode,container){
        //···
    }
    function hydrate(vnode,container) {
        //···
    }
    return{ //渲染器
        render, //渲染函数
        hydrate
    }
}
  1. renderer: createRenderer 的返回值
  2. render: createRenderer 中的 render 函数

(2)自定义渲染器核心思路

// implementation
function baseCreateRenderer{···}
  1. 利用 DOM API 完成 DOM 操作在浏览器端,
  2. 渲染器不能与宿主环境 (浏览器) 产生强耦合

(3)vnode
普通的JavaScript 对象,代表了渲染的内容。

// 该 vnode 用来描述普通标签
const vnode = {
    type:'div'
    //···
}


// 该 vnode 用来描述片段
const vnode = {
    type: Fragment
    //···
}


//该 vnode 用来描述文本节点
const vnode = {
    type: Text
    //···
}

第八章:挂载与更新

①DOM节点操作

挂载:DOM的初次渲染

  • 通过 createElement 新建
  • 通过 parentEl.insertBefore 插入

更新:当响应性数据发生变化时,可能会涉及到 DOM更新
属性更新 <—— 属性节点操作

卸载:该节点不在被需要了
通过 parentEl.removeChild 完成。

②属性节点操作

1.属性

1️⃣HTML Attributes:定义在HTML标签上的属性

<input id="my-input" type="text" value="foo" />

2️⃣DOM Properties:DOM 对象下的属性 ——> 正确的设置元素属性

const el = document.querySelector('#my-input')

2.正确的设置元素属性

1️⃣el.setAttribute('属性名','属性值')

2️⃣ .属性赋值

  • el.属性名属性值
  • el[属性名]=属性值
// 初始状态: <textarea class="test-class"type="text"></textarea>

// 获取 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️⃣事件

  • 添加  el.addEventListener
  • 删除  el.removeEventListener
  • 更新  vei (vue event invokers)
    • 为 addEventListener 回调函数,设置了一个value的属性方法,在回调函数中触发这个方法。通过更新该属性方法的形式,达到更新事件的目的。


第九、十、十一章: Diff 算法

①本质

一个对比的方法
“|日DOM组”更新为“新 DOM组”时,如何更新才能效率更高。

②步骤

  1. sync from start: 自前向后的对比
  2. sync from end:自后向前的对比
  3. common sequence+mount: 新节点多于旧节点,需要挂载
  4. common sequence +unmount: 日节点多于新节点,需要卸载
  5. unknown sequence: 乱序

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函数:<script setup> 只是一个语法糖

  • 返回一个函数:直接作为 render函数渲染
  • 返回一个对象:直接在render中访问暴露出来的对象数据

5️⃣插槽的工作原理与实现:

  • 插槽的本质:组件中innerHTML的内容,在vnode 中以children 属性呈现
  • 原理:针对 children 进行渲染即可

第十三章:异步组件与函数式组件

1️⃣异步组件

定义:异步加载的组件

<template>
    <CompA />
    <component:is="asyncComp" />
</template>
<script>
import {shallowRef} from 'vue'
import CompA from 'CompA.vue'
export default {
    components: { CompA },
    setup(){
        const asyncComp = shallowRef(null)
        //异步加载CompB组件
        import('CompB.vue').then(CompB => asyncComp.value = CompB)
        return {
            asyncComp
        }
    }
}
</script>

作用:页面性能、拆包、服务端下发组件。

2️⃣函数式组件

没有状态的组件。本质上是一个函数,通过静态属性的形式添加props 属性。


第十四章:内建组件和模块

1️⃣KeepAlive

作用:缓存一个组件,避免不断地销毁和创建

核心原理:

(1)组件被卸载时:把组件保存在一个容器中

(2)组件被挂载时:从容器中把组件取出来

2️⃣Teleport

作用:将插槽的内容渲染到其他位置

核心原理:

(1)把Teleport 组件的染逻辑,从渲染器中抽离

(2)在指定的位置进行独立渲染

3️⃣Transition

作用:实现动画逻辑

核心原理:

(1)DOM 元素被挂载时,将动效附加到该 DOM元素上。

(2)DOM 元素被卸载时,等在 DOM 元素动效执行完成后,执行卸载DOM 操作


五、编译器

第十五章:编译器核心技术概述

1️⃣模板 DSL的编译器

DSL:一种领域下,特定语言的编译器

本质:一段程序,可以把A 语言翻译成 B 语言。把 tempalte 模板,编译成 render 渲染函数

编译流程:

1.完整的编译流程

2.Vue的编译流程

2️⃣Vue 编译流程三大步

1.parse:通过parse函数,把模板编译成AST对象
2.transform:通过transform 函数,把AST转化为 JavaScript AST
3.generate:通过 generate 函数,把JavaScript AST转化为 染函数(render)

第十六章:解析器 (parse)

第十七章:编译优化

概念:通过编译的手段提取关键信息,并以此知道生成最优代码的过程
核心:

  • 动态节点受数据变化所影响的)<——Block 树
  • 静态节点(不受数据变化所影响的)

Block 树
本质:虚拟节点树对象
核心:dynamicChildren  收集所有的动态子节点。

其他优化:

静态提升
预字符串化
缓存内联事件处理函数
V-once 指令


六、服务端渲染

第十八章:同构渲染

CSR、SSR以及同构渲染

 CSR:客户端渲染:

SSR:服务端渲染

同构渲染

  • 单独的 CSR
  • 单独的SSR
  • CSR +SSR
    • 首次渲染
    • 非首次染

服务端渲染,将虚拟 DOM 染为 HTML 字符串:解析 vnode,进行字符串拼接,然后返回给客户端
服务端渲染,将组件 渲染为 HTML字符串
客户端激活的原理

  1. 为页面中的 DOM 元素与虚拟节点对象之间建立联系
  2. 为页面中的DOM元素添加事件绑定

renderer.hydrate()

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值