vue源碼之?dāng)?shù)據(jù)綁定

Vue.js是一款MVVM框架喝检,上手快速簡單易用狂巢,通過響應(yīng)式在修改數(shù)據(jù)的時(shí)候更新視圖率碾。Vue.js的響應(yīng)式原理依賴于Object.defineProperty等限,尤大大在Vue.js文檔中就已經(jīng)提到過,這也是Vue.js不支持IE8 以及更低版本瀏覽器的原因释漆。Vue通過設(shè)定對象屬性的 setter/getter 方法來監(jiān)聽數(shù)據(jù)的變化悲没,通過getter進(jìn)行依賴收集,而每個(gè)setter方法就是一個(gè)觀察者男图,在數(shù)據(jù)變更的時(shí)候通知訂閱者更新視圖示姿。

將數(shù)據(jù)data變成可觀察(observable)的

那么Vue是如何將所有data下面的所有屬性變成可觀察的(observable)呢?

function observe(value, cb) {
    Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
}

function defineReactive (obj, key, val, cb) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: ()=>{
            /*....依賴收集等....*
            return val
        },
        set:newVal=> {
            val = newVal;
            cb();/*訂閱者收到消息的回調(diào)*/
        }
    })
}

class Vue {
    constructor(options) {
        this._data = options.data;
        observe(this._data, options.render)
    }
}

let app = new Vue({
    el: '#app',
    data: {
        text: 'text',
        text2: 'text2'
    },
    render(){
        console.log("render");
    }
})

為了便于理解逊笆,首先考慮一種最簡單的情況栈戳,不考慮數(shù)組等情況,代碼如上所示难裆。在initData中會調(diào)用observe這個(gè)函數(shù)將Vue的數(shù)據(jù)設(shè)置成observable的子檀。當(dāng)_data數(shù)據(jù)發(fā)生改變的時(shí)候就會觸發(fā)set,對訂閱者進(jìn)行回調(diào)(在這里是render)乃戈。

那么問題來了褂痰,需要對app._data.text操作才會觸發(fā)set。為了偷懶症虑,我們需要一種方便的方法通過app.text直接設(shè)置就能觸發(fā)set對視圖進(jìn)行重繪缩歪。那么就需要用到代理。

代理

我們可以在Vue的構(gòu)造函數(shù)constructor中為data執(zhí)行一個(gè)代理proxy谍憔。這樣我們就把data上面的屬性代理到了vm實(shí)例上匪蝙。

_proxy.call(this, options.data);/*構(gòu)造函數(shù)中*/

/*代理*/
function _proxy (data) {
    const that = this;
    Object.keys(data).forEach(key => {
        Object.defineProperty(that, key, {
            configurable: true,
            enumerable: true,
            get: function proxyGetter () {
                return that._data[key];
            },
            set: function proxySetter (val) {
                that._data[key] = val;
            }
        })
    });
}

我們就可以用app.text代替app._data.text了主籍。

為什么要依賴收集

先看下面這段代碼

new Vue({
    template: 
        `<div>
            <span>text1:</span> {{text1}}
            <span>text2:</span> {{text2}}
        <div>`,
    data: {
        text1: 'text1',
        text2: 'text2',
        text3: 'text3'
    }
});

按照之前的方法進(jìn)行綁定則會出現(xiàn)一個(gè)問題——text3在實(shí)際模板中并沒有被用到,然而當(dāng)text3的數(shù)據(jù)被修改(this.text3 = 'test')的時(shí)候逛球,同樣會觸發(fā)text3的setter導(dǎo)致重新執(zhí)行渲染千元,這顯然不正確。

先說說Dep

當(dāng)對data上的對象進(jìn)行修改值的時(shí)候會觸發(fā)它的setter颤绕,那么取值的時(shí)候自然就會觸發(fā)getter事件诅炉,所以我們只要在最開始進(jìn)行一次render,那么所有被渲染所依賴的data中的數(shù)據(jù)就會被getter收集到Dep的subs中去屋厘。在對data中的數(shù)據(jù)進(jìn)行修改的時(shí)候setter只會觸發(fā)Dep的subs的函數(shù)涕烧。

定義一個(gè)依賴收集類Dep。

class Dep {
    constructor () {
        this.subs = [];
    }

    addSub (sub: Watcher) {
        this.subs.push(sub)
    }

