從new Vue()開始(Vue2.0源碼分析從使用到理解第一節(jié))

前言

本文是系列開篇,系列的主旨在于分享自己在閱讀vue源碼時的收獲和體會,一方面是讓自己有個總結(jié),另一方面幫助想要理解vue源碼的同學有個可以參考的東西。
寫文章的時候vue版本為2.4.2

開篇

我們來看一下官網(wǎng)的例子這是最簡單的vue的使用實例,本系列從這個實例作為開始來一步一步解析vue2的源碼。本篇就先對Vue構(gòu)造函數(shù)做了一個簡單的解析。


Screenshot from 2017-09-01 15-14-56.png

分析

分析項目結(jié)構(gòu)

這個項目結(jié)構(gòu)我以后每一篇都會把那一篇需要的都會再說一遍,所以不用急著一步到位的了解所有文件夾的用處。

├── src ----------------------------------- 這個是我們最應該關(guān)注的目錄,包含了源碼
│   ├── entries --------------------------- 包含了不同的構(gòu)建或包的入口文件
│   │   ├── web-runtime.js
│   │   ├── web-runtime-with-compiler.js
│   │   ├── web-compiler.js
│   │   ├── web-server-renderer.js
│   ├── compiler
│   │   ├── parser ------------------------ 存放將模板字符串轉(zhuǎn)換成元素抽象語法樹的代碼
│   │   ├── codegen ----------------------- 存放從抽象語法樹(AST)生成render函數(shù)的代碼
│   │   ├── optimizer.js ------------------ 分析靜態(tài)樹,優(yōu)化vdom渲染
│   ├── core ------------------------------ 存放通用的,平臺無關(guān)的代碼
│   │   ├── observer
│   │   ├── vdom
│   │   ├── instance ---------------------- 包含Vue構(gòu)造函數(shù)設(shè)計相關(guān)的代碼
│   │   ├── global-api -------------------- 包含給Vue構(gòu)造函數(shù)掛載全局方法(靜態(tài)方法)或?qū)傩缘拇a
│   │   ├── components
│   ├── server
│   ├── platforms
│   ├── sfc
│   ├── shared

Vue構(gòu)造函數(shù)

我們先去找找Vue構(gòu)造函數(shù)在哪吧,之前的項目結(jié)構(gòu)里面我們也可以看到core文件夾有個instance文件夾。這里面就是構(gòu)造函數(shù)的定義。

Screenshot from 2017-09-01 15-26-11.png

看看index.js的代碼

index.js

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

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

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

這里有個值得一提的地方,我們看到Vue構(gòu)造函數(shù)里面有一句warn('Vue is a constructor and should be called with the 'new' keyword')這里開篇我們就不細看了,這里主要是為了檢測是不是使用的構(gòu)造函數(shù)方式還是直接以函數(shù)的方式調(diào)用的。

然后options被傳進了Vue原型里面的_init方法里面。options回顧一下就是之前的


Screenshot from 2017-09-01 15-32-59.png

init.js

這個文件里面主要內(nèi)容是為Vue原型掛載_init方法
init方法里面的proxy還有內(nèi)部主鍵啥的我們都先不管,看看下面這部分代碼

    ...
    // 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(`${vm._name} init`, startTag, endTag)
    }

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

現(xiàn)在大家可以回憶一下vue文檔里面關(guān)于生命周期那一部分的圖

Screenshot from 2017-09-01 15-59-04.png

基本上上面這段代碼涵蓋了new Vue()一直到created鉤子后判斷options里面是否有el屬性。
我們一句一句看。

initLifecycle

lifecycle.js里面的其中一個函數(shù),基本上是初始化生命周期的一些變量,refs也是這個階段初始化的,這個我們后面會有一章單獨講生命周期。

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

initEvents

event.js其中一個函數(shù),暫時我還沒讀完這一部分的代碼,不是很懂具體是干什么的,我系列寫完會回來重新補充的。

export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

這里有一個值得一提的地方,看到vm._events = Object.create(null),我們控制臺可以輸入一下看一下Object.create(null)結(jié)果是什么。

我一開始有點疑惑,這和對象字面量有啥區(qū)別,不過我又試了下知道了

Screenshot from 2017-09-01 20-36-50.png

我也google了一下,stackoverflow里面也有人問了類似的問題。Creating Js object with Object.create(null)? 反正這個方式創(chuàng)建的對象以null為原型創(chuàng)建一個對象,沒有任何屬性。

然而typeof null為object,可null又不可能是個對象,也沒proto指針,很神奇的東西。

initRender

render.js中的一個函數(shù),$slots在這里初始化的,還有一些我沒看懂,后面補充。

export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null
  const parentVnode = vm.$vnode = vm.$options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data
  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', vm.$options._parentListeners, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs, null, true)
    defineReactive(vm, '$listeners', vm.$options._parentListeners, null, true)
  }
}

initInjections

inject.js的一個函數(shù),這個我也沒看,后面補充。。。

export function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)
  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])
      }
    })
    observerState.shouldConvert = true
  }
}

initState

state.js的一個函數(shù),可以看到props,methods,data,computed,watch都是這個時候初始化的。

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

initProvide

也是inject.js里面的一個函數(shù),這個也后面補充吧。。

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

后記

第一章還是只是介紹Vue構(gòu)造函數(shù)并說了比較簡單的東西,沒深入,下一節(jié)講一下vue的生命周期鉤子實現(xià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ā)布平臺,僅提供信息存儲服務。

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

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