vue源碼閱讀——vue生命周期

這是官網(wǎng)上一個最簡單的例子

<div id="app">
  {{ message }}
</div>

<script>
    var app = new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue!'
      }
   })
 </script>

接下來我通過在chrome中一步步運行代碼來理清其內部邏輯哥蔚。
首先當然是調用構造函數(shù)

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options) // 主要就是這個步驟嚷量,調用原型中的_init方法
}

//下面的這些方法都是給Vue.prototype增加方法和屬性的,具體意義看vue官網(wǎng)api就能了解
initMixin(Vue) // 給原型增加了_init()
stateMixin(Vue) // 增加$data,$props,$set(),$delete(),$watch()
eventsMixin(Vue) // 增加$on(),$once(),$off(),$emit()
lifecycleMixin(Vue) // 增加_update(),$forceUpdate(),$destroy()
renderMixin(Vue) // 增加_render()

然后就進入_init()方法了

var uid$1 = 0;

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    var vm = this; // // 定義一個vm指向Vue實例(這里就是app)
    // a uid
    vm._uid = uid$1++;

    var startTag, endTag;
    /* istanbul ignore if */
    if ("development" !== 'production' && config.performance && mark) {
      startTag = "vue-perf-init:" + (vm._uid);
      endTag = "vue-perf-end:" + (vm._uid);
      mark(startTag);
    }

    // a flag to avoid this being observed
    vm._isVue = true;
    // merge options
    if (options && options._isComponent) { // 這個例子不是組件铺韧,所以先跳過
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options);
    } else {
      // 這個步驟炒雞重要钓丰,給vm增加了一個$options屬性疹蛉,這個屬性是合并之后的options磺平。詳細內容看1mergeOptions
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    /* istanbul ignore else */
    {
      initProxy(vm);
    }
    // expose real self
    vm._self = vm;
    // 下面這些都是和vue生命周期有關的一些操作了
    initLifecycle(vm) // 2初始化生命周期
    initEvents(vm) // 3初始化事件
    initRender(vm) // ?初始化render
    callHook(vm, 'beforeCreate') // ?beforeCreate
    initInjections(vm) // ?初始化inject
    initState(vm) // ?對data/props/computed/watch等進行監(jiān)聽
    initProvide(vm) // ?初始化provide
    callHook(vm, 'created') // ?create

    /* istanbul ignore if */
    if ("development" !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false);
      mark(endTag);
      measure(((vm._name) + " init"), startTag, endTag);
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el); // 1?掛載
    }
  };
}

1.mergeOptions
這個方法顧名思義就是合并分支的。那我們先看沒合并之前的vm

mergeOptions執(zhí)行前

再看這個方法運行之后變成啥樣了


mergeOptions執(zhí)行后

明顯能注意到的就是沃但,vm.$options比之前的options多了components,directives,filters,_base磁滚,然后data變成一個方法了。因此我們大概知道這個方法就是添加vm.$options然后把基礎的options和傳入的options合并(其實看函數(shù)名就差不多知道了)

2.initLifecycle

initLifecycle執(zhí)行前
initLifecycle執(zhí)行后

對比很明顯宵晚,增加了很多屬性垂攘,來看一眼代碼

function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm // 沒有父實例的話把根實例設成自己了

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

看樣子這個函數(shù)是很好理解的,主要目的是為了給vm增加$children淤刃、$parent晒他、$refs、$root钝凶。然后以下劃線開始的這些屬性看命名就能才出來是與vue生命周期有關的一些變量(下面我把以$開頭的稱為實例屬性,把_開頭的稱變量)唁影。這里放一張官網(wǎng)上的生命周期圖片耕陷,具體的在之后的內容中也會涉及到的

vue生命周期
  1. initEvents


    initEvents執(zhí)行前
initEvents執(zhí)行后

看上去也就是多了幾個變量,我們看看具體的函數(shù)執(zhí)行情況吧

initEvents代碼.png