    removeSub (sub: Watcher) {
        remove(this.subs, sub)
    }
    notify () {
        // stabilize the subscriber list first
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
            subs[i].update()
        }
    }
}
function remove (arr, item) {
    if (arr.length) {
        const index = arr.indexOf(item)
        if (index > -1) {
            return arr.splice(index, 1)
        }
    }
}

Watcher

訂閱者汗洒,當(dāng)依賴收集的時(shí)候會addSub到sub中议纯,在修改data中數(shù)據(jù)的時(shí)候會觸發(fā)dep對象的notify,通知所有Watcher對象去修改對應(yīng)視圖溢谤。

class Watcher {
    constructor (vm, expOrFn, cb, options) {
        this.cb = cb;
        this.vm = vm;

        /*在這里將觀察者本身賦值給全局的target瞻凤,只有被target標(biāo)記過的才會進(jìn)行依賴收集*/
        Dep.target = this;
        /*Github:https://github.com/answershuto*/
        /*觸發(fā)渲染操作進(jìn)行依賴收集*/
        this.cb.call(this.vm);
    }

    update () {
        this.cb.call(this.vm);
    }
}

開始依賴收集

class Vue {
    constructor(options) {
        this._data = options.data;
        observer(this._data, options.render);
        let watcher = new Watcher(this, );
    }
}

function defineReactive (obj, key, val, cb) {
    /*在閉包內(nèi)存儲一個(gè)Dep對象*/
    const dep = new Dep();

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: ()=>{
            if (Dep.target) {
                /*Watcher對象存在全局的Dep.target中*/
                dep.addSub(Dep.target);
            }
        },
        set:newVal=> {
            /*只有之前addSub中的函數(shù)才會觸發(fā)*/
            dep.notify();
        }
    })
}

Dep.target = null;

將觀察者Watcher實(shí)例賦值給全局的Dep.target,然后觸發(fā)render操作只有被Dep.target標(biāo)記過的才會進(jìn)行依賴收集世杀。有Dep.target的對象會將Watcher的實(shí)例push到subs中阀参,在對象被修改觸發(fā)setter操作的時(shí)候dep會調(diào)用subs中的Watcher實(shí)例的update方法進(jìn)行渲染。

數(shù)據(jù)綁定原理

前面已經(jīng)講過Vue數(shù)據(jù)綁定的原理了瞻坝,現(xiàn)在從源碼來看一下數(shù)據(jù)綁定在Vue中是如何實(shí)現(xiàn)的蛛壳。

首先看一下Vue.js官網(wǎng)介紹響應(yīng)式原理的這張圖。

這張圖比較清晰地展示了整個(gè)流程所刀,首先通過一次渲染操作觸發(fā)Data的getter(這里保證只有視圖中需要被用到的data才會觸發(fā)getter)進(jìn)行依賴收集衙荐,這時(shí)候其實(shí)Watcher與data可以看成一種被綁定的狀態(tài)(實(shí)際上是data的閉包中有一個(gè)Deps訂閱者,在修改的時(shí)候會通知所有的Watcher觀察者)浮创,在data發(fā)生變化的時(shí)候會觸發(fā)它的setter忧吟,setter通知Watcher,Watcher進(jìn)行回調(diào)通知組件重新渲染的函數(shù)斩披,之后根據(jù)diff算法來決定是否發(fā)生視圖的更新溜族。

Vue在初始化組件數(shù)據(jù)時(shí),在生命周期的beforeCreatecreated鉤子函數(shù)之間實(shí)現(xiàn)了對data垦沉、props煌抒、computed、methods乡话、events以及watch的處理摧玫。

initData

這里來講一下initData耳奕,可以參考源碼instance下的state.js文件绑青,

initData主要是初始化data中的數(shù)據(jù)诬像,將數(shù)據(jù)進(jìn)行Observer,監(jiān)聽數(shù)據(jù)的變化闸婴,其他的監(jiān)視原理一致坏挠,這里以data為例。

function initData (vm: Component) {

  /*得到data數(shù)據(jù)*/
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}

  /*判斷是否是對象*/
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }

  // proxy data on instance
  /*遍歷data對象*/
  const keys = Object.keys(data)
  const props = vm.$options.props
  let i = keys.length

  //遍歷data中的數(shù)據(jù)
  while (i--) {
    /*保證data中的key不與props中的key重復(fù)邪乍,props優(yōu)先降狠,如果有沖突會產(chǎn)生warning*/
    if (props && hasOwn(props, keys[i])) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${keys[i]}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(keys[i])) {
      /*判斷是否是保留字段*/

      /*這里是我們前面講過的代理,將data上面的屬性代理到了vm實(shí)例上*/
      proxy(vm, `_data`, keys[i])
    }
  }
  // observe data
  /*從這里開始我們要observe了庇楞,開始對數(shù)據(jù)進(jìn)行綁定榜配,這里有尤大大的注釋asRootData,這步作為根數(shù)據(jù)吕晌,下面會進(jìn)行遞歸observe進(jìn)行對深層對象的綁定蛋褥。*/
  observe(data, true /* asRootData */)
}

