1.最俗學(xué)習(xí)之-Vue源碼學(xué)習(xí)-引入篇(上)

源碼地址

<p style="font-size: 2rem;color: red;margin-bottom: 1.5rem">前方高能?。。?lt;/p>

這只是一篇個人學(xué)習(xí)Vue.js源碼的筆記,并非教程,鑒于個人水平有限,可能存在錯誤,還望各路大神指點(diǎn)
文章內(nèi)容極度粗俗,各種無腦分析,各種瘋狂輸出,各位看官斟酌而行,切勿走火入魔?。?!

Vue.js版本 --2.1.7

之所以選擇這個是因?yàn)榭戳诉@位大神的分析,決定采用同一個版本,目前Vue已經(jīng)發(fā)布了2.5.x了
這里極力推薦大家去看看,據(jù)說這位大神的兩篇源碼分析都是經(jīng)過尤大佬推薦的哦,本文作為第一篇
也是參考大神的文章作為開頭,參考了極大部分再加上自己的理解而寫的

Vue2.1.7源碼學(xué)習(xí)

JavaScript實(shí)現(xiàn)MVVM之我就是想監(jiān)測一個普通對象的變化

src/core/instance/index.js

這是整個vue的入口文件,首先引入vue后我們只是引入了一個構(gòu)造函數(shù),所以
一般我們初始化的時候都是用new Vue的方式啟動,即下面的Vue函數(shù),里面執(zhí)行了一句
this._init(options),這個options即是我們傳入的各種參數(shù),即


new Vue({
  el: '#app',
  data: {
    name: 'zhang san',
    age: 18
  }
})

下面是src/core/instance/index.js的源碼,其中引入vue的時候馬上就初始化了5個mixin


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ù),然后以Vue構(gòu)造函數(shù)為參數(shù),調(diào)用了五個方法,最后導(dǎo)出 Vue。這五個方法分別來自五個文件:init.js state.js render.js events.js 以及 lifecycle.js。
打開這五個文件,找到相應(yīng)的方法,你會發(fā)現(xiàn),這些方法的作用,就是在 Vue 的原型 prototype 上掛載方法或?qū)傩?,?jīng)歷了這五個方法后的Vue會變成這樣:


// initMixin(Vue)    src/core/instance/init.js **************************************************
Vue.prototype._init = function (options?: Object) {}

// stateMixin(Vue)    src/core/instance/state.js **************************************************
Vue.prototype.$data
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function(){}

// renderMixin(Vue)    src/core/instance/render.js **************************************************
Vue.prototype.$nextTick = function (fn: Function) {}
Vue.prototype._render = function (): VNode {}
Vue.prototype._s = _toString
Vue.prototype._v = createTextVNode
Vue.prototype._n = toNumber
Vue.prototype._e = createEmptyVNode
Vue.prototype._q = looseEqual
Vue.prototype._i = looseIndexOf
Vue.prototype._m = function(){}
Vue.prototype._o = function(){}
Vue.prototype._f = function resolveFilter (id) {}
Vue.prototype._l = function(){}
Vue.prototype._t = function(){}
Vue.prototype._b = function(){}
Vue.prototype._k = function(){}

// eventsMixin(Vue)    src/core/instance/events.js **************************************************
Vue.prototype.$on = function (event: string, fn: Function): Component {}
Vue.prototype.$once = function (event: string, fn: Function): Component {}
Vue.prototype.$off = function (event?: string, fn?: Function): Component {}
Vue.prototype.$emit = function (event: string): Component {}

// lifecycleMixin(Vue)    src/core/instance/lifecycle.js **************************************************
Vue.prototype._mount = function(){}
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {}
Vue.prototype._updateFromParent = function(){}
Vue.prototype.$forceUpdate = function () {}
Vue.prototype.$destroy = function () {}

這樣就結(jié)束了嗎?并沒有,根據(jù)我們之前尋找 Vue 的路線,這只是剛剛開始,我們追溯路線往
回走,那么下一個處理 Vue 構(gòu)造函數(shù)的應(yīng)該是 src/core/index.js 文件,我們打開它:


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

initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Vue.version = '__VERSION__'

export default Vue

我們則從這里作為第一出發(fā)點(diǎn)開始

首先這里說下這個isServerRendering,找到這個env文件,里面有個方法


