# 谈谈对 vue 生命周期的理解

每个 Vue 实例在创建时都会经过一系列的初始化过程,生命周期钩子,就是在初始化过程中的某个阶段,去触发指定的函数,目的是完成一些动作或事件。

  • create 阶段:(Vue 实例被创建)
    • beforeCreate:此时 data 和 methods 中的数据都还没有初始化
    • created: 初始化完毕,data 中有值,未挂载,此时可以请求数据
  • mount 阶段:挂载真实的 DOM 节点
    • beforeMount:尚未挂载
    • mounted:已挂载,此时可操作 DOM
  • update 阶段:当 vue 实例里面的 data 数据变化时,触发 virtual DOM 的更新和 DOM 的重新挂载
    • beforeUpdate : 更新前。在这之后开始 diff virtual DOM,
    • updated:更新后
  • destroy 阶段:vue 实例被销毁
    • beforeDestroy:销毁前,可以手动的卸载一些事件
    • destroy:销毁后

下面的代码展现了 Vue 实例初始化的大概流程。

_init() {
  vm.$options = mergeOptions()
  initLifecycle(vm)
  initEvents(vm)
  initRender(vm)
  // 第一个钩子函数
  callHook(vm, 'beforeCreate')
  initState(vm) {
    initProps()
    initMethods()
    initData() {
      observe()
        // 创建 dep 实例来保存依赖
        Observer()
          this.dep = new Dep()
    }
    initComputed()
    initWatch()
  }
  initProvide(vm)
  // 初始化完毕之后,第二个钩子函数
  callHook(vm, 'created')
}
vm.$mount(vm.$options.el);
callHook(vm, 'beforeMount')
updateComponent = function () {
  vm._update(vnode, hydrating)
}
new Watcher(vm, updateComponent, ...)

createCompilerCreator(baseCompile)
  return function createCompiler(baseOptions)
    function compile (template, options)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# vue 数据绑定的实现原理

vue 2.0 版本使用的是 Object.defineProperty 方法。

  • 整体来看是 Object.defineProperty + 发布订阅模式来实现的
  • 具体来看,initState 阶段,新建一个 dep 实例存放依赖;
  • 然后 vue 会遍历 data 里面的对象,使用 Object.defineProperty 修改对象属性的 get、set 特性。
  • 在这之后,当我们触发对象属性的 getter 的时候 vue 会使用 dep.depend 方法收集属性对应的依赖 (watcher),触发 setter 的时候使用 dep.notify 触发依赖,通知 watcher 重新计算,从而使关联的组件得以更新;
  • watcher 是组件级别的,组件内部的更新需要使用 virtual DOM 的 diff 算法进行局部更新;

vue3.0 思路是类似的,也是将使用了同一状态的依赖记录下来,状态改变时通知依赖更新,只不过监测状态(对象属性)和记录依赖的方式不太一样。

  • dep 类被拆分,保存依赖的对象换成了 Set 类型,depend 方法改名为 track,notify 方法改名为 trigger。
  • 不再使用 Object.defineProperty,而是使用 proxy 代理对象的方式实现对对象属性的监测。

# Diff 算法

Diff 算法是一种对比算法。对比两者是 old VNode 和 new VNode,对比出是哪个虚拟节点更改了,找出这个虚拟节点,并只更新这个虚拟节点所对应的真实节点,而不用更新其他数据没发生改变的节点,实现精准地更新真实 DOM,从而提高效率。