其實(shí)這段代碼主要做了兩件事,一是將_data上面的數(shù)據(jù)代理到vm上睛驳,另一件事通過observe將所有數(shù)據(jù)變成observable烙心。

proxy

接下來看一下proxy代理。

/*添加代理*/
export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

這里比較好理解乏沸,通過proxy函數(shù)將data上面的數(shù)據(jù)代理到vm上淫茵,這樣就可以用app.text代替app._data.text了。

observe

接下來是observe蹬跃,這個(gè)函數(shù)定義在core文件下observer的index.js文件中匙瘪。

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
 /*
 嘗試創(chuàng)建一個(gè)Observer實(shí)例(__ob__),如果成功創(chuàng)建Observer實(shí)例則返回新的Observer實(shí)例蝶缀,如果已有Observer實(shí)例則返回現(xiàn)有的Observer實(shí)例辆苔。
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  /*判斷是否是一個(gè)對象*/
  if (!isObject(value)) {
    return
  }
  let ob: Observer | void

  /*這里用__ob__這個(gè)屬性來判斷是否已經(jīng)有Observer實(shí)例,如果沒有Observer實(shí)例則會新建一個(gè)Observer實(shí)例并賦值給__ob__這個(gè)屬性扼劈,如果已有Observer實(shí)例則直接返回該Observer實(shí)例*/
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (

    /*這里的判斷是為了確保value是單純的對象驻啤,而不是函數(shù)或者是Regexp等情況。*/
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {

    /*如果是根數(shù)據(jù)則計(jì)數(shù)荐吵,后面Observer中的observe的asRootData非true*/
    ob.vmCount++
  }
  return ob
}

Vue的響應(yīng)式數(shù)據(jù)都會有一個(gè)ob的屬性作為標(biāo)記骑冗,里面存放了該屬性的觀察器,也就是Observer的實(shí)例先煎,防止重復(fù)綁定贼涩。

Observer

接下來看一下新建的Observer。Observer的作用就是遍歷對象的所有屬性將其進(jìn)行雙向綁定薯蝎。

/**
 * Observer class that are attached to each observed
 * object. Once attached, the observer converts target
 * object's property keys into getter/setters that
 * collect dependencies and dispatches updates.
 */
export class  {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0