export const inBrowser = typeof window !== 'undefined'

let _isServer
export const isServerRendering = () => {
  if (_isServer === undefined) {
    /* istanbul ignore if */
    if (!inBrowser && typeof global !== 'undefined') {
      // detect presence of vue-server-renderer and avoid
      // Webpack shimming the process
      _isServer = global['process'].env.VUE_ENV === 'server'
    } else {
      _isServer = false
    }
  }
  return _isServer
}

這段代碼判斷是否為服務(wù)端,const inBrowser = typeof window !== 'undefined'這段代碼
大概就明白了

然后綁定在構(gòu)造函數(shù)的原型的一個屬性$isServer上面


Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})


然后再說initGlobalAPI方法的執(zhí)行

src\core\global-api\index.js

這個文件導(dǎo)出一個函數(shù),函數(shù)接受Vue構(gòu)造函數(shù)作為參數(shù)


export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      util.warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)
  Vue.util = util
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = util.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

  util.extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

首先定義一個對象configDef,configDef.get關(guān)聯(lián)到了config,打開src\core\config.js

里面其實(shí)就是導(dǎo)出一個對象,對象里面有很多的屬性,其中有


_assetTypes: [
    'component',
    'directive',
    'filter'
  ],

  _lifecycleHooks: [
    'beforeCreate',
    'created',
    'beforeMount',
    'mounted',
    'beforeUpdate',
    'updated',
    'beforeDestroy',
    'destroyed',
    'activated',
    'deactivated'
  ],


這兩個便是常見的屬性和生命周期,然后判斷在開發(fā)環(huán)境下則有個set方法用來發(fā)出提示的
最后Object.defineProperty(Vue, 'config', configDef),定義構(gòu)造函數(shù)的一個靜態(tài)
屬性config,分別有g(shù)et和set的方法的,然后綁定util,set,delete,nextTick在Vue
構(gòu)造函數(shù)上面,然后創(chuàng)建一個options空對象,然后遍歷config的_assetTypes,也就是
上面說的三個屬性,把它們的名字加上s復(fù)數(shù),即components,directives,filters綁定
再options下面的屬性上,三個都是空對象,<font color=#FF0000>再有一個_base = Vue
是自身的賦值,這里暫時不知道為什么要這么做,可能是為了緩存吧</font>,然后執(zhí)行
util.extend(Vue.options.components, builtInComponents),這里是把自身的默認(rèn)
組件KeepAlive綁定到了options里面,即

Vue.config
Vue.util = util
Vue.set = set
Vue.delete = del
Vue.nextTick = util.nextTick

Vue.options = {
    components: {
        KeepAlive
    },
    directives: {},
    filters: {},
    _base: Vue
}

最后執(zhí)行4個方法


initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)

這4個方法對應(yīng)當(dāng)前目錄的4個js文件,首先是use.js,這里給Vue掛載了靜態(tài)方法,并非原型上的
這個我們用的比較多,用來安裝插件的,比如Vue.use(VueRouter)路由插件就是這樣子,這里首先
判斷installed是否安裝過了,然后執(zhí)行toArray方法,返回一個數(shù)組,toArray方法解釋在
methods realizes目錄下找到對應(yīng)名字文件夾,然后args.unshift(this)在這個返回的數(shù)組頭部
添加Vue構(gòu)造函數(shù),然后執(zhí)行這個install方法,一般插件的install方法形式如官方文檔所寫


MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或?qū)傩?  Vue.myGlobalMethod = function () {
    // 邏輯...
  }

  // 2. 添加全局資源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 邏輯...
    }
    
  })

  // 3. 注入組件
  Vue.mixin({
    created: function () {
      // 邏輯...
    }
  })

  // 4. 添加實(shí)例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 邏輯...
  }
}

Vue.use(MyPlugin)

這里就是執(zhí)行這個install方法了,即調(diào)用Vue的全局方法,注冊指令,在prototype上掛載方法
或者使用全局mixin等等,這里還有個else語句,也就是install不是函數(shù)的情況,那么這種情況
應(yīng)該是直接傳入一個function,形如下面這樣,但最后都是啟動這個方法,在Vue構(gòu)造函數(shù)上面做
一些操作


