内部原理及题目

# 内部原理及题目

# 组件通信

# 父子组件通信

  • props

  • event(emit)

  • style 和 class 在父组件中为子组件设置 style 和 class 属性 会合并到子组件的根元素

  • attributes 形式如 style 和 class 相同 为子组件设置 data-a 的属性 然后会放到子组件的根元素上 也可在子组件通过 this.$attrs 获取

    •  <!-- 父组件 -->
          <Hello data-a="aaa" data-b="bbb" />
      <!-- 生成的子组件  Hello-->
         <div data-a=“aaa” data-b="bbb">
      
         </div>
      
         <!-- 使用this.$attrs 获取到的数据-->
         {'data-a':'aaa','data-b':'bbb'}
      
  • native 修饰符

    • 将事件注册到子组件的根元素上 @click.native = "handleClick"
  • $listeners

  • v-model

  • $parents 和 $children

  • sync 修饰符

  • $slots 和 $scopedSlots

  • ref

# 跨组件通信

  • provide 和 inject
  • router
  • Vuex
  • store 模式
    • store 就是个普通的 js 通过导出的方式来让每个组件都可以访问到 且都具有修改的权限
  • eventBus 事件总线

# 虚拟 dom

1、 什么是虚拟 dom 答:虚拟 dom 就是个普通的 js 对象 用来描述 html 的结构 (属性、位置、子节点) 在 Vue 中每个组件都有一个 render 函数,每个 render 函数都会返回一个虚拟 dom 树 --》每个组件都对应一个虚拟 dom 树 当组件内发生变化时 只需要重新渲染发生改变的那个组件即可。

2、为什么需要虚拟 dom 答:vue 中渲染视图会调用 render 函数, 但是在视图关联的组件进行更新的时候也会调用 render 函数,如果在渲染时直接使用真实 dom,由于真实 dom 的创建、更新、插入等操作会带来大量的性能损耗,从而就会极大的降低渲染的效率 vue 中使用虚拟 dom 主要是为了解决渲染效率的问题

3、虚拟 dom 如何转化为真实 dom 答:在组件首次渲染的时候,会先生成一个虚拟 dom 树 --》根据虚拟 dom 树的结构 创建真实 dom,在把真实 dom 挂载到对应的位置 此时 ,每个虚拟 dom 对应一个真实 dom 如果一个组件依赖的数据变化,需要重新渲染的时候,会重新调用 render 函数,用生成的新的虚拟 dom 树和旧树进行比较 来得到最小的更新量,然后 --》抛弃旧树 修改更新的 dom 节点

4、模板和虚拟 dom 的关系 答: 在 Vue 框架中有 compile 模块,负责将模板转换成 render 函数,而 render 函数调用可以生成虚拟 dom 编译过程分两步: 1- 将模板字符串转换成 AST 抽象语法树 -->这个阶段耗时比较大 2- 将 AST 转换为 render 函数

Vue框架中 有配置文件 vue.config.js
属性配置 runtimeCompiler:true   会将compile模块打包进文件  否则就不会
Vue项目开发环境启动使用的是预编译, 不会存在compile模块

模板的存在只是为了让开发人员更加便捷。 Vue 最终运行的时候,最终需要的是 render 函数,而不是模板,因此模板中的各种语法,在虚拟 dom 中是不存在的,只会变成虚拟 dom 的配置

# v-model 原理

答:v-model 即可以用在表单元素也可以用作自定义组件中,无论是哪一种情况,它都是一个语法糖,最终会生成一个属性和一个事件。 当作用于表单元素时: Vue 会根据作用表单的类型生成合适的属性和事件 如:input 输入框 生成的是 value 属性和 input 事件 如果是 select 单选框或多选框类型 则生成 checked 属性和 change 事件。! 当作用于自定义组件时: 默认情况下 会生成一个 value 属性和一个 input 事件

<Comp v-model="data" />
<!-- 等效于 -->
<Comp :value="data" @input="data=$event" />

也可以通过在自定义组件中配置
model:{
   prop:'number',
   event:'onChange'
}

# 响应式原理

  • 响应式数据的最终目标,是当一个对象本身或对象的属性发生变化时,将会运行一些函数,最常见的就是 render 函数

