美文网首页
Vue源码分析数据响应

Vue源码分析数据响应

作者: HelenYin | 来源:发表于2017-12-21 23:42 被阅读0次

    这篇文章主要分析一下Vue数据响应,并且通过这篇文章你将了解到:
    1.Vue数据响应式的设计思想
    2.观察者模式
    3.了解Observer,Dep,Watcher的源码实现原理

    首先,Vue使用的数据响应使用了数据劫持的方法:

    当我们在new Vue()的时候将data属性下的对象转化成可观察的,用的是这个方法: Observer.defineProterty()。当数据改变的时候触发set(),当获取数据时触发get()

    使用了观察者模式来实现数据响应:

    观察者模式
    观察者模式也称为发布-订阅模式。主要用于处理不同对象之间的交互通信问题。
    发布者:当有事件触发的时候,通知订阅者。
    订阅者:当收到发布者的通知时,做出响应的反应。
    观察者模式的使用场景:当一个对象的状态发生变化时,所有的依赖对象都将得到通知。


    先来看看Vue中new一个实例时

    new Vue({
      data(){
        return {
          name: 'Helen',
          age: 18
        }
      },
      watch: {
        age(newVal){
          ...
          cb()
        }
      }
    })
    

    后面我将围绕这一个示例来讲解,我们先在data中将nameage转成可观察的,然后watch age这个属性,当age改变时,执行回调函数。先想一下实现这个功能的思路。
    首先,我们要将在data中声明了的属性变成可观察的,然后再去监听这个属性。

    class Observer{
      constructor(data){
        this.walk(data)
      }
      walk(){
        //遍历data的每一个属性
        Object.keys(data).forEach(key => {
          defineReactive(data, key, data[key])
        })
      }
    }
    
    function defineReactive(data, key, val){
      Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get(){
          return val
        },
        set(newVal){
          val = newVal
        }
      })
    }
    

    上面的代码将对在new Observer对象时,初始化是执行walkdata的属性循环调用defineReactivedefineReactivedata的属性转成访问器属性。当修改data属性值时,可以通过set get获取通知。现在还需要一个Dep来存放依赖

    class Dep{
        this. depends = []
    }
    

    当data中的属性改变时,使用Dep来触发订阅者,当获取值时,用Dep来收集依赖。
    然后还需要一个Watcher,在依赖发生改变的时候将执行Watcher。源码中有三处用到了new Wacher

    1.在更新视图的时候,updateComponent
    2.watch 属性的时候:$watcher(就是实例中用到的)
    3.computed
    其实讲到这里,已经可以看出Watcher其实是一个订阅者。只有当data的数据改变的时候触发。

    怎么将Dep和Watcher向关联呢?
    class Watcher{
        constructor(exp, cb){
          this.exp = exp //exp 是监听的属性
          this.cb = cb //cb是回调函数
          //多加了这一句
          this.value = data[exp]
        }
      }
    

    this.value = data[exp]这一句的作用是什么?data已经变成访问器对象了,获取data属性的值就是触发了Observerget,额。。这就关联起来了。
    现在需要添加代码:在获取属性时收集依赖,在设置属性值时发送通知执行watcher。

    function defineReactive(data, key, val){
       let dep = new Dep()
      Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get(){
          //收集依赖
          dep.depend()
          return val
        },
        set(newVal){
          //发送通知
          dep.notify()
          val = newVal
        }
      })
    }
    //在修改一下Dep类
    class Dep{
        this. depends = []
        depend(){
          //这里需要将Watcher.target这个静态属性设置成一个Watcher的实例,然后加入到依赖的数组
          this.depends.push(Watcher.target)
      }
      notify(){
        //遍历依赖,执行回调函数
        this.depends.forEach(depend => {
          depend.cb()
        })
      }
    }
    //在修改一下watcher
    class Watcher{
      constructor(exp, cb){
        this.exp = exp
        this.cb = cb
        //将Watcher.target设置成实例
        Watcher.target = this 
        this.value = data[exp]
      }
    }
    

    下面再看一遍源码:

    function defineReactive (
      obj: Object,
      key: string,
      val: any,
      customSetter?: Function
    ) {
      //每一个属性都有一个dep实例,这个Dep实例在get和set闭包作用域链中
      const dep = new Dep()
      //返回属性描述符
      const property = Object.getOwnPropertyDescriptor(obj, key)
      //如果该属性不能被删除或修改则不继续执行
      if (property && property.configurable === false) {
        return
      }
    
      // cater for pre-defined getter/setters
      /**
       * 直接将对象描述符的get和set封装成getter和setter函数
       * 当没有手动设置get和set的时候,getter和setter是undefined
       */
      const getter =  property && property.get
      const setter = property && property.set
      //进行递归绑定
      let childOb = observe(val)
      //每一个属性都要变为可观察的
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
          //这里只有Dep.target存在的情况下才收集依赖,Dep.target其实是        
          //一个watcher对象,全局的Dep.target是唯一的,只有在watcher在
          //收集依赖的时候才会执行dep.depend(),在直接使用js访问属性的
          //时候直接取值
          if (Dep.target) {
            dep.depend()
            //递归绑定
            if (childOb) {
              childOb.dep.depend()
            }
          }
          return value
        },
        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()
          }
    
          if (setter) {
            setter.call(obj, newVal)
          } else {
            val = newVal
          }
          childOb = observe(newVal)
          //data对象的属性值改变就会触发notify来通知订阅者
          dep.notify()
        }
      })
    }
    

    通过上面代码的注释,可以知道Dep是在收集订阅者,每一个data对象的属性都有一个Dep,每一个Dep可以有多个订阅者,这一句dep.depend()其实很关键,其实是在收集订阅者Watcher
    再来看一下Dep部分的代码:

     class Dep {
      static target: ?Watcher;
      id: number;
      subs: Array<Watcher>; //subs存的是这个Dep的所有订阅者Watcher
    
      constructor () {
        this.id = uid++  //每个Vue实例中的每一个Dep实例都有不同的id
        this.subs = []  //用来收集订阅者
      }
    //添加订阅者
      addSub (sub: Watcher) {
        this.subs.push(sub)
      }
    //删除订阅者
      removeSub (sub: Watcher) {
        remove(this.subs, sub)
      }
    //给watcher收集依赖
    //这里是一个关键步骤,Dep.target是一个watcher实例
    //先将这个Dep实例添加到Watcher的依赖中
    //然后在watcher中调用dep.addSub将watcher添加到dep的订阅者中。
      depend () {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }
    //通知订阅者,数据有更新
      notify () {
        // stablize the subscriber list first
        const subs = this.subs.slice()
        //遍历这个依赖的所有订阅者watcher
        for (let i = 0, l = subs.length; i < l; i++) {
          //update()的最终目的就是要执行Watcher的getter
          //执行这个Watcher的getter的时候就会触发这个Watcher的依赖们的get()
          //然后重新收集依赖
          subs[i].update()
        }
      }
    }
    

    注释已经写得很详细了,先看看Watcher的代码,再讲一下Dep的问题

    class Watcher {
      vm: Component;
      expression: string;
      cb: Function;
      id: number;
      deps: Array<Dep>;
      newDeps: Array<Dep>;
      depIds: Set;
      newDepIds: Set;
      getter: Function;
      value: any;
      
      constructor (
        vm: Component,
        expOrFn: string | Function,  
        cb: Function,
        options?: Object 
      ) {
        this.vm = vm
        vm._watchers.push(this)
       
        this.cb = cb
        this.id = ++uid // uid for batching
        this.deps = []
        this.newDeps = []
        this.depIds = new Set()
        this.newDepIds = new Set()
        this.expression = process.env.NODE_ENV !== 'production'
          ? expOrFn.toString()
          : ''
       // parse expression for getter
       //expOrFn可以是字符串,也可能是函数,如果是函数就直接赋给
       //this.getter,如果是字符串表达式就回去parsePath
        if (typeof expOrFn === 'function') {
          this.getter = expOrFn
        } else {
          this.getter = parsePath(expOrFn)
        }
        //这里在初始化时就会去执行get(),然后在get()中执行expOrFn
        //(后面讲详细讲这里)。
        this.value = this.lazy
          ? undefined
          : this.get()
      }
    
      /**
       * Evaluate the getter, and re-collect dependencies.
       */
      //Watcher收集依赖
      get () {
        //pushTarget函数将target设置为该watcher实例
        pushTarget(this)
        const value = this.getter.call(this.vm, this.vm) //就在这一步收集依赖
        // "touch" every property so they are all tracked as
        // dependencies for deep watching
        if (this.deep) {
          traverse(value)
        }
        popTarget()
        //清理依赖
        this.cleanupDeps()
        return value
      }
    
      /**
       * Add a dependency to this directive.
       */
    //添加依赖
      addDep (dep: 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)
          }
        }
      }
    
      /**
       * Clean up for dependency collection.
       */
      cleanupDeps () {
        let i = this.deps.length
        while (i--) {
          const dep = this.deps[i]
          //如果这个watcher不依赖与某个数据就要把这个依赖给删除
          if (!this.newDepIds.has(dep.id)) {
            dep.removeSub(this)
          }
        }
        let tmp = this.depIds
        //更新depIds
        this.depIds = this.newDepIds
        this.newDepIds = tmp
        //清空newDepIds
        this.newDepIds.clear()
        tmp = this.deps
        //这里赋值了this.deps
        //更新deps
        this.deps = this.newDeps
        this.newDeps = tmp
        //清空newDeps
        this.newDeps.length = 0
      }
    
      /**
       * Subscriber interface.
       * Will be called when a dependency changes.
       */
    //update的最终都会执行this.run()
      update () {
        /* istanbul ignore else */
        if (this.lazy) {
          this.dirty = true
        } else if (this.sync) {
          this.run()
        } else {
          queueWatcher(this)
        }
      }
    
      /**
       * Scheduler job interface.
       * Will be called by the scheduler.
       */
    //run方法中,会执行this.get(),就会重新收集依赖
      run () {
        if (this.active) {
          //重点是这里,会this.get()
          const value = this.get()
          if (
            value !== this.value ||
            // Deep watchers and watchers on Object/Arrays should fire even
            // when the value is the same, because the value may
            // have mutated.
            isObject(value) ||
            this.deep
          ) {
            // set new value
            const oldValue = this.value
            this.value = value
            //在$watch的时候用的回调函数
            this.cb.call(this.vm, value, oldValue)
          }
        }
      }
      /**
       * Depend on all deps collected by this watcher.
       */
      depend () {
        let i = this.deps.length
        while (i--) {
          this.deps[i].depend()
        }
      }
    }
    

    我们来想一下什么时候expOrFn是字符串呢?答案是:Vue实例的watch中写表达式的时候。那什么时候expOrFn又是函数呢?在computed的时候,还有在mount的时候,将undateComponent作为expOrFn传入。
    我们在初始化Watcher的时候会执行expOrFn,会发生什么呢?其实会去获取data的对象的属性值,那么就会执行观察者的get(),让我们再看一下get部分的代码:

    get(){
      if (Dep.target) {
        dep.depend()
      }
    }
    

    这里就收集了订阅者Watcher,同时也收集了依赖

    依赖到底是什么?收集依赖又是什么?

    Dep是data对象中的每一个属性数据,Watcher可以是一个updateComponent模板,也可以是函数,也可以是表达式,无论是那种情况,都依赖data中的属性数据,也就是说,Watcher的依赖就是这些数据。
    直接用一个例子来说:

    new Vue({
      data(){
        return {
          a: 1,
          b: 2
        }
      },
      computed: {
        sum(){
          return this.a + this.b
        }
      }
    })
    

    sum()就是一个Watcher,this.athis.b就是这个Watcher的Dep。

    Dep.target为什么是唯一的

    我们在收集订阅这的时候,需要知道这个依赖的订阅者是谁。

    用一张图说明一下

    相关文章

      网友评论

          本文标题:Vue源码分析数据响应

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