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

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

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

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

接下來我通過在chrome中一步步運行代碼來理清其內(nèi)部邏輯。
首先當然是調(diào)用構(gòu)造函數(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) // 主要就是這個步驟,調(diào)用原型中的_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。詳細內(nèi)容看1mergeOptions
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    /* istanbul ignore else */
    {
      initProxy(vm);
    }
    // expose real self
    vm._self = vm;
    // 下面這些都是和vue生命周期有關(guān)的一些操作了
    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然后把基礎(chǔ)的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 // 沒有父實例的話把根實例設(shè)成自己了

  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生命周期有關(guān)的一些變量(下面我把以$開頭的稱為實例屬性,把_開頭的稱變量)。這里放一張官網(wǎng)上的生命周期圖片,具體的在之后的內(nèi)容中也會涉及到的

vue生命周期
  1. initEvents


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

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

initEvents代碼.png

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

4.initRender

initRender執(zhí)行前

initRender執(zhí)行后

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

initRender代碼

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

defineReactive

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

defineReactive

但是最重要的這里,事實上對于get,在我們這個例子中,Dep.target為null,因此get就是返回了value,而set也就是普通的設(shè)置了值而已,那么這么長的代碼有什么用呢,這其實又和數(shù)據(jù)綁定有關(guān)。關(guān)于數(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之前會調(diào)用這樣一個東西

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)
  }
}

這段代碼從命名上看是很簡單的,沒有閱讀障礙。那我們看看和我們相關(guān)的initData。

function initData (vm: Component) {
  // 獲取data
  let data = vm.$options.data
  // 因為data有可能是個函數(shù),這里給它轉(zhuǎn)換了一下
  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變量,和我這個例子好像也沒有什么關(guān)系

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

10.mount

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

像這段代碼,和生命周期圖結(jié)合起來看,就一目了然,如果有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部分為止。真的要銷毀的話得手動調(diào)用$destroy。還有很多函數(shù)沒有仔細擴散,等之后調(diào)試到相關(guān)例子的時候會再補充。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容