Vue.use(function (Vue, options) {
  // 1. 添加全局方法或?qū)傩?  Vue.myGlobalMethod = function () {
    // 邏輯...
  }

  // 2. 添加全局資源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 邏輯...
    }
    
  })

  // 3. 注入組件
  Vue.mixin({
    created: function () {
      // 邏輯...
    }
  })

  // 4. 添加實(shí)例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 邏輯...
  }
})


最后則plugin.installed = true,設(shè)置為已經(jīng)安裝了,這樣就不會重復(fù)安裝插件了,這樣
initUse(Vue)方法大致明白了思路


到了mixin.js文件,也就是第二個方法initMixin(Vue)
到了extend.js文件,也就是第二個方法initExtend(Vue)

這兩個文件涉及到比較多的問題,加之個人水平有限,暫且留著,這里簡單說下
mixin文件主要綁定了Vue.mixin方法,就是常用的全局混合,里面調(diào)用了mergeOptions合并策略
這個非常重要,后面細(xì)說,extend文件綁定了Vue.extend方法,這個就是常見的vue構(gòu)造器,可以
理解為創(chuàng)建一個子組件吧,還添加了一個屬性Vue.cid = 0。


這里直接看第4個方法initAssetRegisters(Vue),找到assets.js文件

這里又是遍歷這個數(shù)組_assetTypes,這里和上面說的有點(diǎn)類似,當(dāng)初看的時候有點(diǎn)懵逼了
其實(shí)是這樣的


// 上面綁定是這這樣的,在Vue.options上面綁定的

Vue.options = {
    components: {
        KeepAlive
    },
    directives: {},
    filters: {}
}

// 這里綁定的是這樣的

Vue.component = function(id, definition){
  
}

Vue.directive = function(id, definition){
  
}

Vue.filter = function(id, definition){
  
}

// 一個在Vue的options屬性上,一個直接掛載在Vue上,一個后面帶有復(fù)數(shù)s,一個沒有

這個三個方法都是同一個函數(shù)操作,接受兩個參數(shù),第一個是str即指令名稱,第二個是fun或
者obj,如果沒有第二個參數(shù)則返回對應(yīng)的指令,比如


var directiveData = Vue.directive('show')

console.log(directiveData)

// 這樣可以看到v-show指令的情況,這個情況一般較少

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

var filterData = Vue.filter('capitalize')

console.log(filterData)

// 這樣可以看到自定義過濾器capitalize的方法,如果之前沒有注冊這個過濾器,那么則返回undefined

// 這里我們還是按照平常使用的方法例子來說,首先是指令

// 文檔提供了兩種方式注冊指令

1.

Vue.directive('demo', {
  bind: function (el, binding, vnode) {
    var s = JSON.stringify
    el.innerHTML =
      'name: '       + s(binding.name) + '<br>' +
      'value: '      + s(binding.value) + '<br>' +
      'expression: ' + s(binding.expression) + '<br>' +
      'argument: '   + s(binding.arg) + '<br>' +
      'modifiers: '  + s(binding.modifiers) + '<br>' +
      'vnode keys: ' + Object.keys(vnode).join(', ')
  }
})

2.

Vue.directive('color-swatch', function (el, binding) {
  el.style.backgroundColor = binding.value
})

// 但是會發(fā)現(xiàn),第2種最后會經(jīng)過

if (type === 'directive' && typeof definition === 'function') {
  definition = { bind: definition, update: definition }
}

// 這個if,最后還是變成了第1種的方式變成了
{
  bind: function (el, binding) {
    el.style.backgroundColor = binding.value
  },
  update: function (el, binding) {
    el.style.backgroundColor = binding.value
  }
}

// 最后掛載在Vue構(gòu)造函數(shù)上面this.options[type + 's'][id] = definition

// return這個方法,這個倒是比較少用到

// 還記得Vue.options這個值是這樣的

Vue.options = {
    components: {
        KeepAlive
    },
    directives: {
      // 這兩個是Vue自帶的,在另一個地方初始化的,這里暫時這樣理解就好,存在這兩個方法的
      model: {

      },
      show: {

      },
      // 如果經(jīng)過了第一種方法注冊指令,那么就會添加下面一個了
      demo: {
        bind: function () {
          // some methods
        }
      }
    },
    filters: {}
}

當(dāng)然,注冊自定義指令還有其他的生命周期鉤子和參數(shù),可以參考文檔自定義指令

                                                            
// 然后是注冊過濾器,方法和指令一樣,只不過三個if都不走
// 直接是this.options[type + 's'][id] = definition
// 例如文檔例子

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

