🖊 人不光是靠他生来就拥有一切,而是靠他从学习中所得到的一切来造就自己。 —— 歌德
视图层:
视图层分为命令式和声明式,声明式代码的性能不优于命令式代码的性能
-
命令式
- 注重过程
- vue内部采用
-
声明式
- 注重结果
- 更好的维护
- vue暴露给用户
- 声明式代码的更新性能消耗=找出差异的性能消耗+修改的性能消耗
虚拟dom
使用js对象来描述真实的dom结构
实现:
暂时无法在飞书文档外展示此内容
渲染器:
实现思路:
1、创建元素
2、为元素添加属性和事件
3、处理children
render函数中接受两个参数:
- vnode:虚拟dom
- container:一个真实dom元素,作为挂载点,渲染器会把虚拟dom渲染到该挂载点下.
性能:
为了最小化找出差异这一步的性能消耗而出现的.
性能差 性能高
Innerhtml < 虚拟dom < 原生javascript
心智负担中等 心智负担小 心智负担大
性能差 可维护性强 可维护性弱
性能不错 性能高
Diff算法
当新旧虚拟dom的直接点都是一组节点时,为了以最小的性能开销完成更新的操作,需要比较两组子节点,用于比较的算法就叫做diff算法
why?
虚拟dom:使用js对象来描述真实的dom结构
How?
- 简单diff算法(复用-react)
- 双端diff算法(vue2)
- 快速diff算法(动态规划「最长子序列」vue3)
- 简单diff算法(复用-react)
第一步:拿到新的一组子节点中去旧的一组直接点中寻找可以复用的节点,如果找到了,则记录该节点的位置索引,该索引称为最大索引「优化:遍历新旧两组子节点中数量较少的一组,并通过patch函数进行打补丁」
第二步:在整个更新的过程中,如果一个节点的索引值小于最大索引值,则说明该节点对应的真实dom元素需要移动「原理:使用的key属性找到可以复用的节点,通过dom移动操作来完成更新,避免过多的对dom元素进行销毁使用unmount进行卸载和新建使用insert函数进行处理」
- 双端diff算法(vue2)
第一步:使用4个指针[newStartIndex、newEndIndex、oldStartIndex、oldEndIndex]分别对新旧vnode进行控制,使其在4个端点进行比较,并试图找到可以复用的节点
第二步:若4个端点没有正确匹配,那么通过判断非头部,非尾部的节点是否可以复用,用新的子节点的头部节点去和旧的一组子节点中寻找,并将找到的索引值存储到变量的idxInOld中,
(1)如果idxInOld>0,则有可以复用的节点,将该节点对应的真实的dom移动到头部,且将oldChildren[idxInOld]设置为undefined.
(2)如果idxInOld不大于0,说明村子新的子节点.
第三步:比较完成的条件.
(1)newStartIndex>newEndIndex&&oldStartIndex>oldEndIndex 更新结束
(2)newStartIndex<=newEndIndex&&oldStartIndex>oldEndIndex 存在新节点
(3)newStartIndex>newEndIndex&&oldStartIndex<=oldEndIndex. 存在需要移除的节点
框架体积的控制
tree-shaking
就是消除那些永远不会被执行的代码,也就是排除dead code.
如果一个函数调用会产生副作用,那么不能将其移除.(优化点:使用/#PURE/进行注释)
错误处理
错误处理可以决定用户应用程序的健壮性.
- 我们可以将错误处理的程序封装成一个函数,假设叫它callWithErrorHandling
//使用reigsterErrorHandler函数,用户可以使用它注册错误处理程序,然后在callWithErrorHandling函数内部捕获错误,
//把错误传递给用户注册的错误处理程序.
// 错误处理的能力完全由用户进行控制,用户即可以选择忽略错误,也可以调用上报程序将错误的信息上报给监控的系统.
export default{
foo(fn){
callWithErrorHandling(fn)
}
//用户可以调用该函数注册统一的错误处理函数
registerErrorHandler(fn){
handleError=fn
}
}
function callWithErrorHandling(fn){
try{
fn&&fn()
}catch(e){
handleError(e)
}
}
vue.js中我们也可以注册统一的错误处理函数
app.config.errorHandler=()=>{
}
良好的ts支持
ts是js的超集,优点:代码即文档,维护性更强.编辑器会自动提示,一定程度上避免了低级错误.
vue3设计思路
组件
组件就是一组dom元素的封装
渲染器
作用:把虚拟dom对象渲染为真实的dom元素
工作原理:递归遍历虚拟dom对象,并调用原生的dom Api来完成真实dom的创建
编辑器
将模版编辑成渲染函数
我们熟悉的.vue文件为例
Template标签的内容就是模版内容,编辑器会把模版内容编辑为渲染函数并添加到scrpit标签的组件对象上
模版的工作原理(也就是vue.js渲染页面的流程): 渲染内容最终都是通过渲染函数产生的,然后渲染器在把渲染函数返回的虚拟dom渲染为真实的dom
<template>
<div @click='handler'>
click me
</div>
</template>
<script>
export default{
data(),
methods:{
handler:()=>{}
}
}
</script>
响应系统的作用和实现
实时性:拦截一个对象属性的读取和设置操作
不同版本实现区别(都是采用 发布-订阅模式 ) 区别
vue2:采用的是object.defineProperty函数进行实现
通过更改默认的 getter/setter 函数,在 get 过程中收集依赖,在 set 过程中派发更新的。
vue3:采用的是代理对象proxy来实现.
目标对象的代理对象,拦截对原对象的所有操作;用户可以通过注册相应的拦截方法来实现对象操作时的自定义行为。
两者相比:
- 拦截操作更加多样
- 拦截定义更加直接
- 性能更加高效
Proxy 代理对象之后不仅可以拦截对象属性的读取、更新、方法调用之外,对整个对象的新增、删除、枚举等也能直接拦截,而 Object.defineProperty 只能针对对象的已知属性进行读取和更新的操作拦截
响应式设计
工作流程
1、当读取操作发生时,将副作用函数收集到‘桶’中,
2、当设置操作发生时,从桶中取出副作用函数并执行.
附录: