Vue源碼閱讀


生命週期

圖片描述

new Vue

new Vue 發生了什麼

new關鍵字表明實例化一個對象, 而Vue其實是一個類, 源碼位置是/src/core/instance/index.jshtml

clipboard.png

new Vue() 以後。 Vue 會調用 _init 函數進行初始化,也就是這裏的 init 過程,它會初始化生命週期事件propsmethodsdatacomputedwatch
clipboard.pngvue

源碼 -> _initnode

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

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${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 = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    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)
    }
  }
}

在初始化時,會調用以上_init中代碼,生命週期就是經過 callHook 調用的
它的定義在 src/core/instance/lifecycle 中:git

export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}

callHook 函數的邏輯很簡單,根據傳入的字符串 hook,去拿到 vm.$options[hook] 對應的回調函數數組,而後遍歷執行,執行的時候把 vm 做爲函數執行的上下文。github

callhook 函數的功能就是調用某個生命週期鉤子註冊的全部回調函數。web

beforeCreate & created

初始化最核心的邏輯是這段:vue-router

clipboard.png

beforeCreatecreated 函數都是在實例化 Vue 的階段,在 _init 方法中執行的,它的定義在 src/core/instance/init.js 中:segmentfault

beforeCreate 調用的時候,是獲取不到 props 或者 data 中的數據的,由於這些數據的初始化都在 initState 中。後端

能夠看到 beforeCreate 和 created 的鉤子調用是在 initState 的先後,initState 的做用是初始化 props、data、methods、watch、computed 等屬性,以後咱們會詳細分析。那麼顯然 beforeCreate 的鉤子函數中就不能獲取到 props、data 中定義的值,也不能調用 methods 中定義的函數。

在這倆個鉤子函數執行的時候,並無渲染 DOM,因此咱們也不可以訪問 DOM,通常來講,若是組件在加載的時候須要和後端有交互,放在這倆個鉤子函數執行均可以,若是是須要訪問 props、data 等數據的話,就須要使用 created 鉤子函數。
--> 此段來自Vue.js 技術揭祕api

clipboard.png

clipboard.png

接下來會執行這裏的掛載函數mountComponent

clipboard.png

beforeMount 就是在掛載前執行的,而後開始建立 VDOM 並替換成真實 DOM,最後執行 mounted 鉤子。

這裏會有個判斷邏輯,若是是外部 new Vue({}) 的話,不會存在 $vnode ,因此直接執行 mounted 鉤子了。若是有子組件的話,會遞歸掛載子組件,只有當全部子組件所有掛載完畢,纔會執行根組件的掛載鉤子。

clipboard.png

對照生命週期圖,咱們看看在beforeMount鉤子和mounted鉤子之間的 Create vm.$el and replace "el" width it 具體都有作了什麼:

clipboard.png

clipboard.png

掛載

初始化以後調用 $mount 會掛載組件,若是是運行時編譯,即不存在 render function 可是存在
template 的狀況,須要進行「編譯」步驟。

Vue 實例掛載如何實現
Vue 中咱們是經過 $mount 實例方法去掛載 vm 的, $mount 方法在多個文件中都有定義,如 src/platform/web/entry-runtime-with-compiler.jssrc/platform/web/runtime/index.jssrc/platform/weex/runtime/index.js。由於 $mount 這個方法的實現是和平臺、構建方式都相關的。

clipboard.png

clipboard.png

clipboard.png

NextTick

雙向綁定

SFC文件解析爲SFCDescriptor

圖片描述

Virtual DOM

Virtual DOM 就是用一個原生的 JS 對象去描述一個 DOM 節點,因此它比建立一個 DOM 的代價要小不少。在 Vue.js 中,Virtual DOM 是用 VNode 這麼一個 Class 去描述,它是定義在 src/core/vdom/vnode.js 中的。

Virtual DOM 其實就是一棵以 JavaScript 對象(VNode 節點)做爲基礎的樹,用對象屬性來描述節點,實際上它只是一層對真實 DOM 的抽象。最終能夠經過一系列操做使這棵樹映射到真實環境上。因爲 Virtual DOM 是以 JavaScript 對象爲基礎而不依賴真實平臺環境,因此使它具備了跨平臺的能力,好比說瀏覽器平臺WeexNode 等。

VNode

