Vue响应式的原理

每次面试逃不过的一道面试题,那是什么呢?那就是 噔噔噔噔~~~ Vue的响应式,下面我们模拟一下面试的场景,看看大家是否感同身受,哈哈哈哈哈!!!!

 

面试官:看过Vue源码吗?

求职者:看过一点。

面试官:那你简单讲一下 Vue 的响应式原理

求职者:那什么,好的....

面试官:那就开始吧!

求职者:Vue的响应式原理主要经历了 3 个过程

    1. 响应式处理的核心过程

    2. 收集依赖的过程,

    3. 数据改变时,watcher的执行过程

面试官:额,点点头,可以具体说一下嘛?比如响应式处理的核心过程是什么样的过程?是如何执行的?

求职者:好的,那我先讲一下 【 响应式处理的核心过程 】 ,比如 写一个Vue 的 data 数据

const vm = new Vue({
    el: '#app',
    data: {
      NumberGroup: [1, 2, 3],
      StringName: 'XiaoMing Tongxue',
      userInfo: {
        name: 'DaYa Gao',
        sex: 'Girl'
      }
    }
  })

    1. 首先 通过 _init方法初始化实例成员,其中 initState下的 initData 来初始化Vue的data属性,同时通过observe将data转化成响应式数据

export function initState(vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm) // 初始化data属性
  } else {
    observe(vm._data = {}, true /* asRootData */) // 将 data 转换为响应式数据
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

 

    2. observe方法 内 将data属性下添加 Dep 属性,并添加__ob__ 属性, 标识为 响应式数据

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep() // 添加依赖
    this.vmCount = 0
    def(value, '__ob__', this) // 添加__ob__属性,表示将其设置为响应式标识
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

    

    3. 判断 data属性,如果是对象,则调用 walk方法,将data的值进行遍历,通过 defineReactive 转化为响应式数据。

    4. 其中 defineReactive 中分别对 NumberGroup, StringName, userInfo, 添加 Dep 属性,并对其子属性也添加 Dep 属性, 

    5. 添加 Dep 属性后,通过 Object.definedProperty 将所有属性 (父级,和子级 ) 转换为响应式数据

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep() // 给属性添加依赖

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val) // 给属性的子级设置依赖
  Object.defineProperty(obj, key, {
    enumerable: true, // 可枚举
    configurable: true, // 可配置
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) { // 是否有依赖 ( watcher )
        dep.depend() // 将 watcher 存储 在 dep.subs 中
        if (childOb) {
          childOb.dep.depend() // 属性子级 也存储 watcher 到 dep.subs 中,注意 属性子级的 subs 与属性 subs 不同
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    }
  })
}

 

 

求职者:到这里第一个阶段 【 响应式处理的核心过程 】结束了。

面试官:请继续你的表演~

求职者:第二阶段是 【 收集依赖的过程 】

    5. 通过Object.definePrototype内的 get方法 使用 dep.depend() 收集依赖,对每个属性加入对应的watcher

    6. 如果此处属性是数组,则封装 js 原生数组方法,并通过 watcher 与 js 数组方法结合

    7. vm._render 渲染虚拟Dom

    8. vm._update 将虚拟 Dom 转换为真实 Dom

    9. 将真实 Dom 展示到界面上

面试官:额,继续

求职者:第三阶段是 【 数据改变时,watcher的执行过程 】

    10. 比如修改 vm.StringName="Hahaha" 

    11. 因为 vm.StringName 是一个 Object.definedPrototype 方法,会直接调用 set 方法

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal) // 新值变为响应式数据
      dep.notify() // watcher 通知数据修改
    }
  })
}

  

    12. set 方法 先去判断新值与旧值的区别,如果不相同,则新值覆盖就值,并将新值 通过 observe转换为响应式对象

    13. 修改成功后,调用 Dep.notify() 通知 watcher 的 update 实现更新,通过 nextTick 延时 更新,

    14. 并通过 vm._render 渲染虚拟 Dom

    15. vm.update 将虚拟 Dom 转换 为真实 Dom

    updateComponent = () => {
      vm._update(vm._render(), hydrating) // 将虚拟 Dom 转换为真实 Dom 
    }

    16.  将真实 Dom 展示到界面上

    

 

 

面试官:Object.definePrototype 有几个属性?分别是什么?代表什么?

求职者:共有 3 个属性

Object.defineProperty(obj, prop, desc)

1. obj 需要定义属性的当前对象

2. prop 需要定义的属性名

3. desc 属性描述符

例如:

  const obj = { name: 'hhahha' }

    Object.defineProperty(obj, 'name', {
      enumerable: true, // 可枚举 
      configurable: true, // 可配置,false = 不可以修改,不可以删除
      get: function (params) {
        console.log('get', params)
        return obj['name']
      },
      set: function (params) {
        console.log('set', params)

      }
    })

 

 

原文地址:https://www.cnblogs.com/gqx-html/p/14189609.html