Vue源码实现--依赖收集(1)

最近闲来在比较深入的学习vue的源码,受益匪浅,在这边记录一些心得,顺便给自己定个小目标--自己实现一个简单的vue框架,不考虑错误检查,不考虑边界情况,包含vue最主要最基础的流程。
第一篇文章,就来简单说说vue的依赖收集。
首先,何为依赖收集?
用例1:我们使用vue的时候,经常会用到$watch方法去监听属性的改变,比如:

app.$watch('a', function () {
  console.log('a is change')
})

用例2:再比如vue框架帮我们做的数据与视图的绑定,每次改变数据的时候,页面也会跟着刷新,这些都和vue的依赖收集机制有着密切的关系。
从$watch方法说起,现在有一个obj={a:1},要怎样监测到obj.a的改变,就需要使用Object.defineProperty为a这个属性设置get方法,在get方法里面就能知道a的改变了。
首先先把vm.data里的数据代理到vm下:

  function initData (vm) {
    var data = vm.$options.data
    // 通常每个data我们都是通过一个工厂方法返回一个新对象,避免重复实例化导致几个实例用了同一个data,因此这里要执行这个方法获得data对象
    data = vm._data = typeof data === 'function' ? data.call(vm) : data || {}
    var keys = Object.keys(data)
    var i = keys.length
    while(i--) {
      proxy(vm, keys[i]) 
    }
    observer(data)
  }
  function proxy (vm, key) {
    Object.defineProperty(vm, key, {
      configurable: true,
      enumerable: true,
      get: function () {
        return vm._data[key]
      },
      set: function (val) {
        vm._data[key] = val
      }
    })
  }

然后需要一个Observer类,Observer可直译为观察者(这里的观察者和观察者模式并不一样,不可混淆),Observer类做的事情就是使每个属性可被观测,并添加上ob这个属性:

  /* Observe */
  var Observer = (function () {
    var Observer = function (data) {
      Object.defineProperty(data, '__ob__', {
        enumerable: false, 
        value: this
      })
      this.walk(data)
    }
    Observer.prototype.walk = function (data) {
      var keys = Object.keys(data)
      var i = keys.length
      while (i--) {
        defineReactive(data, keys[i], data[keys[i]])
      }
    }
    return Observer
  })()
  function defineReactive (data, key, val) {
    observer(val)
    var dep = new Dep()
    Object.defineProperty(data, key, {
      get: function () {
        if (Dep.target) {
          dep.addSub(Dep.target)
        }
        return val
      },
      set: function (newVal) {
        if (val === newVal) {
          return
        }
        val = newVal
        dep.notify()
      }
    })
  }
  function observer (data) {
    if (typeof data !== 'object') {
      return
    }
    new Observer(data)
  }

在initData方法中调用observer方法。上面的代码会为每一个被Observer的属性在闭包中定义一个dep实例,这个dep在getter被调用的时候如果存在Dep.target,就会去收集当前的Dep.target到闭包中dep实例的subs数组中
上面的代码中可以看到,还有一个Dep类,Dep负责收集与该属性有依赖关系的Watcher,代码很简单:

  /*Dep*/
  var Dep = (function () {
    var Dep = function () {
      this.subs = []
    }
    // Dep.target是一个Watcher
    Dep.target = null
    Dep.prototype.addSub = function (sub) { // sub为Watcher
      this.subs.push(sub)
    }
    Dep.prototype.notify = function () {
      for (var i = 0, l=this.subs.length; i < l; i++) {
        this.subs[i].update()
      }
    }
    return Dep
  })()

Dep是一个典型的发布订阅模式,有个subs数组,是一个Watcher数组,用来收集所有这个属性上的订阅者,当这个属性发生变化时候,那么在属性的setter里就可以获取到这个变化,然后调用闭包中维护的dep实例的dep.notify方法调用所有的订阅者。
那么剩下的就是Watcher这个订阅者了,很多地方可能都会称为观察者,但是在这里为了避免和Observer类混淆,我就称为订阅者:

  /*Watcher*/
  var Watcher = (function () {
    var Watcher = function (vm, expOrFn, cb, options) {
      if (typeof expOrFn === 'string') {
        this.getter = function () {
          return vm[expOrFn]
        }
      } else if (typeof expOrFn === 'function') {
        // todo
      }
      this.vm = vm
      this.cb = cb
      this.value = this.get()
    }
    Watcher.prototype.get = function () {
      Dep.target = this
      let value = this.getter.call(this.vm)
      Dep.target = null
      return value
    }
    Watcher.prototype.update = function () {
      // todo
      this.cb()
    }
    return Watcher
  })()

Watcher的构造函数接收这几个参数,vm(vue实例),expOrFn(要被订阅的属性),cb(回调方法),还有options(配置),
在这里expOrFn是被观察的对象上的一个属性名称,是一个字符串;但是在我们之前说的依赖收集用例2中,数据与视图的绑定时候的依赖收集,这里的expOrFn就会直接是一个updateComponent方法,所以在这里统一把属性也转成一个方法this.getter。
然后调用Watcher的get方法,this.value = this.get(),
在get方法中先把Dep的静态属性target设置为当前的实例,然后执行this.getter方法,此时,属性的getter方法中就会执行:

if (Dep.target) {
     dep.addSub(Dep.target)
}

这样就把这个Watcher实例收集到了闭包的dep中了
此时执行如下代码,就会按我们预想的,输出a is change和a is change2了

    var app = new Vue({
      data: function () {
        return {
          a: 1,
          b: {
            c: 2
          }
        }
      }
    })
    app.$watch('a', function () {
      console.log('a is change')
    })
    app.$watch('a', function () {
      console.log('a is change2')
    })
    app.a = 3

总结一下基本的原理:
1.首先observer一个对象,使其中的参数都有get,set方法;
2.watch一个属性的时候,new 一个Watcher实例,并把Dep.target设置为当前的Watcher实例,然后获取一次该属性的值,触发get方法中的依赖收集。
另外附上本篇完整的例子 附件
上面就是一个简单的依赖收集的原理,如果去看vue的源码,就会发现复杂很多,有很多代码很多属性我这里并没有展示出来,这也是我看源码时候的一些疑问:
比如:
1.数组的改变要怎样被依赖收集
2.Computed属性的Watch要怎样实现
3.Observer的dep,和Watcher上的deps分别是做什么用的
4.Watcher中的user,lazy,deep,sync分别是什么用的
今天先写到这里,上面几个问题且看下面的几篇博文

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容