這么一張圖看上去已經(jīng)很清晰了据沈,vm.$options._parentListeners未定義哟沫,因此之后的步驟也不會執(zhí)行的,整個函數(shù)就新增了兩個變量锌介。不過這個函數(shù)本來想干的事嗜诀,看作者注釋的話是想添加父實例的事件猾警,這個功能在我之后調試到相應例子之后會再進行擴散的

4.initRender

initRender執(zhí)行前

initRender執(zhí)行后

好害怕啊突然變長好多~總結一下就是又多了很多屬性($attrs$listeners隆敢、$slots发皿、$scopedSlots、$vnode)和變量,還增加了方法($createElement)拂蝎。先看一下執(zhí)行過程

initRender代碼

這里大致可以分為三個部分穴墅,第一又增加了變量和屬性,同樣我們在這個例子里看不出什么温自,比如說$slots這個實例屬性篙顺,第二是給vm增加了一個$createElement方法熄守,調用這個方法其實就是調用createElement方法,看命名就知道是一個生成元素的方法。第三個是調用了一個defineReactive方法段化,這個方法是什么意思呢

defineReactive

這些步驟基本是在做初始化

defineReactive

但是最重要的這里,事實上對于get堂鲜,在我們這個例子中筒扒,Dep.target為null,因此get就是返回了value也拜,而set也就是普通的設置了值而已以舒,那么這么長的代碼有什么用呢,這其實又和數(shù)據(jù)綁定有關慢哈。關于數(shù)據(jù)綁定我這里也先不再延伸蔓钟。所以這第三部分就是在給$attrs、$listeners重寫get和set

5.callHook(vm, 'beforeCreate')

function callHook (vm, hook) {
  var handlers = vm.$options[hook];
  if (handlers) {
    for (var i = 0, j = handlers.length; i < j; i++) {
      try {
        handlers[i].call(vm); // 執(zhí)行beforeCreate下的每個函數(shù)
      } catch (e) {
        handleError(e, vm, (hook + " hook"));
      }
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook);
  }
}

這個函數(shù)很簡單卵贱,我們這個簡單的vue實例也沒有傳beforeCreate這個選項滥沫,因此這步也可以忽略,只需要知道在create之前會調用這樣一個東西

6.initInjections

 function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm) //獲得vm.$options.inject的所有屬性
  if (result) {
    observerState.shouldConvert = false
    Object.keys(result).forEach(key => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key]) // 把inject監(jiān)聽起來
      }
    })
    observerState.shouldConvert = true
  }
}

瞅瞅這個簡單的代碼键俱,對于我們這個例子來說兰绣,又是可以跳過的。不過我們還需大致看看這個函數(shù)编振,它其實是對inject選項的初始化缀辩,這個眼熟的defineReactive,沒錯就是對inject的每個屬性的get踪央,set都重寫了一下臀玄。

7.initState

initState執(zhí)行前

這個函數(shù)明顯是很重要的,因為它處理了props/methods/data/computed/watch等選項畅蹂,而這些選項又是我們經(jīng)常需要用到的健无。那我們從源碼看看它怎么處理的

