vue源碼解讀 -- 整體架構(gòu)

vue在引入后會進行一些初始化操作,主要是在全局對象和vue原形鏈上掛載函數(shù),這些函數(shù)在后續(xù)的實例化過程中用到時再進行討論。

一個例子

如下是vue一個最簡單的例子,基于此例展開對vue整體架構(gòu)的分析

<div id="app">{{a}}</div>
<script>
  var model = new Vue({
    el: '#app',
    data: {
      a: 1
    }
  });
  model.a = 10;
</script>
1. 初始化

vue本身是個函數(shù),執(zhí)行new操作后調(diào)用其init函數(shù):

function Vue (options) {
  this._init(options)
}

_init函數(shù)的作用可以歸納為兩點:初始化狀態(tài)和掛載節(jié)點。

  • 初始化狀態(tài)是對options中的data、props(組件中用到)、methods、computed等進行初始化操作,偏數(shù)據(jù)層面。
  • 掛在節(jié)點會對dom進行分析,生成指令,同時生成關(guān)聯(lián)數(shù)據(jù)和指令的watcher對象。在數(shù)據(jù)改變時,會通過watcher通知directive進行視圖的更新。
  Vue.prototype._init = function (options) {
    ...
    // 狀態(tài)的初始化
    this._initState()

    // 掛在到節(jié)點
    if (options.el) {
      this.$mount(options.el)
    }
  }
2. 狀態(tài)的初始化

狀態(tài)初始化會的作用作用是把data下面的數(shù)據(jù)字段都設(shè)計成響應(yīng)式的:數(shù)據(jù)獲取(getter)的時候收集其關(guān)聯(lián)到的指令,數(shù)據(jù)變化(setter)的時候通知指令進行視圖的更新。
_initState主要包括以下幾個函數(shù)

Vue.prototype._initState = function () {
  this._initProps()  // 組件中的props初始化 
  this._initMeta()  // 不知道干嘛的
  this._initMethods()  // 把methods中的方法綁定到實例上
  this._initData()  // 數(shù)據(jù)初始化 下面分析
  this._initComputed()  // computed屬性的初始化
}

// _initMethods 可以直接在實例上獲取到methods中的方法
Vue.prototype._initMethods = function () {
  var methods = this.$options.methods
  if (methods) {
    for (var key in methods) {
      this[key] = bind(methods[key], this)
    }
  }
}

// _initData
Vue.prototype._initData = function () {
  ...
  // observe會遍歷data中的字段,對每個字段執(zhí)行defineReactive操作
  // 讀取字段時,會收集其依賴,這里的依賴是watcher對象的列表
  // 當設(shè)置字段值時, 會對依賴執(zhí)行notify()操作 
  observe(data, this)
}
function defineReactive (obj, key, val) {
  var dep = new Dep()
  ...
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend() // 收集字段的依賴
        if (childOb) {  // 收集子對象的依賴
          childOb.dep.depend()
        }
        ...
    },
    set: function reactiveSetter (newVal) {
      ...
      // 此處會調(diào)用依賴列表的watcher進行數(shù)據(jù)視圖的同步! 
      // watcher會調(diào)用directive的更新方法
      dep.notify()
    }
  })
}

// _initComputed
// 相對于普通的data字段,computed字段會在指令生成前額外生成一個watcher
// 此watcher也會加入到依賴列表中去;但是此watcher是lazy的。
// 非lazy的watcher會在watcher生成后執(zhí)行g(shù)et函數(shù)
// lazy的watcher  會在computed中的字段讀取值時才會調(diào)用到其getter函數(shù)
Vue.prototype._initComputed = function () {
  for (var key in computed) {
    var userDef = computed[key]
    var def = {
      enumerable: true,
      configurable: true
    }
    def.get = makeComputedGetter(userDef, this)
    Object.defineProperty(this, key, def)
  }
}

function makeComputedGetter (getter, owner) {
  var watcher = new Watcher(owner, getter, null, {
    lazy: true
  })
  return function computedGetter () {
    if (watcher.dirty) {
      watcher.evaluate()
    }
    if (Dep.target) {
      watcher.depend()
    }
    return watcher.value
  }
}
3. 節(jié)點掛載

節(jié)點掛載集中了dom結(jié)構(gòu)分析、指令的生成、指令和數(shù)據(jù)關(guān)聯(lián)的watcher對象生成等幾個功能。

Vue.prototype.$mount = function (el) {
  ...
  this._compile(el)
  ...
  return this
}

這個compile函數(shù)會對DOM節(jié)點進行解析,根據(jù)指令的不同(如:v-model、v-text、{{}})等生成link函數(shù);這些link函數(shù)最終都會執(zhí)行到下面代碼段中的函數(shù),這個函數(shù)的作用可以概括為:生成指令并和節(jié)點進行關(guān)聯(lián)。

  // el:指令所在的節(jié)點 例子中的{{a}}
  // descriptor: 例子中的解析得到的指令:
  //    expression:"a"
  //    filters:undefined
  //    name:"text"
  vm._bindDir(descriptor, el, host, scope, frag)

上述步驟執(zhí)行完后,vue會依據(jù)directive的priority進行排序,然后對指令執(zhí)行bind操作:

