美文网首页Web前端之路
vue源码分析之响应式原理(Watcher、Observer、D

vue源码分析之响应式原理(Watcher、Observer、D

作者: 慕时_木雨凡 | 来源:发表于2019-07-31 22:36 被阅读0次

    vue作为最受欢迎的前端开发框架。非常值得我们倾心研究一番。

    读源码的动力

    • 源码阅读可以看到作者(前端技术最顶端的人)对js的理解
    • 可以看到作者优秀的设计思想
    • 可以更加快速的处理和理解我们在日常工作出现的问题
    • 提高自己的技术深度和广度

    Vue响应式原理

    1. 使用Object.defineProperty将data数据变成响应式对象,通过Observer给对象添加get 和 set属性
    2. 调用对象数据时会触发getter,改变对象数据时会触发setter
    3. 在getter中进行依赖收集(使用到当前数据的地方会被作为一个Watcher对象处理)
    4. 在setter中通过Dep进行派发更新(通过处理watcher对象Dep.target = watcher从而调用nextTick进行数据更新)

    通过Observer处理响应式对象

    1. 初始化Vue实例时调用initState(this)
    2. 在initState(this)函数中调用new Observer(vm.$options.data)处理data数据
    3. 在Observer(val)中通过调用walk()循环data数据并调用defineReactive(obj, key, val)实现响应式绑定
    Vue.prototype._init = function() {
        initState(this)
    }
    
    function initState(vm) {
      new Observer(vm.$options.data)
    }
    
    class Observer {
      constructor (value) {
        this.walk(value)
      }
      walk (obj) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
          defineReactive(obj, keys[i], obj[keys[i]])
        }
      }
    }
    
    function defineReactive (obj, key, val) {
      val = obj[key]
      Object.defineProperty(obj, key, {
        get: function reactiveGetter () {
          return value
        },
        set: function reactiveSetter (newVal) {
          val = newVal
        }
      })
    }
    

    给对象添加getter和setter属性

    1. 利用Object.defineProperty函数的特性给对象添加get和set属性
    2. 监测getter和setter是否存在,不存在就添加get/set属性
    function defineReactive (obj, key, val) {
      const property = Object.getOwnPropertyDescriptor(obj, key)
      if (property && property.configurable === false) {
        return
      }
      const getter = property && property.get
      const setter = property && property.set
      Object.defineProperty(obj, key, {
        get: function reactiveGetter () {
          return value
        },
        set: function reactiveSetter (newVal) {
          val = newVal
        }
      })
    }
    

    利用Dep在get中进行依赖收集

    1. Dep是Watcher的管理器
    2. Dep.target就是当前的Watcher
    3. depend()中会调用执行Dep.target.addDep(this)
    4. 通过一连串的函数调用最终将当前的watcher存储在subs[]中
      let childOb = !shallow && observe(val)
      get: function reactiveGetter () {
        const value = getter ? getter.call(obj) : val
        if (Dep.target) {
          dep.depend()
          if (childOb) {
            childOb.dep.depend()
          }
        }
        return value
      }
      depend () {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }
      addDep (dep) {
        const id = dep.id
        if (!this.newDepIds.has(id)) {
          this.newDepIds.add(id)
          this.newDeps.push(dep)
          if (!this.depIds.has(id)) {
            dep.addSub(this)
          }
        }
      }
    

    在set中进行派发更新

    1. set函数会在修改当前data数据时调用
    2. set中会执行dep.notify()函数 -- 派发更新的重点在notify()中
    3. notify会循环subs得到之前在get中存储的Watcher并调用update()
    4. update中会执行Watcher的run函数
    5. run函数中执行this.cb.call(this.vm, value, oldValue),最终会触发updateComponent函数进行Dom更新
        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()
        }
        notify () {
          const subs = this.subs.slice()
          for (let i = 0, l = subs.length; i < l; i++) {
            subs[i].update()
          }
        }
        update () {
          if (this.lazy) {
            this.dirty = true
          } else if (this.sync) {
            this.run()
          } else {
            queueWatcher(this)
          }
        }
        run () {
          this.cb.call(this.vm, value, oldValue)
        }
    

    Watcher是什么时候创建的?Watcher是如何触发Dom更新的

    1. 在初始化虚拟Dom时initVirtualComponent()函数中会执行 new Watcher(vm, updateComponent, noop, null, true)创建Watcher
    2. Watcher对象的cb数据updateComponent函数,执行this.cb.call(this.vm, value, oldValue)也就是执行了updateComponent
    3. updateComponent()中调用_update(),进而更新Dom

    相关文章

      网友评论

        本文标题:vue源码分析之响应式原理(Watcher、Observer、D

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