Vue设计思想

Virtual DOM Tree

虚拟DOM为前端框架带来了跨平台的能力,是一层对真实DOM的抽象

在JS中,虚拟DOM是一个Object对象,并且至少包含tag,attrs和children三个属性,不同的框架对这三个属性的命名可能会有差别

通过VNode,Vue可以对抽象树进行创建节点,删除节点以及修改节点,经过diff算法得出一些需要修改的最小单位,再更新视图,减少了DOM操作,提高了性能

性能问题

一个真实的DOM节点,哪怕是一个最简单的div也包含很多属性,操作DOM的代价是昂贵的,频繁操作还是会出现页面卡顿,影响用户体验

举个例子

用传统的原生API或jQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程

当你在一次操作时,需要更新10个DOM节点,浏览器收到第一个更新DOM请求后,并不知道后续还有9个过呢更新操作,因此会马上执行流程,最终执行10次流程

而通过VNode,同样更新10个DOM节点,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,避免了大量的无谓计算

双向数据绑定

采用数据劫持,发布者订阅者模式,通过Proxy来劫持元素各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调

  • Observer观测需要被观测的对象,其数据对象被Vue递归遍历,包括子属性对象的属性,都加上setter和getter。那么对这个对象的某个值赋值,就会触发setter,就能监听到数据变化
  • Compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
  • Watcher,订阅者,是Observer和Compile之间的通信桥梁,主要工作:
    • 在自身实例化时,往属性订阅dep中添加自己
    • 自身需要有一个update()方法
    • 等到属性发生变动,dep.notice()通知时,能调用自己的update()方法,并触发Compile中的回调

MVVM作为数据绑定的入口,整合Observer,Compile和Watcher,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令(whoseJam:相当于SD中的Rules),利用Watcher搭建起Observer和Compile的通信,最终实现:数据变化->视图更新视图交互变化->数据更新

eb15ed6582a24138ba151f027f2d537

模板编译

  • 解析:Vue的模板编译器将模板字符串解析成抽象语法树,树中的每个节点描述了模板中的各种元素、指令、属性等信息
  • 静态分析:对AST进行静态分析,收集模板中的指令、属性等静态信息
  • 优化
  • 代码生成:将优化后的AST转换为可执行的渲染函数。渲染函数是一个普通的js函数,它接受数据作为参数,返回虚拟DOM树

slot

默认插槽

//子组件 (假设名为ebutton)
<template>
  <div class= 'button'>
      <button></button>
      <slot>SOME TEXT会显示在此处</slot>
  </div>
</template>

//父组件 (引用子组件ebutton)
<template>
  <div class= 'app'>
     <ebutton>SOME TEXT</ebutton>
  </div>
</template>

具名插槽

//子组件 (假设名为ebutton)
<template>
  <div class= 'button'>
      <button>  </button>
      <slot name= 'one'> 这就是默认值1</slot>
      <slot name='two'> 这就是默认值2 </slot>
      <slot name='three'> 这就是默认值3 </slot>
  </div>
</template>

//父组件 (引用子组件ebutton)
<template>
  <div class= 'app'>
     <ebutton> 
        <template v-slot:one> 这是插入到one插槽的内容 </template>
        <template v-slot:two> 这是插入到two插槽的内容 </template>
        <template v-slot:three> 这是插入到three插槽的内容 </template>
     </ebutton>
  </div>
</template>

$nextTick

在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,获取更新后的DOM

  • Vue更新DOM是有策略的,不是同步更新
  • nextTick以一个函数作为入参
  • nextTick后能拿到最新的数据

JS执行机制

同步与异步

  • 同步:在主线程上排队执行的任务,只有一个任务执行完毕,才能执行后一个任务
  • 异步:不进入主线程,而进入任务队列,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程中执行

机制

  • 所有同步任务都在主线程上执行,形成一个执行栈(Execution Context Stack)
  • 主线程之外,还存在一个任务队列(Task Queue)。只要异步任务有了运行结果,就在任务队列里面放置一个事件
  • 一旦执行栈中的所有同步任务都执行完毕,系统就会读取任务队列,看看里面有哪些事件,那些对应的异步任务,就会结束等待状态,进入执行栈,开始执行

image.png

nextTick实现

实现很简单,完全基于语言执行机制实现,直接创建一个异步任务,那么nextTick自然就达到在同步任务后执行的目的

const p = Promise.resolve();
export function nextTick(fn?: () => void): Promise<void> {
    return fn ? p.then(fn) : p;
}

数据修改过后不会立即更新的原因:有一个queueJob维护了所有effect,之后还需要对这些effect进行去重,减少计算量,等所有同步任务完成之后,再用nextTick来施加影响

单页面应用和多页面应用

SPA,SinglePage Aplication,只有一个主页,一开始只需要加载一次js,css等相关资源,所有内容都包含在主页面里,对每一个功能模块组件化。单页面应用跳转,就是切换相关组件,仅仅刷新局部资源

MPA,MultiPage Application,指有多个独立页面的应用,每个页面必须重复加载js,css等相关资源,多页面应用的跳转,需要整页资源刷新