實現 Virtual DOM 下的一個 VNode 節點
// VNode 就是一個 JavaScript 對象,用 JavaScript 對象的屬性來描述當前節點的一些狀態,
// 用 VNode 節點的形式來模擬一棵 Virtual DOM 樹。
class VNode {
  constructor(tag, data, children, text, elm, context, componentOptions, asyncFactory) {
      // 當前節點的標籤名
      this.tag = tag // String
      // 當前節點的一些數據信息, 好比props,attrs等數據  
      this.data = data // VNodeData
      // 當前節點的子節點,是一個數組
      this.children = children // Array<VNode>
      // 當前節點的文本
      this.text = text // String
      // 當前虛擬節點對應的真實dom節點
      this.elm = elm // Node

      this.ns = undefined // String | Void
      // rendered in this component's scope
      this.context = context // Component | Void
    
    // real context vm for functional nodes
      this.fnContext = undefined // Component | void
      // for SSR caching
      this.fnOptions = undefined
      // functional scope id support
      this.fnScopeId = undefined

      this.key = data && data.key // String | Number | Void
    this.componentOptions = componentOptions // VNodeComponentOptions | Void
    this.componentInstance = undefined // component instance
    // Component placeholder node
    this.parent = undefined // VNode | Void

    // strictly internal 
    // contains raw HTML? (server only)
    this.raw = false // boolean 
    // hoisted static node
    this.isStatic = false // boolean
    // necessary for enter transition check
    this.isRootInsert = true // boolean
    // empty comment placeholder
    this.isComment = false // boolean
    // is a cloned node ?
    this.isCloned = false // boolean
    // is a v-once node ?
    this.isOnce = false // boolean
    // aysync component factory function
    this.asyncFactory = asyncFactory // Function
    this.asyncMeta = undefined // Oject | void
  }

  get child() {
      return this.componentInstance
  }
}

// 對VNode進一步封裝,實現一些產生經常使用VNode方法
// 建立空節點
const createEmptyVNode = (text) => {
  const node = new VNode()
  node.text = text
  node.isComment = true
  return node
}
// 建立文本節點
function createTextVNode (val) {
    return new VNode(undefined, undefined, undefined, String(val))
}

// 克隆一個VNode節點
// optimized shallow clone
// used for static nodes and slot nodes because they may be reused across
// multiple renders, cloning them avoids errors when DOM manipulations rely
// on their elm reference 
function cloneVNode(vnode) {
  const cloned = new VNode(
      vnode.tag,
      vnode.data,
      // clone children array to avoid mutating original in case of cloning a child
      vnode.children && vnode.children.slice()
      vnode.text,
      vnode.elm,
      vnode.context,
      vnode.componentOptions,
      vnode.asyncFactory
  )
  cloned.ns = vnode.ns
  cloned.isStatic = vnode.isStatic
  cloned.key = vnode.key
  cloned.isComment = vnode.isComment
  cloned.fnContext = vnode.fnContext
  cloned.fnOptions = vnode.fnOptions
  cloned.fnScopeId = vnode.fnScopeId
  cloned.asyncMeta = vnode.asyncMeta
  cloned.isCloned = true
  return cloned    
}

若是咱們有這樣一個vue組件

<template>
  <span class="demo" v-show="isShow">
    This is a span.
  </span>
</template>

轉化成render函數描述的js代碼形式就是這樣的:

vue-router

相關基礎看這篇文章 -> Vue 路由知識點概括總結

Vue.use

Vue 通用的插件註冊原理

Vue 從它的設計上就是一個漸進式 JavaScript 框架,它自己的核心是解決視圖渲染的問題,其它的能力就經過插件的方式來解決。

如何註冊插件 --> Vue.use: Vue 提供了 Vue.use 的全局 API 來註冊這些插件
vue/src/core/global-api/use.js

/* @flow */

import { toArray } from '../util/index'

export function initUse (Vue: GlobalAPI) {
  // Vue.use 接受一個 plugin 參數
  Vue.use = function (plugin: Function | Object) {
    // 而且維護了一個 _installedPlugins 數組,它存儲全部註冊過的 plugin
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    const args = toArray(arguments, 1)
    args.unshift(this)
    // 判斷 plugin 有沒有定義 install 方法
    if (typeof plugin.install === 'function') {
      // 若是有的話則調用該方法,而且該方法執行的第一個參數是 Vue
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    // 最後把 plugin 存儲到 installedPlugins 中
    installedPlugins.push(plugin)
    return this
  }
}


// 能夠看到 Vue 提供的插件註冊機制很簡單,
// 每一個插件都須要實現一個靜態的 install 方法,
// 當咱們執行 Vue.use 註冊插件的時候,就會執行這個 install 方法,
// 而且在這個 install 方法的第一個參數咱們能夠拿到 Vue 對象,
// 這樣的好處就是做爲插件的編寫方不須要再額外去import Vue 了

實例 component,props,slot

咱們知道, 當vue庫文件加載完後,vue的初始化中已有這個東西:

Vue.options={
    components:{
        KeepAlive:Object,
        Transition:Object,
        TransitionGroup:Object
    },
    directives:{
        show:Object,
        model:Object
    },
    filter:{},
    _base:function Vue$3(options){...}
}

這些都是vue庫內置的組件和指令,當執行Vue.component、Vue.directive、Vue.filter時就是在對這些內置組件、指令、過濾器進行擴充,因此:

var child=Vue.component('child',{
        template:'<div>child</div>',
        props:['name']
    })

執行完後

Vue.options.components={
        KeepAlive:Object,
        Transition:Object,
        TransitionGroup:Object,
        child:function VueComponent(options)
    }

參考

https://yuchengkai.cn/docs/fr...
https://ustbhuangyi.github.io...
vue中SFC文件解析爲SFCDescriptor的流程
Vue源碼分析(11)--實例分析component,props,slot
vue變化偵測原理

相關文章
相關標籤/搜索