new Vue({})初始化流程

new Vue的初始化流程

new Vue({})之後發生了什麼?

首先來看Vue的構造函數,

一、src/core/instance/index.js

源碼:

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._init,所在文件爲src/core/instance/init.js

二、src/core/instance/init.js

可以在這個文件中看到,_init()方法定義了很多的初始化方法, _init()源碼:

export function initMixin(Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    ....中間代碼忽略,從45行看起
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)    
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)    //聲明$parent,$root,$parent,$refs
    initEvents(vm)       //對父組件傳入的事件添加監聽,事件是誰創建誰監聽,子組件創建事件子組件監聽
    initRender(vm)       //聲明$slots和$createElement()
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props   獲取注入數據
    initState(vm)   //(重要)數據響應式:初始化狀態:props,methods,data,computed,watch
    initProvide(vm) // resolve provide after data/props  //提供數據注入
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

2.1 我們來描述一下這些初始化方法:

  1. initProxy:作用域代理,攔截組件內訪問其它組件的數據。
  2. initLifecycle:建立父子組件關係,在當前組件實例上添加一些屬性和生命週期標識。如parent,parent,refs,$children,_isMounted等。
  3. initEvents:對父組件傳入的事件添加監聽,事件是誰創建誰監聽,子組件創建事件子組件監聽
  4. initRender:聲明slotsslots和createElement()等。
  5. initInjections:注入數據,初始化inject,一般用於組件更深層次之間的通信。
  6. initState:重要)數據響應式:初始化狀態。很多選項初始化的彙總:data,methods,props,computed和watch。
  7. initProvide:提供數據注入。**思考:爲什麼先注入再提供呢??**答:1、首先來自祖輩的數據要和當前實例的data,等判重,相結合,所以注入數據的initInjections一定要在InitState的上面。2、從上面注入進來的東西在當前組件中轉了一下又提供給後代了,所以注入數據也一定要在上面。
  8. vm.mount(vm.mount(vm.options.el):掛載實例。

三、接下來再從_init()方法的initLifecycle()開始初始化生命週期。

mountComponent: src/core/instance/lifecircle.js

執行掛載:在這個文件中獲取VDom並轉換爲真實DOM。
首先在lifecircle.js文件中,定義了Vue.prototype._update 方法,這個方法會獲取Vdom,接着執行mountComponent,然後執行vm._update(vnode, hydrating),源碼如下:

let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)  //把虛擬Dom轉化爲真實dom
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)   //執行render() 獲取vdom並渲染dom
    }
  }

四、接着在updateComponent這個方法中執行vm._update()方法會進入src/core/instance/render.js文件 中

Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options

    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // There's no need to maintain a stack because all render fns are ui4op[poiuy]
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm
      //這裏把vdom轉化爲了真實dom
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } 

通過這句vnode = render.call(vm._renderProxy, vm.$createElement)把vdom替換成了真實dom。

始始化流程總結

new Vue() (vue構造函數) => _init() (初始化options中的各種屬性,data,methods,props,computed,watch)=> $mount (調用mountComponent())===> mountComponent() (創建updateComponent()) ===> updateComponent() (執行_render()和_update()) ===> _render() (獲取虛擬dom) ===> update() (把獲取的虛擬dom轉化爲真實dom)

這裏有一個例子來說明:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src='../../dist/vue.js'></script>
</head>

<body>
  <div id="app">
    <h2>數據初始化</h2>
    <p>{{foo}}</p>
  </div>
  <script>
    new Vue({
      el: '#app',
      data() {
        return {
          foo: 'fooooooo'
        }
      },
    })
  </script>

</body>

</html>

在瀏覽器中打開這個文件,斷點設置在new Vue({})這裏,F11進入到這個構造函數內部。

調試過程如下:

  1. 在這裏插入圖片描述
  2. 在這裏插入圖片描述
  3. 在這裏插入圖片描述
  4. List item
  5. List item
  6. List item
  7. List item
  8. List item
  9. 步驟8中的執行完成,界面上的{{foo}}就變成了foo,至此初始化過程就完成了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章