Vue 中的 diff 使用的是深度优先算法,只在同层进行对比,时间复杂度O(n)

  • 对比当前同层的虚拟节点是否为同一种类型的标签;
    • 当 key、tag、isComment、data 相同,同时满足当标签类型为 input 的时候 type 相同。
    • 是:继续执行 patchVnode 方法进行深层比对
    • 否:没必要比对了,直接整个节点替换成新虚拟节点
  • patchVnode
    • 如果新旧 VNode 都是静态的,同时它们的 key 相同(代表同一节点),并且新的 VNode 是 clone 或者是标记了 once(标记 v-once 属性,只渲染一次),那么只需要替换 elm 以及 componentInstance 即可。
    • 新老节点均有 children 子节点,则对子节点进行 diff 操作,调用 updateChildren,这个 updateChildren 也是 diff 的核心。
    • 如果老节点没有子节点而新节点存在子节点,先清空老节点 DOM 的文本内容,然后为当前 DOM 节点加入子节点。
    • 当新节点没有子节点而老节点有子节点的时候,则移除该 DOM 节点的所有子节点。
    • 当新老节点都无子节点的时候,只是文本的替换。
  • updateChildren

# Computed 和 Watch 的区别

  • computed
    • 支持缓存,只有依赖的数据发生改变时,才会重新计算;
    • 不支持异步,内部有异步代码时无效;
    • 当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
  • watch
    • 不支持缓存,参数有变化就会触发计算;
    • 支持异步;
    • 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch, 使用 watch 选项允许执行异步操作 ( 访问一个 API ), 限制执行该操作的频率,并在得到最终结果前,设置中间状态 这些都是计算属性无法做到的。

# slot 是什么,作用,原理

slot 又名插槽,是 Vue 的内容分发机制,组件内部的模板引擎使用 slot 元素作为承载分发内容的出口。插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。

slot 又分三类,默认插槽,具名插槽和作用域插槽。

  • 默认插槽:又名匿名查抄,当 slot 没有指定 name 属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
  • 具名插槽:带有具体名字的插槽,也就是带有 name 属性的 slot, 一个组件可以出现多个具名插槽。
  • 作用域插槽:默认插槽 具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。

实现原理:当子组件 vm 实例化时,获取到父组件传入的 slot 标签的内容,存放在 vm.$slot 中,默认插槽为 vm.$slot.default, 具名插槽为 vm.$slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,使用 $slot 中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。

# 过滤器的作用,如何实现

过滤器是用来过滤数据的,在 Vue 中使用 filters 来过滤数据,filters 不会修改数据,而是过滤数据,改变用户看到的输出(计算属性 computed , 方法 methods 都是通过修改数据来处理数据格式的输出显示)。

# 单向数据流

是什么:数据只能由父组件流向子组件,子组件通过事件来通知父组件更新自有的数据。

为什么?

组件相当于一个函数,props 相当于函数的传参。如果组件内部可以改变 props 就相当于在函数内部改变参数。那么这个函数就产生了副作用,那么这个函数就不是一个 pure function。这会使函数变的不可测试,不可测试也就不能预测执行结果,从而降低代码可维护性。

# v-model 原理

其实是一个语法糖,v-bind 传参,v-on 监听子组件触发的事件。v-model 在不同的 HTML 标签上使用会抛出不同的事件:

  • text 和 textarea 元素使用 value 属性和 input 事件;
  • checkbox 和 radio 使用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

# v-for 加 key 的作用

唯一标记,diff 操作可以更准确、更快速。

# keep-alive 原理

todo

# Proxy 与 Object.defineProperty 对比

Proxy 的优势如下:

  • Proxy 可以直接监听对象而非属性;
  • Proxy 可以直接监听数组的变化;
  • Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
  • Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
  • Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;

Object.defineProperty 的优势:兼容性好,支持 IE9。

# vue.$set 原理

如果目标是数组,直接使用数组的 splice 方法触发相应式;

如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理。

# Vue 性能优化

  • 不需要响应式的对象一开始不要写在 data 里;
  • v-if 和 v-show 区分使用场景;
  • computed 和 watch 区分使用场景;
  • v-for 遍历必须为 item 添加 key,且避免同时使用 v-if;
  • 事件的销毁;
  • 路由懒加载;
  • 第三方插件的按需引入;
上次更新: 5/15/2022, 6:53:35 PM