var filterData = Vue.filter('capitalize')

console.log(filterData)

// 這樣就可以注冊一個過濾器了

// 最后就剩下component方法了,這個是注冊組件用的,按照文檔例子說起

Vue.component('my-component', {
  name: 'my-name',
  template: '<span>{{ message }}</span>',
  data () {
    return {
      message: 'hello'
    }
  }
})

// 首先走第1個if,這里有個config.isReservedTag(id)方法,方法解釋看methods realizes目錄
// 這里構(gòu)建前的源碼的config.isReservedTag方法找到的是一個no方法,但其實(shí)這個方法在
// platforms\web\util\element.js文件里面,這里直接看構(gòu)建后的源碼即可
// 這個方法就是不允許用戶使用html原有的標(biāo)簽作為組件的標(biāo)簽名字,然后到了第2個if語句,這里有個
// 方法isPlainObject,方法解釋看methods realizes目錄,這里這個是一個obj,如果沒有傳入name
// 則會使用id也就是注冊的組件名字作為name,上面name是my-name,如果沒有則name就是my-component
// 然后調(diào)用definition = this.options._base.extend(definition),這個其實(shí)就是上面跳過的方法
// initExtend(Vue)同樣的,其實(shí)這里的this.options._base就是Vue構(gòu)造函數(shù),因?yàn)?// console.log(this.options._base === Vue)  // => true
// 最后掛載在this.options上面,就變成了這樣

// 還記得Vue.options這個值是這樣的

Vue.options = {
    components: {
      KeepAlive: {
        
      },
      // 這兩個是Vue自帶的,在另一個地方初始化的,這里暫時這樣理解就好,存在這兩個方法的
      Transition: {
        
      },
      TransitionGroup: {
        
      },
      my-component: {
        
      }
    },
    directives: {
      // 這兩個是Vue自帶的,在另一個地方初始化的,這里暫時這樣理解就好,存在這兩個方法的
      model: {

      },
      show: {

      },
      // 如果經(jīng)過了第一種方法注冊指令,那么就會添加下面一個了
      demo: {
        bind: function () {
          // some methods
        }
      }
    },
    filters: {}
}

<p style="font-weight: bold;margin-bottom: 10px;color: #FF0000">剩下的幾個問題</p>


Vue.set = set                       // 涉及到Vue的數(shù)據(jù)響應(yīng)式系統(tǒng),先保留
Vue.delete = del                    // 涉及到Vue的數(shù)據(jù)響應(yīng)式系統(tǒng),先保留
Vue.nextTick = util.nextTick        // 水平有限,看不懂 - -#
initMixin(Vue)                      // 這個后面再講
initExtend(Vue)                     // 水平有限,看不懂 - -#

綜上所述:

initGlobalAPI 的作用是在 Vue 構(gòu)造函數(shù)上掛載靜態(tài)屬性和方法,Vue 在經(jīng)過 initGlobalAPI
之后,會變成這樣:


Vue.config
Vue.util = util
Vue.set = set
Vue.delete = del
Vue.nextTick = util.nextTick

Vue.options = {
    components: {
        KeepAlive
    },
    directives: {},
    filters: {},
    _base: Vue
}

Vue.use
Vue.mixin
Vue.cid = 0
Vue.extend
Vue.component = function(){}
Vue.directive = function(){}
Vue.filter = function(){}

Vue.prototype.$isServer

Vue.version = '__VERSION__'

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

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

  • 都說巴黎出美女,巴黎的確有美女出沒。倘若夏季來到莫斯科,那里簡直就是眼睛的天堂,美女的海洋,片片是絕色美女群?。?...
    米素文閱讀 1,705評論 17 24
  • 我們每個人生活在這個五彩繽紛而又眼花繚亂的世界上,就必須有個固定的衣食來源來養(yǎng)活自己,借用黑格爾的一句話來說,一...
    崇修閱讀 316評論 0 0
  • 冷嗎 這扇漏風(fēng)的窗子 像你在說話 我離開后 及時拍掉了墻灰 魚群圍上來 以為那是可吃的東西 我向每一條魚都道歉了 ...
    竹喧歸浣女閱讀 153評論 5 6

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