赞
踩
目录
学习内容:
这篇文章为观看这个博主笔记,详情请点击链接查看!
框架的设计,本身就是一种权衡的艺术。 —— 尤雨溪
命令式:关注过程
- 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
运行时: 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 函数进行调用和异常处理的。该函数提供了非常方便的机制来统一处理异常情况,并且可以极大地提高代码的容错性和可维护性。
声明式的模板描述
<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 渲染函数
一组 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️⃣实现逻辑图示
1.getter 行为
2.setter 行为
1️⃣响应性的可调度性
当数据更新的动作,触发副作用函数重新执行时,有能力决定:副作用函数 (effet) 执行的时机、次数以及方式。
调度系统:有能力调整输出顺序
2️⃣实现原理
基于 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<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)渲染器与渲染函数
- 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标签上的属性
<input id="my-input" type="text" value="foo" />
2️⃣DOM Properties:DOM 对象下的属性 ——> 正确的设置元素属性
const el = document.querySelector('#my-input')
2.正确的设置元素属性
1️⃣el.setAttribute('属性名','属性值')
2️⃣ .属性赋值
- // 初始状态: <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️⃣事件
- 为 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函数:<script setup> 只是一个语法糖
5️⃣插槽的工作原理与实现:
定义:异步加载的组件
- <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>
作用:页面性能、拆包、服务端下发组件。
没有状态的组件。本质上是一个函数,通过静态属性的形式添加props 属性。
作用:缓存一个组件,避免不断地销毁和创建
核心原理:
(1)组件被卸载时:把组件保存在一个容器中
(2)组件被挂载时:从容器中把组件取出来
作用:将插槽的内容渲染到其他位置
核心原理:
(1)把Teleport 组件的染逻辑,从渲染器中抽离
(2)在指定的位置进行独立渲染
作用:实现动画逻辑。
核心原理:
(1)DOM 元素被挂载时,将动效附加到该 DOM元素上。
(2)DOM 元素被卸载时,等在 DOM 元素动效执行完成后,执行卸载DOM 操作
DSL:一种领域下,特定语言的编译器
本质:一段程序,可以把A 语言翻译成 B 语言。把 tempalte 模板,编译成 render 渲染函数
编译流程:
1.完整的编译流程
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()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。