zoukankan      html  css  js  c++  java
  • 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)
    
          }
        })
    

     

     

  • 相关阅读:
    独立性检验|适应性检验|卡方检验|离散型数据|连续型数据
    双因子方差分析|adjusted R|强度|SSA|SSE|SST|
    发明人|申请人|专利权人|完全民事能力|谁主张谁证明|新颖性|创造性|实用性|先申请制
    方差分析|重复|指标|因素|处理效应|实验误差|实验设计可避免的误差|一般线性|广义线性
    字符数组1
    四舍五入输出数字
    如何输出0001, 0002, 0003... 这样的数
    shell scripting Python
    [C&Python]9x9乘法口诀打印
    [pointer]调用函数找出一维数组中的最值,
  • 原文地址:https://www.cnblogs.com/gqx-html/p/14189609.html
Copyright © 2011-2022 走看看