    /*
    將Observer實(shí)例綁定到data的__ob__屬性上面去遥倦,之前說過observe的時(shí)候會先檢測是否已經(jīng)有__ob__對象存放Observer實(shí)例了,def方法定義可以參考https://github.com/vuejs/vue/blob/dev/src/core/util/lang.js#L16
    */
    def(value, '__ob__', this)
    if (Array.isArray(value)) {

      /*
          如果是數(shù)組,將修改后可以截獲響應(yīng)的數(shù)組方法替換掉該數(shù)組的原型中的原生方法袒哥,達(dá)到監(jiān)聽數(shù)組數(shù)據(jù)變化響應(yīng)的效果缩筛。
          這里如果當(dāng)前瀏覽器支持__proto__屬性,則直接覆蓋當(dāng)前數(shù)組對象原型上的原生數(shù)組方法堡称,如果不支持該屬性瞎抛,則直接覆蓋數(shù)組對象的原型。
      */
      const augment = hasProto
        ? protoAugment  /*直接覆蓋原型的方法來修改目標(biāo)對象*/
        : copyAugment   /*定義(覆蓋)目標(biāo)對象或數(shù)組的某一個(gè)方法*/
      augment(value, arrayMethods, arrayKeys)
      /*如果是數(shù)組則需要遍歷數(shù)組的每一個(gè)成員進(jìn)行observe*/
      this.observeArray(value)
    } else {

      /*如果是對象則直接walk進(jìn)行綁定*/
      this.walk(value)
    }
  }

  /**
   * Walk through each property and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)

    /*walk方法會遍歷對象的每一個(gè)屬性進(jìn)行defineReactive綁定*/
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {

    /*數(shù)組需要遍歷每一個(gè)成員進(jìn)行observe*/
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

Observer為數(shù)據(jù)加上響應(yīng)式屬性進(jìn)行雙向綁定却紧。如果是對象則進(jìn)行深度遍歷桐臊,為每一個(gè)子對象都綁定上方法,如果是數(shù)組則為每一個(gè)成員都綁定上方法晓殊。

如果是修改一個(gè)數(shù)組的成員断凶,該成員是一個(gè)對象,那只需要遞歸對數(shù)組的成員進(jìn)行雙向綁定即可巫俺。但這時(shí)候出現(xiàn)了一個(gè)問題:如果我們進(jìn)行pop懒浮、push等操作的時(shí)候,push進(jìn)去的對象根本沒有進(jìn)行過雙向綁定识藤,更別說pop了砚著,那么我們?nèi)绾伪O(jiān)聽數(shù)組的這些變化呢? Vue.js提供的方法是重寫push痴昧、pop稽穆、shift、unshift赶撰、splice舌镶、sort、reverse這七個(gè)數(shù)組方法豪娜。修改數(shù)組原型方法的代碼可以參考observer/array.js以及observer/index.js餐胀。

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    //.......

    if (Array.isArray(value)) {
      /*
          如果是數(shù)組,將修改后可以截獲響應(yīng)的數(shù)組方法替換掉該數(shù)組的原型中的原生方法瘤载,達(dá)到監(jiān)聽數(shù)組數(shù)據(jù)變化響應(yīng)的效果否灾。
          這里如果當(dāng)前瀏覽器支持__proto__屬性,則直接覆蓋當(dāng)前數(shù)組對象原型上的原生數(shù)組方法鸣奔,如果不支持該屬性墨技,則直接覆蓋數(shù)組對象的原型。
      */
      const augment = hasProto
        ? protoAugment  /*直接覆蓋原型的方法來修改目標(biāo)對象*/
        : copyAugment   /*定義(覆蓋)目標(biāo)對象或數(shù)組的某一個(gè)方法*/
      augment(value, arrayMethods, arrayKeys)

      /*如果是數(shù)組則需要遍歷數(shù)組的每一個(gè)成員進(jìn)行observe*/
      this.observeArray(value)
    } else {
      /*如果是對象則直接walk進(jìn)行綁定*/
      this.walk(value)
    }
  }
}

/**
 * Augment an target Object or Array by intercepting
 * the prototype chain using __proto__
 */
 /*直接覆蓋原型的方法來修改目標(biāo)對象或數(shù)組*/
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * Augment an target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
/*定義(覆蓋)目標(biāo)對象或數(shù)組的某一個(gè)方法*/
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}
/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { def } from '../util/index'

/*取得原生數(shù)組的原型*/
const arrayProto = Array.prototype
/*創(chuàng)建一個(gè)新的數(shù)組對象挎狸,修改該對象上的數(shù)組的七個(gè)方法扣汪,防止污染原生數(shù)組方法*/
export const arrayMethods = Object.create(arrayProto)

/**
 * Intercept mutating methods and emit events
 */
 /*這里重寫了數(shù)組的這些方法,在保證不污染原生數(shù)組原型的情況下重寫數(shù)組的這些方法锨匆,截獲數(shù)組的成員發(fā)生的變化崭别,執(zhí)行原生數(shù)組操作的同時(shí)dep通知關(guān)聯(lián)的所有觀察者進(jìn)行響應(yīng)式處理*/
[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  /*將數(shù)組的原生方法緩存起來,后面要調(diào)用*/
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments:
    // http://jsperf.com/closure-with-arguments
    let i = arguments.length
    const args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    /*調(diào)用原生的數(shù)組方法*/
    const result = original.apply(this, args)

    /*數(shù)組新插入的元素需要重新進(jìn)行observe才能響應(yīng)式*/
    const ob = this.__ob__
    let 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)

    // notify change
    /*dep通知所有注冊的觀察者進(jìn)行響應(yīng)式處理*/
    ob.dep.notify()
    return result
  })
})

從數(shù)組的原型新建一個(gè)Object.create(arrayProto)對象,通過修改此原型可以保證原生數(shù)組方法不被污染茅主。如果當(dāng)前瀏覽器支持proto這個(gè)屬性的話就可以直接覆蓋該屬性則使數(shù)組對象具有了重寫后的數(shù)組方法舞痰。如果沒有該屬性的瀏覽器,則必須通過遍歷def所有需要重寫的數(shù)組方法暗膜,這種方法效率較低,所以優(yōu)先使用第一種鞭衩。

