Vue2.x源碼學習筆記-Vue構造函數

咱們知道使用vue.js開發應用時,都是new Vue({}/*options*/)html

那Vue構造函數上有哪些靜態屬性和方法呢?其原型上又有哪些方法呢?vue

通常我都會在瀏覽器中輸入Vue來look see seenode

 

能夠看到Vue構造函數上掛載了這麼多屬性和方法,so這麼nb。webpack

能夠看到有不少的全局的api,以及實例的方法(其實就是Vue.prototype上的方法)。ios

那麼問題來了,尤大是如何把這麼多方法和屬性掛載上去的。那麼帶着問題,進入vue源碼 look see see去git

 如今寫項目可能都使用es6+的語法,用webpack打包或者其餘工具打包了。es6

先進入項目中,找到package.json文件,這裏面有項目的依賴,有開發環境、生產環境等編譯的啓動腳本,有項目的許可信息等。github

然而咱們使用npm run dev時,其實就是package.json文件中scripts屬性中dev屬性,它是這麼寫的web

"dev": "rollup -w -c build/config.js --environment TARGET:web-full-dev"

它執行了build/config.js文件(一個打包配置的文件),且帶了個web-full-dev參數過去了,先這麼理解這,咱們去build/config.js文件中去看看,且搜下web-full-dev,會發現npm

// Runtime+compiler development build (Browser)
  'web-full-dev': {
    entry: path.resolve(__dirname, '../src/entries/web-runtime-with-compiler.js'),
    dest: path.resolve(__dirname, '../dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },

咱們能夠發現入口文件是'../src/entries/web-runtime-with-compiler.js',然而src目錄下的那麼文件夾和文件都會編譯成dist目錄下的vue.js。

咱們分別打開相關目錄下的文件

/src/entries/web-runtime-with-compiler.js --> /src/entries/web-runtime.js --> /src/core/index.js --> /src/core/instance/index.js
經過package.json文件,依次打開相關文件終於找到Vue構造函數了

 

 能夠看到Vue構造函數是如此簡單,一個if分支加上一個原型上的_init方法。那麼怎麼往這個構造函數上混入原型方法和靜態屬性和靜態方法呢?

 咱們能夠看到經過

// 給Vue構造函數原型添加 _init方法
initMixin(Vue)
// 給Vue構造函數原型添加 $data,$props屬性、$set,$delete,.$watch方法
stateMixin(Vue)
// 給Vue構造函數原型添加 $on、$once、$off、$emit方法
eventsMixin(Vue)
// 給Vue構造函數原型添加 _update、$forceUpdate、$destroy方法
lifecycleMixin(Vue)
// 給Vue構造函數原型添加 $nextTick、_render、以及_o,_n,_s,_l,_t,_q,_i,_m,_f,_k,_b,_v,_e,_u內部調用的方法
renderMixin(Vue)

這幾個方法就給Vue.prototype添加了這麼多方法了。

接着沿剛纔所提到的文件引入順序一步步來看。/src/core/instance/index.js執行以後,是/src/core/index.js文件來看下源碼

import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'

initGlobalAPI(Vue) // 給Vue構造函數添加了一些靜態方法和屬性(屬性: config, util, options, cid; 
                   // 方法: set, delete, nextTick, use, mixin, extend, component, directive, filter方法)

// 給Vue構造函數的原型添加 $isServer 屬性
Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

// 給Vue構造函數添加 version 屬性
Vue.version = '__VERSION__'

export default Vue

能夠看到initGlobalAPI方法給Vue構造函數添加了好多靜態屬性和方法(也就是官網api提到的全局api)。

咱們能夠先看下其源碼

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  Vue.options = Object.create(null)
  config._assetTypes.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue) // 給Vue構造函數添加 use方法
  initMixin(Vue) // 給Vue構造函數添加 mixin方法
  initExtend(Vue) // 給Vue構造函數添加 extend方法
  initAssetRegisters(Vue) // 給Vue構造函數添加 component, directive, filter方法
}

而後又給Vue.prototype原型添加了$isServer屬性

再而後給Vue添加了version靜態屬性。

接着再看下/src/entries/web-runtime.js文件中的代碼

// install platform specific utils 安裝平臺相對應的方法
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

// install platform runtime directives & components 安裝平臺相對應的指令和組件
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function 若是是環境是瀏覽器的話, 給Vue構造函數添加__patch__函數
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method 給Vue構造函數添加 $mount 函數
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}  

能夠看到

1. 根據不一樣的平臺重寫config對象中mustUseProp,  isReservedTag, getTagNamespace, isUnknowElement屬性的值。

2. 經過exend方法,擴展指令對象和組件對象

先不關心extend方法的具體實現,看看他把咱們的Vue.options.directives和Vue.options.components變成了什麼鳥樣

內置指令和組件就是這麼來的啊,很好,繼續往下see see

3. 而後給Vue.prototype添加__patch__(虛擬dom相關) 和 $mount(掛載元素)方法

 接着看下/src/entries/web-runtime-with-compiler.js 文件的代碼:

const mount = Vue.prototype.$mount
// 重寫Vue構造函數原型上的$mount方法
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        delimiters: options.delimiters
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  return mount.call(this, el, hydrating)
}

/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */
function getOuterHTML (el: Element): string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}

Vue.compile = compileToFunctions // 給Vue構造函數添加 compile方法

該文件中重寫Vue構造函數原型上的$mount方法,且給vue添加了compile屬性

 

至此Vue上的靜態屬性和方法,還有原型上的方法怎麼來的就這麼看完了。

一個構造函數有了,那怎麼玩它呢,必然new它,獲得的實例,究竟它有哪些屬性,屬性怎麼一步一步掛載到實例上去的,下篇帖子用個小例子說明。

 

總結: 該筆記主要記錄Vue構造函數上的靜態屬性和方法還有原型方法是如何一步一步添加到Vue構造函數上的。並無解讀屬性或者方法的源碼。

            感謝濤哥:https://github.com/liutao/vue2.0-source/blob/master/%E4%BB%8E%E5%85%A5%E5%8F%A3%E6%96%87%E4%BB%B6%E6%9F%A5%E7%9C%8BVue%E6%BA%90%E7%A0%81.md

相關文章
相關標籤/搜索