[Vue面试题] 1.0

108 阅读3分钟

1. Vue 响应式原理

image.png

核心实现类:

Observer : 它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新

Dep : 用于收集当前响应式对象的依赖关系,每个响应式对象包括子对象都拥有一个 Dep 实例(里面 subs 是 Watcher 实例数组),当数据有变更时,会通过 dep.notify()通知各个 watcher。

Watcher : 观察者对象 , 实例分为渲染 watcher (render watcher),计算属性 watcher (computed watcher),侦听器 watcher(user watcher)三种

依赖收集

  1. initState 时,对 computed 属性初始化时,触发 computed watcher 依赖收集
  2. initState 时,对侦听属性初始化时,触发 user watcher 依赖收集
  3. render()的过程,触发 render watcher 依赖收集
  4. re-render 时,vm.render()再次执行,会移除所有 subs 中的 watcer 的订阅,重新赋值。

派发更新

  1. 组件中对响应的数据进行了修改,触发 setter 的逻辑
  2. 调用 dep.notify()
  3. 遍历所有的 subs(Watcher 实例),调用每一个 watcher 的 update 方法。

2. computed 的实现原理

computed 本质是一个惰性求值的观察者。

computed 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。

其内部通过 this.dirty 属性标记计算属性是否需要重新求值。

当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,

computed watcher 通过 this.dep.subs.length 判断有没有订阅者,

有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。 )

没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。 )

3. computed 和 watch 有什么区别及运用场景?

当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的

4. 为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

Object.defineProperty 本身有一定的监控到数组下标变化的能力,但是在 Vue 中,从性能/体验的性价比考虑,尤大大就弃用了这个特性(Vue 为什么不能检测数组变动 )。为了解决这个问题,经过 vue 内部处理后可以使用以下几种方法来监听数组 push() pop() shift() unshift() splice() sort() reverse()

简单来说, Object.defineProperty只能劫持对象的属性, 带有深度的复杂对象需要对对象的每个属性进行遍历, 想实现劫持整个对象很复杂。 Proxy 可以劫持整个对象,并返回一个新的对象。Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

7. vue 是如何对数组方法进行变异的 ?

vue通过原型拦截的方式重写了数组的push() pop() shift() unshift() splice() sort() reverse()方法; 首先获取到数组的Observer对象, 如果有新值, 调用 observeArray对新的值监听, 手动调用notify通知render watcher, 执行update

8. Vue 组件 data 为什么必须是函数 ?

组件可以复用, JS对象是引用关系, 重复引用相同组件时, 对象data会互相污染组件的数据, 初始值, function返回一个新的对象, 内存地址不一样, 这样data才是单独的