function linkAndCapture (linker, vm) {
  var originalDirCount = vm._directives.length
  linker()
  var dirs = vm._directives.slice(originalDirCount)
  dirs.sort(directiveComparator)
  for (var i = 0, l = dirs.length; i < l; i++) {
    // 指令的bind方法:把數(shù)據(jù)和視圖進行關(guān)聯(lián)
    // 生成watcher實例 通過watcher進行后續(xù)的數(shù)據(jù)和視圖同步:
    // 1、一個雙向綁定的元素,如input,通過監(jiān)聽其input or change事件, 有數(shù)據(jù)更新時,調(diào)用directive的set
    // 2、directive的set調(diào)用watcher的set方法
    // 
    dirs[i]._bind()
  }
  return dirs
}

bind()可以理解為數(shù)據(jù)和dom的一種綁定,其包括兩步:數(shù)據(jù)和watcher綁定、watcher和指令綁定。 也就是數(shù)據(jù)更新是通過watcher通知directive,進而更新view:

  Directive.prototype._bind = function () {
  ...
    // watcher的更新回調(diào)  用于directive的更新
    // 當數(shù)據(jù)更新時會執(zhí)行到此函數(shù)
    if (this.update) {
      this._update = function (val, oldVal) {
        if (!dir._locked) {
          dir.update(val, oldVal)
        }
      }
    }
    ...
    // 1. 生成指令的watcher對象 
    var watcher = this._watcher = new Watcher(
      this.vm,
      this.expression,
      this._update, // callback
      {
        filters: this.filters,
        twoWay: this.twoWay,
        deep: this.deep,
        preProcess: preProcess,
        postProcess: postProcess,
        scope: this._scope
      }
    )
    
    if (this.afterBind) {
      this.afterBind()
    } else if (this.update) {
      // 2. 已經(jīng)綁定了數(shù)據(jù)的依賴  進行view的更新
      this.update(watcher.value)
    }
  }
  this._bound = true
}

watcher實例生成時,會進行數(shù)據(jù)的依賴綁定:

export default function Watcher (vm, expOrFn, cb, options) {
  ...
  vm._watchers.push(this)
  ...
  // get函數(shù)的執(zhí)行主要是收集依賴
  this.value = this.lazy
    ? undefined
    : this.get()
}

Watcher.prototype.get = function () {
  this.beforeGet() // 依賴配置
  ...
  value = this.getter.call(scope, scope)  // 進行收集
}
// 在beforeGet中進行依賴的配置
Watcher.prototype.beforeGet = function () {
  // 這個設(shè)置 會讓getter執(zhí)行時 把watcher作為依賴
  Dep.target = this
  this.newDeps = Object.create(null)
}
// defineReactive中進行收集
export function defineReactive (obj, key, val) {
  ...
  Object.defineProperty(obj, key, {
      ...
      get: function reactiveGetter () {
        ...
        // 依賴收集
        if (Dep.target) {
          dep.depend()
  ...
}
4. 數(shù)據(jù)更新

實例中執(zhí)行model.a = 10;,它會首先進入defineReactive的setter

  set: function reactiveSetter (newVal) {
      ...
      // 此處會調(diào)用依賴列表的watcher進行數(shù)據(jù)視圖的同步! 
      // watcher會調(diào)用directive的更新方法
      dep.notify()
    }

如下是nodtify的定義,subs是依賴到的watcher列表:

Dep.prototype.notify = function () {
  // stablize the subscriber list first
  var subs = toArray(this.subs)
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}

watcher的update函數(shù)會把watcher放到一個列表中,然后統(tǒng)一依次調(diào)用callback執(zhí)行更新,這里的callback是Directive.prototype._bind中的update:

Watcher.prototype.update = function (shallow) {
  ...
  pushWatcher(this)
}

export function pushWatcher (watcher) {
  ...
    q.push(watcher)
    // queue the flush
    if (!waiting) {
      waiting = true
      nextTick(flushBatcherQueue)
    }
  }
}

function flushBatcherQueue () {
  runBatcherQueue(queue)
  ...
}

function runBatcherQueue (queue) {
  for (var i = 0; i < queue.length; i++) {
    var watcher = queue[i]
    ...
    watcher.run()
  }
}

Watcher.prototype.run = function () {
  ...
  // 此處的cb就是_bind中的update函數(shù)
  this.cb.call(this.vm, value, oldValue)
  ...
}

至此就實現(xiàn)了一個最基本的數(shù)據(jù)和視圖的綁定。如下是一張簡單的架構(gòu)圖:

架構(gòu)圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

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

  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容,還有我對于 Vue 1.0 印象不深的內(nèi)容。關(guān)于...
    云之外閱讀 5,069評論 0 29
  • 1.安裝 可以簡單地在頁面引入Vue.js作為獨立版本,Vue即被注冊為全局變量,可以在頁面使用了。 如果希望搭建...
    Awey閱讀 11,066評論 4 129
  • 隨筆2 昨晚看到這些圖片,發(fā)現(xiàn)原來在很多不知道的角落里,有很多人,沒有信念,沒有快樂,只是掙扎著在走下去。一刀一刀...
    風塵1998閱讀 169評論 0 1
  • 我們這個球隊是自發(fā)組織的業(yè)余羽毛球隊,隊員們的技術(shù)參差不齊,有的人打得特別好,象我這樣的打得特別蹩腳。但隊友...
    珠珠米閱讀 153評論 2 1
  • 題記:樹林美麗深邃幽靜,但我有承諾尚待實現(xiàn),行百里方可沉睡。 ——弗羅斯特 ...
    杜杜杜杜杜杜杜杜杜閱讀 935評論 0 2