vue面试-响应数据原理

469 阅读3分钟

前言


今天这篇文章小编根据源码来讲解分析一下vue的响应式数据原理,vue面试这个小编会作为一个系列去结合源码分享一系列的相关面试常见问题。因为今天是开头所以想说的会多一点,也希望读者大大在之后一起学习的时候能避免到一些不必要的小白专属坑。

  • flow类型检测

    有了解看过vue源码的同学应该知道里面用了很多ts语法,但实质上是运用的flow,与ts是同样的类型检查工具,两者目的都是将javascript从弱类型语言变成了强类型语言。查看源码时由于编辑器默认是对js的语法检查,所以可能会报错,我们在settings中加入

    "javascript.validate.enable": false
    
  • 理解误区

    我们看源码的关键点还是在于梳理流程,有的时候就不用太去纠结每一行代码的作用意义,这样耗费大量时间效果也不佳,小编所分享的大家也只看注释部分代码就可以理解流程了,我是为了方法完整性就尽量都拷贝了下来

Vue响应数据原理


实际场景

我们知道vue数据与组件是响应式的,最明显的v-model,很通俗的来讲在vue的内部对数据以及相关的组件进行了一个绑定(依赖收集),当数据属性更新那么vue内部通过依赖关系再去跟新页面反馈

源码分析

数据提取

vue内部initData方法实现用户数据data的提取 文件目录:

src

  • core
    • instance
      • state.js
function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance 这一段删减掉了

  // 前面操作是拿到用户data数据,然后这边我们以observe方法去操作数据
  observe(data, true /* asRootData */)
}

对象观测检查

当我们提取到数据对象这边内部会进行一个观测检查,该对象是否已经被检测未被检测的话会再次创建实例检测,observe方法提供该功能,那么未检测的我们去创建了检测实例

export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 这边检查一下不是对象就不进行观测
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 检测对象是否已经被观测
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 如果没有被观测那么创建观测实例 
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  // 最后返回实例
  return ob
}

创建检测实例

这次到Observer类中去看一下为数据对象所创建的实例类是怎样的

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // 将此对象作为根$data的vm的数量

  constructor (value: any) {
      this.walk(value)
  }
}

这一步我们就将数据对象交给到walk方法

定义响应式

walk方法就是将更新的数据中介到方法defineReactive中去跟新属性。

walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i]) // 定义响应式
    }
  }

实际定义

那么在defineReactive方法中我们就到了定义的最底层,以js的defineProperty方法去定义属性

let childOb = !shallow && observe(val) // 如果当前值还是对象则递归调用observe
export function defineReactive (
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    
    get: function reactiveGetter () {  // 当用户获取数据时调用
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend() // 通过该方法去收集依赖,当数据发生改变就会通知依赖改变就到了set方法汇总中
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      // 如果值一样那么久return
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      // 值不一样就是调用该核心方法去响应DOM结构
      dep.notify()
    }
  })
}

总结

vue数据响应原理我们重在理解他的底层工作流程,以及响应核心方法,当被问到的时候根据流程答出相应核心方法对于这个问题那就很稳妥了。