function initState (vm: Component) {
  // 首先在vm上初始化一個_watchers數(shù)組,緩存這個vm上的所有watcher
  vm._watchers = []
  // 獲取options,包括在new Vue傳入的液斜,同時還包括了Vue所繼承的options
  const opts = vm.$options
  // 初始化props屬性
  if (opts.props) initProps(vm, opts.props)
  // 初始化methods屬性
  if (opts.methods) initMethods(vm, opts.methods)
  // 初始化data屬性
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // 初始化computed屬性
  if (opts.computed) initComputed(vm, opts.computed)
  // 初始化watch屬性
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

這段代碼從命名上看是很簡單的累贤,沒有閱讀障礙叠穆。那我們看看和我們相關的initData

function initData (vm: Component) {
  // 獲取data
  let data = vm.$options.data
  // 因為data有可能是個函數(shù)臼膏,這里給它轉換了一下
  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
  const methods = vm.$options.methods
  // 遍歷每個屬性
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      // data的屬性不能同時在method中
      if (methods && hasOwn(methods, key)) {
        warn(
          `method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    // data的屬性不能同時在props中
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      // 給data里的每個屬性重寫了get和set
      proxy(vm, `_data`, key)
    }
  }
  // 時刻觀察data
  // observe data
  observe(data, true /* asRootData */)
}

這個observe函數(shù)實際上做了很多事情硼被。
8.initProvide

function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

初始化provide選項。超簡單的代碼讶请,誰都能看懂祷嘶,加了個_provided變量,和我這個例子好像也沒有什么關系

  1. callHook(vm, 'created')
    和callHook(vm, 'beforeCreate')一樣一樣的夺溢,不再贅述了

10.mount

if (vm.$options.el) {
      vm.$mount(vm.$options.el) //如果傳入了el就掛載
    }

像這段代碼论巍,和生命周期圖結合起來看,就一目了然风响,如果有el選項的話就掛載

$mount-1
$mount-2
mount
mountComponent核心代碼
Wacher-1
Wacher-2
get

那么再來看看updateComponent


updateComponent

這里首先要執(zhí)行vm._render()

_render-1

回到_update

_update

我們傳了舊的和新的node進去


__patch__

這步執(zhí)行完頁面上是這樣的

兩個

直到下圖執(zhí)行完后嘉汰,頁面才算更新完畢

去掉一個
好了

說的可能還有點不太完整。整個_init方法實際上只是到mount部分為止状勤。真的要銷毀的話得手動調用$destroy鞋怀。還有很多函數(shù)沒有仔細擴散,等之后調試到相關例子的時候會再補充持搜。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末密似,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子葫盼,更是在濱河造成了極大的恐慌残腌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贫导,死亡現(xiàn)場離奇詭異抛猫,居然都是意外死亡,警方通過查閱死者的電腦和手機孩灯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門闺金,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人峰档,你說我怎么就攤上這事败匹。” “怎么了讥巡?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵掀亩,是天一觀的道長。 經(jīng)常有香客問我尚卫,道長归榕,這世上最難降的妖魔是什么尸红? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任吱涉,我火速辦了婚禮刹泄,結果婚禮上,老公的妹妹穿的比我還像新娘怎爵。我一直安慰自己特石,他們只是感情好,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布鳖链。 她就那樣靜靜地躺著姆蘸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芙委。 梳的紋絲不亂的頭發(fā)上逞敷,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音灌侣,去河邊找鬼推捐。 笑死,一個胖子當著我的面吹牛侧啼,可吹牛的內容都是我干的牛柒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼痊乾,長吁一口氣:“原來是場噩夢啊……” “哼皮壁!你這毒婦竟也來了?” 一聲冷哼從身側響起哪审,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蛾魄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后协饲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畏腕,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年茉稠,在試婚紗的時候發(fā)現(xiàn)自己被綠了描馅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡而线,死狀恐怖铭污,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情膀篮,我是刑警寧澤嘹狞,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站誓竿,受9級特大地震影響磅网,放射性物質發(fā)生泄漏。R本人自食惡果不足惜筷屡,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一涧偷、第九天 我趴在偏房一處隱蔽的房頂上張望簸喂。 院中可真熱鬧,春花似錦燎潮、人聲如沸喻鳄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽除呵。三九已至,卻和暖如春爪喘,著一層夾襖步出監(jiān)牢的瞬間颜曾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工秉剑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泛啸,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓秃症,卻偏偏與公主長得像候址,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子种柑,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理岗仑,服務發(fā)現(xiàn),斷路器聚请,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • 一荠雕、Native 每一個頁面都是一個 instance,framework 是全局唯一的驶赏,js 和 native ...
    Yang152412閱讀 5,667評論 0 50
  • 前言 人生苦多炸卑,快來 Kotlin ,快速學習Kotlin煤傍! 什么是Kotlin盖文? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,211評論 9 118
  • 十月
    阿洛特斯閱讀 153評論 0 3
  • 一、前言 是否也有一顆焦慮的心:想學點什么卻不知道學什么好蚯姆?是否也有一顆迷茫的心:在學習的道路上舉步維艱五续,蹣跚蹉跎...
    夜_闌珊閱讀 2,735評論 2 18