模板的工作原理
1 | 渲染器: 把虚拟DOM渲染为真实的DOM |
响应式系统的作用与实现
1 | 响应式数据: 当值变化后, 副作用函数自动重新执行 |
分支切换和 cleanup
1 | 每次副作用函数执行时, 我们可以先把它从所有与之相关联的依赖集合中删除, 当副作用函数执行完毕后, 会重新建立联系, 在新的联系中, 不会包含遗留的副作用函数 |
嵌套的 effect 与 effect 栈
1 | 我们用全局变量activeEffect来存储effect函数注册的副作用函数, 这意味着同一时刻, activeEffect所存储的副作用函数只能有一个, 当副作用函数发生嵌套时, 内层副作用函数的执行会覆盖activeEffect的值, 并且永远不会恢复 |
避免无限递归循环
1 | effect(() => obj.foo++) // 既读取obj.foo的值, 又会设置obj.foo的值 |
调度执行
1 | 调度执行是指当trigger动作触发副作用函数重新执行时, 有能力决定副作用函数执行的时机, 次数, 以及方式 |
计算属性 computed 和 lazy
1 | export function effect(fn, options = {}) { |
watch 的实现原理
1 | 所谓watch本质就是观测一个响应式数据, 当数据发生变化时通知并执行响应的回调函数, 利用了effect以及options.scheduler选项 |
理解 Proxy 和 Reflect
1 | Proxy只能代理对象, 且只能拦截对一个对象的基本操作 |
代理 Object
1 | 对一个普通对象的所有可能的读取操作 |
只读与浅只读
1 | 如果一个数据是可读的 |
原始值的响应式方案
1 | js中的proxy无法提供对原始值得代理, 因此想要将原始值变成响应式数据, 就必须对其做一层包裹, 也就是ref |
渲染器与响应式系统的结合
1 | 渲染器不仅能够渲染真实的DOM, 它还是框架跨平台能力的关键 |
自定义渲染器
1 | createApp => renderer.createApp => createRenderer(options).createApp |
正确的设置元素属性
1 | HTML attributes的作用是设置与之对应的DOM properties的初始值 |
卸载操作
1 | 直接通过innerHTML = ‘’ 缺点 |
区分 vnode 类型
1 | 当vnode的type不同时, 卸载旧vnode, 挂载新的vnode |
事件的处理
1 | 绑定一个伪造的事件处理函数invoker, 然后把真正的事件处理函数设置为invoker.value属性的值, 这样当更新事件的时候, 我们将不再需要调用removeEventListener函数来移除上一次绑定的事件, 只需要更新invoker.value的值即可, 同时处理了事件与更新时间间的差异 |
更新子节点
1 | vnode.children的三种情况: 没有子节点, 文本子节点, 一组子节点 |
文本节点和注释节点
1 | 认为创建文本节点和注释节点 |
Fragment
1 | 渲染器渲染Fragment时, 只会渲染Fragment的子节点 |
简单 diff 算法
1 | 在进行对比时, 应该遍历新旧节点中长度较短的一组, 执行patch函数每项比对更新, 接着再对比新旧两组节点的长度, 如果新的一组节点更长, 则说明有新节点需要挂载, 否则说明有旧节点需要卸载 |
DOM 复用与 key 的作用
1 | 想要通过移动DOM移动完成更新, 必须要保证一个前提: 新旧两组节点中确实存在可以服用的节点 vnode.type && vnode.key |
找到需要移动的元素
1 | 当新旧两个子节点的节点顺序不变时, 不需要额外的移动操作 |
如何移动元素
1 | 要移动的并不是虚拟节点本身, 而是真实的DOM节点 => vnode.el |
新增节点
1 | 找到新增节点: 在旧节点组中没有对应的key |
移除不存在节点
1 | 当基本的更新结束时, 我们需要遍历一遍旧节点, 然后去新的节点中寻找具有相同key值的节点, 找不到则删除 |
双端 diff 算法
1 | 双端diff算法是一种同时对新旧两组节点的两个端点进行比较的算法 |
非理性情况
1 | 1, 2, 3, 4 |
快速 dif 算法
1 | 快速diff算法包含了预处理: 找出相同的前置元素和后置元素 |
判断是否需要进行 DOM 移动操作
1 | 1,2,3,4,6,5 |
如何移动元素
1 | lis函数: 最长递增子序列中的元素在source数组中的位置索引 |
渲染组件
1 | 判断组件: vnode.type === ‘object’ |
组件状态与自更新
1 | function mountComponent(vnode, container, anchor) { |
nextTick
1 | const queue = new Set() |
props 与组件的被动更新
1 | <MyComponent title=‘my title’ :other=‘val’ /> |
代理组件实例
1 | const renderContext = new Proxy(instance, { |
setup 函数的作用与实现
1 | setup函数只会在被挂载的时候执行一次, 它的返回值可以有两种情况 |
组件事件与 emit 的实现
1 | <MyComponent @change=“handler” /> |
插槽的工作原理与实现
1 | 当在父组件中使用插槽 |
注册生命周期
1 | currnetInstance |
异步组件与函数式组件
1 | 提供的能力 |
函数式组件
1 | 函数式组件没有自身状态, 可以接收外部传入的props |
keepAlive
1 | keep-alive的本质是缓存管理, 再加上特殊的挂载/卸载逻辑 |
模板 DSL 的编译器
1 | 源代码-词法分析-语法分析-语义分析-中间代码生成-优化-目标代码生成 |
tokenzie
1 | 正则表达式的本质就是有限自动机 |