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

這是官網上一個最簡單的例子

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

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

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

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官網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等進行監聽
    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執行前

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


mergeOptions執行后

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

2.initLifecycle

initLifecycle執行前
initLifecycle執行后

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

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
}

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

vue生命周期
  1. initEvents


    initEvents執行前
initEvents執行后

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

initEvents代碼.png

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

4.initRender

initRender執行前

initRender執行后

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

initRender代碼

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

defineReactive

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

defineReactive

但是最重要的這里,事實上對于get,在我們這個例子中,Dep.target為null,因此get就是返回了value,而set也就是普通的設置了值而已,那么這么長的代碼有什么用呢,這其實又和數據綁定有關。關于數據綁定我這里也先不再延伸。所以這第三部分就是在給$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); // 執行beforeCreate下的每個函數
      } catch (e) {
        handleError(e, vm, (hook + " hook"));
      }
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook);
  }
}

這個函數很簡單,我們這個簡單的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監聽起來
      }
    })
    observerState.shouldConvert = true
  }
}

瞅瞅這個簡單的代碼,對于我們這個例子來說,又是可以跳過的。不過我們還需大致看看這個函數,它其實是對inject選項的初始化,這個眼熟的defineReactive,沒錯就是對inject的每個屬性的get,set都重寫了一下。

7.initState

initState執行前

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

function initState (vm: Component) {
  // 首先在vm上初始化一個_watchers數組,緩存這個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有可能是個函數,這里給它轉換了一下
  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函數實際上做了很多事情。
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

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

_render-1

回到_update

_update

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


__patch__

這步執行完頁面上是這樣的

兩個

直到下圖執行完后,頁面才算更新完畢

去掉一個
好了

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

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

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,779評論 18 139
  • 一、Native 每一個頁面都是一個 instance,framework 是全局唯一的,js 和 native ...
    Yang152412閱讀 5,683評論 0 50
  • 前言 人生苦多,快來 Kotlin ,快速學習Kotlin! 什么是Kotlin? Kotlin 是種靜態類型編程...
    任半生囂狂閱讀 26,241評論 9 118
  • 十月
    阿洛特斯閱讀 155評論 0 3
  • 一、前言 是否也有一顆焦慮的心:想學點什么卻不知道學什么好?是否也有一顆迷茫的心:在學習的道路上舉步維艱,蹣跚蹉跎...
    夜_闌珊閱讀 2,753評論 2 18