《vue.js设计与实现》

56 阅读6分钟

🖊 人不光是靠他生来就拥有一切,而是靠他从学习中所得到的一切来造就自己。 —— 歌德

视图层:

视图层分为命令式和声明式,声明式代码的性能不优于命令式代码的性能

  • 命令式

    • 注重过程
    • 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/进行注释)

错误处理

错误处理可以决定用户应用程序的健壮性.

  1. 我们可以将错误处理的程序封装成一个函数,假设叫它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、当设置操作发生时,从桶中取出副作用函数并执行.



附录:

[1]juejin.cn/post/720245…