实现上: Vue 主要用到了几个核心部件

  • Observer

  • Dep

  • Watcher

  • Scheduler

  • Observer

    • Observer 要实现的目标很简单,就是把一个普通对象转换为响应式的对象
    • 为了实现这一点,Observer 把对象的每个属性通过 Object.defineProperty 转换为带有 getter 和 setter 的属性。这样一来当访问或设置属性值时 Vue 就有机会做一些事情。
    原始对象obj{a:1} -->  Observer --> obj{a:get(),set()}
    如果对象中嵌套对象 则会递归遍历
    如果数据是一个数组 或者是数组内嵌套对象  Observer 则会深度遍历数组
    
    • Observer 是 Vue 内部构造器, 我们可以通过 Vue 提供的静态方法 Vue.observable(object) 来把一个数据变成响应式数据
    • Observer 是发生在 beforeCreate 之后 和 created 之前 之间
    • 如果对象要新增属性 或 删除原有属性的话 响应数据是监测不到的 obj:{a:1,b:2} obj.c = 3 或 delete obj.c 这样监测不到 然后 Vue 提供了一个方案 就是新增属性的时候可以使用 set 方法 删除的时候可以使用 delete 方法
    const obj ={
       a:1,
       b:2,
       c:3
    }
    
    this.$set(obj,'c')
    this.$delete(obj,'c')
    
    • 对于数组的话 Vue 会更改他的隐式原型,因为 Vue 要监听那些可以更改数组内容的方法。
      • 数组 _proto_ --> Vue 自定义对象 Vue 自定义对象**_proto_** --> Array.prototype

Observer 要做的事情 就是要让一个对象 内部属性的读取、赋值、内容变换 都要通知 Vue

  • Dep
    • Dep 解决的问题是如何知道谁在用我
    • Dep 的含义是 Dependency,表示依赖的意思
    • Vue 会为响应式对象中的每个属性、对象本身、数组创建一个 Dep 实例,每个 Dep 实例都有能力做一下两件事
    • 记录依赖(是谁在用我)
    • 派发更新(我变了,我要通知那些用到我的人)
    • 当读取响应式对象的某个属性时,它会进行依赖收集:有人用到我了
    • 当改变某个属性时,它会派发更新,用到我的人注意 我的值改变了。
相当于
{
   a:1,
   b:2,
   c:[1,2,3],
   d:{
      aa:4,
      bb:5
   }
}

添加Dep实例
{
   //[Dep]:[render]
   a:1, // [Dep]:[render]
   b:2, // [Dep]:[render]
   c:[1,2,3], // [Dep]:[render]
   d:{
      // [Dep]:[render]
      aa:4,// [Dep]:[render]
      bb:5 // [Dep]:[render]
   }
}

当使用了依赖改变时 会看Dep中 是那个调用 然后就调用这个地方
  • Watcher

    • Watcher 解决的问题就是 Dep 如何知道是谁在用我
    • 当某个函数执行的过程中,用到了响应式数据,响应式数据是不知道哪个函数在用自己的,Vue 通过一种办法来解决这个问题。
    • 我们不要直接执行函数,而是把函数交给 Watcher 去执行。Watcher 是一个对象。每个这样的函数执行时都应该创建一个 watcher,通过 watcher 去执行。
    • watcher 会设置一个全局变量 让全局变量记录当前负责执行的 watcher 等于自己,然后在执行函数。在函数的执行过程中,如果发生了依赖记录 dep.depend(),那么 Dep 就会把这个全局变量记录下来,表示有一个 watcher 用到了我这个属性。
    • 当 Dep 进行派发更新时,它会通知之前记录的所有 watcher:我改变了。
    • 每个 Vue 组件实例都至少对应一个 watcher,这个 watcher 记录了该组件的 render 函数。
    • watcher 会先将 render 函数运行一次来进行依赖收集,然后那些在 render 中用到的响应式数据就会记录这个 watcher
    • 当数据发生变化时,dep 就会通知该 watcher,watcher 重新运行 render 函数 ,让界面重新渲染同时重新记录当前的依赖
  • Scheduler

    • watcher 收到派发更新的通知时不是立即就执行对应函数,而是把自己交给调度器 scheduler,调度器维护一个执行队列,该队列同一个 watcher 只会存在一次,队列中的 watcher 不是立即执行的,通过一个$nextTick 的方法,把这些需要执行的 watcher 放到事件循环的微队列中。
    • 首次渲染是同步的要收集依赖,当响应式数据变化时,render 函数的执行是异步的,并且在微队列中。