在保證不污染不覆蓋數(shù)組原生方法添加監(jiān)聽学搜,主要做了兩個(gè)操作,第一是通知所有注冊的觀察者進(jìn)行響應(yīng)式處理论衍,第二是如果是添加成員的操作瑞佩,需要對新成員進(jìn)行observe。

但是修改了數(shù)組的原生方法以后我們還是沒法像原生數(shù)組一樣直接通過數(shù)組的下標(biāo)或者設(shè)置length來修改數(shù)組坯台,可以通過Vue.set以及splice方法炬丸。

Watcher

Watcher是一個(gè)觀察者對象。依賴收集以后Watcher對象會被保存在Deps中蜒蕾,數(shù)據(jù)變動的時(shí)候會由Deps通知Watcher實(shí)例稠炬,然后由Watcher實(shí)例回調(diào)cb進(jìn)行視圖的更新。

export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: ISet;
  newDepIds: ISet;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    /*_watchers存放訂閱者實(shí)例*/
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    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
    /*把表達(dá)式expOrFn解析成getter*/
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
   /*獲得getter的值并且重新進(jìn)行依賴收集*/
  get () {
    /*將自身watcher觀察者實(shí)例設(shè)置給Dep.target咪啡,用以依賴收集首启。*/
    pushTarget(this)
    let value
    const vm = this.vm

    /*
      執(zhí)行了getter操作,看似執(zhí)行了渲染操作撤摸,其實(shí)是執(zhí)行了依賴收集毅桃。
      在將Dep.target設(shè)置為自身觀察者實(shí)例以后,執(zhí)行g(shù)etter操作准夷。
      譬如說現(xiàn)在的的data中可能有a钥飞、b、c三個(gè)數(shù)據(jù)衫嵌,getter渲染需要依賴a跟c读宙,
      那么在執(zhí)行g(shù)etter的時(shí)候就會觸發(fā)a跟c兩個(gè)數(shù)據(jù)的getter函數(shù),
      在getter函數(shù)中即可判斷Dep.target是否存在然后完成依賴收集楔绞,
      將該觀察者對象放入閉包中的Dep的subs中去论悴。
    */
    if (this.user) {
      try {
        value = this.getter.call(vm, vm)
      } catch (e) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      }
    } else {
      value = this.getter.call(vm, vm)
    }
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    /*如果存在deep,則觸發(fā)每個(gè)深層對象的依賴墓律,追蹤其變化*/
    if (this.deep) {
      /*遞歸每一個(gè)對象或者數(shù)組膀估,觸發(fā)它們的getter,使得對象或數(shù)組的每一個(gè)成員都被依賴收集耻讽,形成一個(gè)“深(deep)”依賴關(guān)系*/
      traverse(value)
    }

    /*將觀察者實(shí)例從target棧中取出并設(shè)置給Dep.target*/
    popTarget()
    this.cleanupDeps()
    return value
  }

  /**
   * Add a dependency to this directive.
   */
   /*添加一個(gè)依賴關(guān)系到Deps集合中*/
  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]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
   /*
      調(diào)度者接口察纯,當(dāng)依賴發(fā)生改變的時(shí)候進(jìn)行回調(diào)。
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      /*同步則執(zhí)行run直接渲染視圖*/
      this.run()
    } else {
      /*異步推送到觀察者隊(duì)列中,由調(diào)度者調(diào)用饼记。*/
      queueWatcher(this)
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
   /*
      調(diào)度者工作接口香伴,將被調(diào)度者回調(diào)。
    */
  run () {
    if (this.active) {
      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.
        /*
            即便值相同具则,擁有Deep屬性的觀察者以及在對象/數(shù)組上的觀察者應(yīng)該被觸發(fā)更新即纲,因?yàn)樗鼈兊闹悼赡馨l(fā)生改變。
        */
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        /*設(shè)置新的值*/
        this.value = value

        /*觸發(fā)回調(diào)渲染視圖*/
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
   /*獲取觀察者的值*/
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   */
   /*收集該watcher的所有deps依賴*/
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
   /*將自身從所有依賴收集訂閱列表刪除*/
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      /*從vm實(shí)例的觀察者列表中將自身移除博肋,由于該操作比較耗費(fèi)資源低斋,所以如果vm實(shí)例正在被銷毀則跳過該步驟。*/
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

Dep

來看看Dep類匪凡。其實(shí)Dep就是一個(gè)發(fā)布者膊畴,可以訂閱多個(gè)觀察者,依賴收集之后Deps中會存在一個(gè)或多個(gè)Watcher對象病游,在數(shù)據(jù)變更的時(shí)候通知所有的Watcher唇跨。

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  /*添加一個(gè)觀察者對象*/
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  /*移除一個(gè)觀察者對象*/
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  /*依賴收集,當(dāng)存在Dep.target的時(shí)候添加觀察者對象*/
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  /*通知所有訂閱者*/
  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
/*依賴收集完需要將Dep.target設(shè)為null衬衬,防止后面重復(fù)添加依賴买猖。*/

defineReactive

接下來是defineReactive。defineReactive的作用是通過Object.defineProperty為數(shù)據(jù)定義上getter\setter方法滋尉,進(jìn)行依賴收集后閉包中的Deps會存放Watcher對象政勃。觸發(fā)setter改變數(shù)據(jù)的時(shí)候會通知Deps訂閱者通知所有的Watcher觀察者對象進(jìn)行試圖的更新。

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: Function
) {
  /*在閉包中定義一個(gè)dep對象*/
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  /*如果之前該對象已經(jīng)預(yù)設(shè)了getter以及setter函數(shù)則將其取出來兼砖,新定義的getter/setter中會將其執(zhí)行奸远,保證不會覆蓋之前已經(jīng)定義的getter/setter。*/
  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set

  /*對象的子對象遞歸進(jìn)行observe并返回子節(jié)點(diǎn)的Observer對象*/
  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {

      /*如果原本對象擁有g(shù)etter方法則執(zhí)行*/
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {

        /*進(jìn)行依賴收集*/
        dep.depend()
        if (childOb) {

          /*子對象進(jìn)行依賴收集讽挟,其實(shí)就是將同一個(gè)watcher觀察者實(shí)例放進(jìn)了兩個(gè)depend中懒叛,一個(gè)是正在本身閉包中的depend,另一個(gè)是子元素的depend*/
          childOb.dep.depend()
        }
        if (Array.isArray(value)) {

          /*是數(shù)組則需要對每一個(gè)成員都進(jìn)行依賴收集耽梅,如果數(shù)組的成員還是數(shù)組薛窥,則遞歸。*/
          dependArray(value)
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {

      /*通過getter方法獲取當(dāng)前值眼姐,與新值進(jìn)行比較诅迷,一致則不需要執(zhí)行下面的操作*/
      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方法則執(zhí)行setter*/
        setter.call(obj, newVal)
      } else {
        val = newVal
      }

      /*新的值需要重新進(jìn)行observe,保證數(shù)據(jù)響應(yīng)式*/
      childOb = observe(newVal)

      /*dep對象通知所有的觀察者*/
      dep.notify()
    }
  })
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末众旗,一起剝皮案震驚了整個(gè)濱河市罢杉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贡歧,老刑警劉巖滩租,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赋秀,死亡現(xiàn)場離奇詭異,居然都是意外死亡律想,警方通過查閱死者的電腦和手機(jī)猎莲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來技即,“玉大人著洼,你說我怎么就攤上這事《穑” “怎么了身笤?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長澈歉。 經(jīng)常有香客問我展鸡,道長屿衅,這世上最難降的妖魔是什么埃难? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮涤久,結(jié)果婚禮上涡尘,老公的妹妹穿的比我還像新娘。我一直安慰自己响迂,他們只是感情好考抄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蔗彤,像睡著了一般川梅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上然遏,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天贫途,我揣著相機(jī)與錄音,去河邊找鬼待侵。 笑死丢早,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的秧倾。 我是一名探鬼主播怨酝,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼那先!你這毒婦竟也來了农猬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤售淡,失蹤者是張志新(化名)和其女友劉穎盛险,沒想到半個(gè)月后瞄摊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苦掘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年换帜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹤啡。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡惯驼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出递瑰,到底是詐尸還是另有隱情祟牲,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布抖部,位于F島的核電站说贝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏慎颗。R本人自食惡果不足惜乡恕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望俯萎。 院中可真熱鬧傲宜,春花似錦、人聲如沸夫啊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撇眯。三九已至报嵌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間熊榛,已是汗流浹背锚国。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留来候,地道東北人跷叉。 一個(gè)月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像营搅,于是被迫代替她去往敵國和親云挟。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容