美文网首页前端开发那些事儿vue、javascript
面试官:你可以手写Vue2的响应式原理吗?

面试官:你可以手写Vue2的响应式原理吗?

作者: 源大侠 | 来源:发表于2021-03-24 10:50 被阅读0次

    这道题目是面试中相当高频的一道题目了,但凡你简历上有写:“熟练使用Vue并阅读过其部分源码”,那么这道题目十有八九面试官都会去问你。

    什么?你简历上不写阅读过源码,那面试官也很有可能会问你是否阅读过响应式相关的源码

    还是那句歌词唱的:

    挣不脱 逃不过
    眉头解不开的结
    命中解不开的劫
    
    image.png

    整体流程

    作为一个前端的MVVM框架,Vue的基本思路和Angular、React并无二致,其核心就在于: 当数据变化时,自动去刷新页面DOM,这使得我们能从繁琐的DOM操作中解放出来,从而专心地去处理业务逻辑。
    这就是Vue的数据双向绑定(又称响应式原理)。数据双向绑定是Vue最独特的特性之一。此处我们用官方的一张流程图来简要地说明一下Vue响应式系统的整个流程:

    image.png
    Vue中,每个组件实例都有相应的watcher实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

    这是一个典型的观察者模式。

    关键角色

    Vue 数据双向绑定的实现逻辑里,有这样三个关键角色:

    • Observer: 它的作用是给对象的属性添加gettersetter,用于依赖收集和派发更新
    • Dep: 用于收集当前响应式对象的依赖关系,每个响应式对象包括子对象都拥有一个Dep实例(里面subsWatcher实例数组),当数据有变更时,会通过dep.notify()通知各个watcher
    • Watcher: 观察者对象 , 实例分为渲染 watcher (render watcher),计算属性 watcher (computed watcher),侦听器watcher(user watcher)三种

    Watcher 和 Dep 的关系

    为什么要单独拎出来一小节专门来说这个问题呢?因为大部分同学只是知道:Vue的响应式原理是通过Object.defineProperty实现的。被Object.defineProperty绑定过的对象,会变成「响应式」化。也就是改变这个对象的时候会触发getset事件。
    但是对于里面具体的对象依赖关系并不是很清楚,这样也就给了面试官一种:你只是背了答案,对于响应式的内部实现细节,你并不是很清楚的印象。
    关于WatcherDep 的关系这个问题,其实刚开始我也不是很清楚,在查阅了相关资料后,才逐渐对里面的具体实现有了清晰的理解。

    image.png
    刚接触Dep这个词的同学都会比较懵: Dep究竟是用来做什么的呢?我们通过defineReactive方法将data中的数据进行响应式后,虽然可以监听到数据的变化了,那我们怎么处理通知视图就更新呢?
    Dep就是帮我们依赖管理的。
    如上图所示:一个属性可能有多个依赖,每个响应式数据都有一个Dep来管理它的依赖。

    一段话总结原理

    上面说了那么多,下面我总结一下Vue响应式的核心设计思路:
    当创建Vue实例时,vue会遍历data选项的属性,利用Object.defineProperty为属性添加gettersetter对数据的读取进行劫持(getter用来依赖收集,setter用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。
    每个组件实例会有相应的watcher实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有computed watcher,user watcher实例),之后依赖项被改动时,setter方法会通知依赖与此data的watcher实例重新计算(派发更新),从而使它关联的组件重新渲染。
    到这里,我们已经了解了“套路”,下面让我们用伪代码来实现一下Vue的响应式吧!

    核心实现

    /**
     * @name Vue数据双向绑定(响应式系统)的实现原理
     */
    
    // observe方法遍历并包装对象属性
    function observe(target) {
      // 若target是一个对象,则遍历它
      if (target && typeof target === "Object") {
        Object.keys(target).forEach((key) => {
          // defineReactive方法会给目标属性装上“监听器”
          defineReactive(target, key, target[key]);
        });
      }
    }
    // 定义defineReactive方法
    function defineReactive(target, key, val) {
      const dep = new Dep();
      // 属性值也可能是object类型,这种情况下需要调用observe进行递归遍历
      observe(val);
      // 为当前属性安装监听器
      Object.defineProperty(target, key, {
        // 可枚举
        enumerable: true,
        // 不可配置
        configurable: false,
        get: function () {
          return val;
        },
        // 监听器函数
        set: function (value) {
          dep.notify();
        },
      });
    }
    
    class Dep {
      constructor() {
        this.subs = [];
      }
    
      addSub(sub) {
        this.subs.push(sub);
      }
    
      notify() {
        this.subs.forEach((sub) => {
          sub.update();
        });
      }
    }
    

    相关文章

      网友评论

        本文标题:面试官:你可以手写Vue2的响应式原理吗?

        本文链接:https://www.haomeiwen.com/subject/nybchltx.html