总体流程: 1 原始对象 交给 --> Observe 得到具有响应式 getter、setter 的对象 ,首次渲染 要运行 render 函数, 而这个 render 函数是通过 watcher 调用的,通过 函数中用到的响应式数据收集到依赖。 (用到的各种 watcher) 在 getter 中收集依赖 2、数据改变 setter 派发更新 通知 watcher --》 watcher 把自己交给 调度器 scheduler --》scheduler 会把 watcher 添加到队列 然后在把这些 watcher 放到 nextTick 中 然后异步执行 watcher 执行 代表 render 函数执行 然后重新收集依赖

# Vue diff 原理

  • diff 时机
  • 当组件创建时,以及依赖的属性或数据变化时会运行一个函数,该函数会做两件事
    • 1、运行 _render 生成一颗新的虚拟 dom 树(vnode tree)
    • 2、运行 _update 传入虚拟 dom 的根节点,对新旧两颗树进行对比,最终完成对真实 dom 的更新
function Vue(){
    var updateComponent = ()=>{
       this._update(this._render())
    }
    new Watcher(updateComponent)
}

diff 就发生在_update 函数运行过程中

  • _update 函数在干什么

    • update 函数接收到一个新的虚拟 dom 树
    • update 在组件实例中通过_vnode 属性拿到旧的虚拟 dom
    • update 首先会给组件的_vnode 属性重新赋值 让他指向新的虚拟 dom 树
    • 然后进行 新旧两颗虚拟 dom 树的对比
  • patch 函数的对比流程

    • 「相同」:是指两个虚拟节点的标签类型、key 值相同 ,input 元素还会看 type 属性
    • 「新建元素」:根据一个虚拟节点的信息,创建一个真实 dom,同时挂载到虚拟节点的 elm 属性上(表示一个虚拟节点对应一个真实 dom)
    • 「销毁元素」:指 vnode.elm.remove()
    • 「更新」:是指两个虚拟 dom 进行对比更新,仅发生在两个虚拟节点「相同」的情况下。
    • 「对比子节点」:是指对两个虚拟 dom 的子节点进行对比
    • 对比:
      • 1、对比根节点
      • 如果根节点「相同」
      • 将旧节点的真实 dom 赋值到新节点:newVnode.elm = oldVnode.elm
      • 对比新旧节点的属性,有变化的更新到真实 dom 中
      • 处理完毕,开始对比子节点
      • 2、对比子节点
      • 采用头尾指针的方式来进行对比
      • 头指针「相同」将 真实 dom 直接「更新」
      • 头指针不同尾指针相同
      • 头指针不同尾指针不同 头尾指针相同 「更新」dom 移到头指针前
      • 新的虚拟 dom 树种没有相同的 则「新建节点」
      • 就得虚拟 dom 没有 则循环遍历 销毁节点

    阐述 Vue diff 算法

     当组件创建和更新时,vue均会执行内部的update函数,该函数使用render函数生成的虚拟dom树,将新旧两颗树进行对比,找到差异点 更新真实dom
     对比差异的过程叫diff,Vue内部通过一个patch的过程进行对比
     对比时采用深度优先,同层比较的方式进行对比
     在判断两个节点是否相同的时候,Vue通过虚拟节点的 tag key 进行判断。
     具体来说,首先对更节点进行比对,如果相同则将旧节点关联的真实dom挂载到新节点上,然后根据需要更新属性到真实dom,然后在对比子节点数组,如果不相同
     则按照新节点的信息递归创建所有真实dom,同时挂到对应虚拟节点上,然后移除旧dom
     在对比其子节点数组时,Vue对每个子节点数组使用了两个指针,分别指向头尾,然后不断向中间靠拢来进行对比。这样做的目的是尽量复用真实dom,尽量少的销毁和创建真实dom。如果发现相同,则进入和根节点一样的比对流程,如果发现不同,则移动真实dom到合适位置
     一直递归遍历,直到整棵树完成对比。