上篇介绍了创立vue实例时大略做了一些什么事件,其中有一项是初始化数据,本篇来看一下数据察看具体是怎么做的。

_initData就是数据察看的终点了:

exports._initData = function () {  // 代理data到实例  var data = this._data  var keys = Object.keys(data)  var i = keys.length  var key  while (i--) {    key = keys[i]    if (!_.isReserved(key)) {      this._proxy(key)    }  }  // 察看data数据  Observer.create(data).addVm(this)}

_proxy办法上一篇曾经说过了,就是把data数据代理到vue实例上,能够通过this.xx拜访到this.data.xx的数据,要害是Observer

createObserver类的静态方法,用来给一个数组或对象创立察看对象:

Observer.create = function (value) {  if (    value &&    value.hasOwnProperty('__ob__') &&    value.__ob__ instanceof Observer  ) {    return value.__ob__  } else if (_.isArray(value)) {    return new Observer(value, ARRAY)  } else if (    _.isPlainObject(value) &&    !value._isVue  ) {    return new Observer(value, OBJECT)  }}

从这里能够晓得vue只会对数组和纯正的对象进行察看,其余比方函数什么的是不会察看的,其次要逻辑是判断该属性是否曾经察看过了,是的话就返回观察者对象,否则别离对数组和对象应用不同的标记来实例化察看对象。

来看Observer类:

function Observer (value, type) {  this.id = ++uid  this.value = value  this.deps = []  // 将该察看实例设置到该对象或数组的一个属性,不便前面检查和应用  _.define(value, '__ob__', this)  if (type === ARRAY) {// 数组分支    var augment = _.hasProto      ? protoAugment      : copyAugment    augment(value, arrayMethods, arrayKeys)    this.observeArray(value)  } else if (type === OBJECT) {// 对象分支    this.walk(value)  }}

初始化了一些属性,先看一下比较简单的对象分支:

p.walk = function (obj) {  var keys = Object.keys(obj)  var i = keys.length  var key, prefix  while (i--) {    key = keys[i]    prefix = key.charCodeAt(0)    if (prefix !== 0x24 && prefix !== 0x5F) { // 跳过 $ or _结尾的公有属性      this.convert(key, obj[key])    }  }}

walk办法对对象的每个子属性遍历调用convert办法:

p.convert = function (key, val) {  var ob = this  // 如果该属性的值也是个数组或对象,那么也须要进行察看,observe办法最终调用的也是Object.create办法  var childOb = ob.observe(val)  // 每个属性都会创立一个依赖收集实例,利用闭包来保留  var dep = new Dep()  // 该属性的察看实例增加到属性值的察看对象里  if (childOb) {    childOb.deps.push(dep)  }  Object.defineProperty(ob.value, key, {    enumerable: true,    configurable: true,    get: function () {      // 这里进行收集依赖,Observer.target是一个全局属性,是一个watcher实例,后续再细说,当援用该属性前把watcher实例赋值给这个全局属性,此处就能援用到,而后收集到该属性的dep实例列表里      if (Observer.target) {        Observer.target.addDep(dep)      }      return val    },    set: function (newVal) {      if (newVal === val) return      // 如果旧的值是对象或数组那么必定也有对应的察看实例,所以须要从对应的察看实例里移除该属性的dep      var oldChildOb = val && val.__ob__      if (oldChildOb) {        var oldDeps = oldChildOb.deps        oldDeps.splice(oldDeps.indexOf(dep), 1)      }      val = newVal      // 查看新值,新赋的值是对象或数组又须要进行递归创立察看实例      var newChildOb = ob.observe(newVal)      if (newChildOb) {        newChildOb.deps.push(dep)      }      // 告诉该属性的依赖进行更新      dep.notify()    }  })}

接下来看一下数组的分支:

if (type === ARRAY) {    var augment = _.hasProto    ? protoAugment    : copyAugment    augment(value, arrayMethods, arrayKeys)    this.observeArray(value)}

vue批改了数组原型上的一些办法,比方:pushshift等等,起因是应用这些办法操作数组不会触发该属性的setter,所以vue就无奈检测到变动进行更新,所以须要拦挡这些办法进行批改。

这里应用了两种办法,如果浏览器反对__proto__,间接通过批改数组的__proto__来设置新的原型对象,如果不反对,则应用Object.defineProperty来笼罩增加批改后的数组办法。

var arrayProto = Array.prototype// 创立一个以数组原型对象为原型的新对象var arrayMethods = Object.create(arrayProto);[  'push',  'pop',  'shift',  'unshift',  'splice',  'sort',  'reverse'].forEach(function (method) {  // 缓存数组的原始办法  var original = arrayProto[method]  _.define(arrayMethods, method, function mutator () {    //  这里将arguments拷贝了一份,防止将该对象间接传递给其余函数应用,可能对性能不利    var i = arguments.length    var args = new Array(i)    while (i--) {      args[i] = arguments[i]    }    // 调用原始办法    var result = original.apply(this, args)    // 获取该数组的察看实例    var ob = this.__ob__    // 获取新插入数组的值    var inserted    switch (method) {      case 'push':        inserted = args        break      case 'unshift':        inserted = args        break      case 'splice':        inserted = args.slice(2)        break    }    // 如果有新插入的值,那么对它递归进行察看    if (inserted) ob.observeArray(inserted)    // 告诉依赖更新    ob.notify()    return result  })})

逻辑很简略,就是当调用了这些办法更新数组后察看新插入的数据,以及告诉更新,这里是调用察看对象ob的更新办法notify

p.notify = function () {  var deps = this.deps  for (var i = 0, l = deps.length; i < l; i++) {    deps[i].notify()  }}

通过下面的convert办法咱们晓得这个deps数组里收集的是该属性值对应的属性的依赖收集实例dep,有点绕:

{    data: {        a: [1, 2, 3],        b: {            c: [4, 5, 6]        }    }}

比方这个例子,疏忽b的话,一共存在两个Observer实例,一个是属性data的值的,另一个是 [1, 2, 3]的, [1, 2, 3]Observer实例的deps数组收集了adep,咱们应用上述数组的办法更新了这个数组,会告诉adep进行更新告诉,这很容易了解,如果咱们给a设置了新值,比方:data.a = 2是会触发asetter的,外面会调用adepnotify办法,只是当初这个a的值变成了数组,数组变动了就相当于a变动了,但问题是数组变动并不会触发asetter,所以就只能手动去调用adep的更新办法去告诉a的依赖也去更新,然而,比方c的数组变动了,会告诉c的依赖更新,然而不会向上再去告诉b的依赖更新。

数组的原型办法批改完后就须要去遍历该数组的元素进行察看:

p.observeArray = function (items) {  var i = items.length  while (i--) {    this.observe(items[i])  }}

很简略,遍历数组调用observe办法。

到这里,就实现了对data上所有数据的察看了,总结一下,从data对象开始,给该对象创立一个察看实例,而后遍历它的子属性,值是数组或对象的话又创立对应的察看实例,而后再持续遍历它们的子属性,持续递归,直到把每个属性都转换成gettersetter

在第一次渲染的时候会援用用到的值,也就是会触发对应属性的getter,援用前会把对应的watcher赋值到Observer.target属性,JavaScript代码执行是单线程的,所以同一时刻只会有一个Observer.target,所以只有某个属性的getter里获取到了此刻的Observer.target,那肯定代表该watcher是依赖该属性的,那么就增加到该属性的依赖收集对象dep里,这里奇妙的应用闭包来保留每个属性的dep实例,后续如果该属性值变动了,那么会触发setter,如果新赋值是对象或数组又会递归进行察看,最初再告诉该属性的所有依赖进行更新。

下面始终都提到了这个dep,当初来看一下:

function Dep () {  this.id = ++uid  this.subs = []}var p = Dep.prototypep.addSub = function (sub) {  this.subs.push(sub)}p.removeSub = function (sub) {  if (this.subs.length) {    var i = this.subs.indexOf(sub)    if (i > -1) this.subs.splice(i, 1)  }}p.notify = function () {  var subs = _.toArray(this.subs)  for (var i = 0, l = subs.length; i < l; i++) {    subs[i].update()  }}

这个类很简略,这就是全副代码,性能是收集订阅者、删除订阅者以及遍历调用订阅者的update办法。

最初看一下批改数组和对象的辅助办法,如:$set$remove等。

对于数组,间接应用索引设置数组项vue是不能检测到的,所以提供了$set办法:

_.define(  arrayProto,  '$set',  function $set (index, val) {    if (index >= this.length) {      this.length = index + 1    }    return this.splice(index, 1, val)[0]  })

给数组的原型上增加了$set办法,调用splice办法来设置值,这个办法因为曾经被重写过了,所以能够触发更新,咱们齐全能够间接应用splice办法。

对于对象,在data初始化后在增加新属性也是不能检测到的,在0.11版本提供各了$add办法:

_.define(  objProto,  '$add',  function $add (key, val) {    if (this.hasOwnProperty(key)) return    var ob = this.__ob__    if (!ob || _.isReserved(key)) {      this[key] = val      return    }    ob.convert(key, val)    if (ob.vms) {      var i = ob.vms.length      while (i--) {        var vm = ob.vms[i]        vm._proxy(key)        vm._digest()      }    } else {      ob.notify()    }  })

间接调用convert办法就能够了,设置完得告诉更新,这里分了两种状况,如果设置的是data的根属性,那么须要把该属性代理到vue实例上,另外须要告诉该实例及其所有子实例的watcher进行强制更新。如果不是根属性,那么调用所在对象的观察者实例的notify办法,告诉对象对应的属性的订阅者进行更新。

数据察看到这里就完结了,然而当初还不晓得,依赖到底是什么时候才进行收集的,Observer.target到底什么时候才会被赋值,如果数据更新了,watcher是什么,watcher又是怎么触发DOM更新以及怎么更新,问题还有很多,咱们下回再见。