Vue.js源碼學(xué)習(xí)一 —— 數(shù)據(jù)選項(xiàng) State 學(xué)習(xí)

關(guān)于Vue源碼學(xué)習(xí)的博客纵朋, HcySunYangVue2.1.7源碼學(xué)習(xí)是我所見過講的最清晰明了的博客了艺蝴,非常適合想了解Vue源碼的同學(xué)入手凸椿。本文是在看了這篇博客之后進(jìn)一步的學(xué)習(xí)心得肠槽。
注意:本文所用Vue版本為 2.5.13
PS:本文有點(diǎn)草率乡小,之后會(huì)重寫改進(jìn)阔加。

關(guān)于源碼學(xué)習(xí)

關(guān)于學(xué)習(xí)源碼,我有話要說~
一開始我學(xué)習(xí)Vue的源碼满钟,是將 Vue.js 這個(gè)文件下載下來逐行去看……因?yàn)槲衣犘帕宋彝抡f的“不過一萬多行代碼胜榔,實(shí)現(xiàn)也很簡單,可以直接看湃番∝仓”結(jié)果可想而知,花了十幾個(gè)小時(shí)看完代碼吠撮,還通過打斷點(diǎn)看流程尊惰,除了學(xué)習(xí)到一些新的js語法、一些優(yōu)雅的代碼寫法泥兰、和對整個(gè)代碼熟悉了之外择浊,沒啥其他收獲。
其實(shí)逾条,這是一個(gè)丟西瓜撿芝麻的行為琢岩,沒有明確的目的籠統(tǒng)的看源碼,最終迷失在各種細(xì)枝末節(jié)上了师脂。
所以呢担孔,我看源碼的經(jīng)驗(yàn)教訓(xùn)有如下幾點(diǎn):

  • 看代碼江锨,必須帶著問題去找實(shí)現(xiàn)代碼。
  • 保持主線糕篇,不要糾結(jié)于細(xì)枝末節(jié)啄育。永遠(yuǎn)記住你要解決什么問題。
  • 找到一篇優(yōu)質(zhì)的博客拌消、向前輩學(xué)習(xí)挑豌,讓前輩帶著你去學(xué)習(xí)事半功倍。
  • 想看某編程語言的代碼墩崩,必須要有扎實(shí)的語言基礎(chǔ)氓英。走路不穩(wěn)就想跑,會(huì)摔得很慘~
  • 學(xué)習(xí)之道鹦筹,不能盲目铝阐。應(yīng)該找到一種快速有效的方法,來有目的的實(shí)現(xiàn)學(xué)習(xí)目標(biāo)铐拐。不要用戰(zhàn)術(shù)上的勤奮來掩蓋戰(zhàn)略上的失誤徘键。看代碼如此遍蟋、看書學(xué)習(xí)亦如此~

如何開始

這里我們來解決從哪里開始看代碼的流程吹害,重點(diǎn)是找到Vue構(gòu)造函數(shù)的實(shí)現(xiàn)
首先虚青,找到 package.json 文件它呀,從中找到編譯命令 "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",這里 rollup 是類似于 Webpack 的打包工具挟憔,打包文件在 script/config.js 中,找到該文件烟号。找 entry 入口關(guān)鍵字(不會(huì)rollup绊谭,但配置方式和 Webpack 差不太多)。入口文件有好多配置汪拥,我們就找到會(huì)生成 dist/vue.js 的配置項(xiàng):

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

好达传,這里就找到了 web/entry-runtime-with-compiler.js 這個(gè)路徑,完整路徑應(yīng)該是 src/platform/web/entry-runtime-with-compiler.js迫筑。在這個(gè)文件中我們找到一個(gè)Vue對象import進(jìn)來了宪赶。

import Vue from './runtime/index'

我們順著找到到 src/platform/web/runtime/index.js 這個(gè)文件,在文件中發(fā)現(xiàn)導(dǎo)入文件

import Vue from 'core/index'

就順著這個(gè)思路找脯燃,最終找到 src/core/instance/index.js 這個(gè)文件搂妻。
完整找到Vue實(shí)例入口文件的流程如下:

package.json
script/config.js
src/platform/web/entry-runtime-with-compiler.js
src/platform/web/runtime/index.js
src/core/index.js
src/core/instance/index.js

簡單看看Vue構(gòu)造函數(shù)的樣子~

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) // 狀態(tài)混合
eventsMixin(Vue) // 事件混合
lifecycleMixin(Vue) // 生命周期混合
renderMixin(Vue) // 渲染混合

export default Vue

可以看到Vue的構(gòu)造函數(shù),里面只做了 this._init(options) 行為辕棚。這個(gè) _init 方法在執(zhí)行 initMixin 方法的時(shí)候定義了欲主。找到同目錄下的 init.js 文件邓厕。

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 {
      // 合并配置項(xiàng)
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm) // 初始化代理
    } else {
      vm._renderProxy = vm
    }
    
    vm._self = vm // 暴露對象自身
    initLifecycle(vm) // 初始化生命周期
    initEvents(vm) // 初始化事件:on,once,off,emit
    initRender(vm) // 初始化渲染:涉及到Virtual DOM
    callHook(vm, 'beforeCreate') //  觸發(fā) beforeCreate 生命周期鉤子
    initInjections(vm) // 在初始化 data/props 前初始化Injections
    initState(vm) // 初始化狀態(tài)選項(xiàng)
    initProvide(vm) // 在初始化 data/props 后初始化Provide
    // 有關(guān)inject和provide請查閱 https://cn.vuejs.org/v2/api/#provide-inject
    callHook(vm, 'created') // 觸發(fā) 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)
    }
    // 如果Vue配置項(xiàng)中有el,直接掛在到DOM中
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

抓住重點(diǎn)扁瓢,我們是要來學(xué)習(xí)State的详恼。從上面代碼中可以找到initState方法的執(zhí)行,這就是我們此行的目的——State數(shù)據(jù)選項(xiàng)引几。除此之外還有其他重要方法的初始化方式昧互,這將會(huì)在之后的博客中繼續(xù)討論和學(xué)習(xí)。

學(xué)習(xí)State

之前是簡單提一下學(xué)習(xí)源碼的方法論和如何開始學(xué)習(xí)Vue源碼學(xué)習(xí)伟桅。并且找到了我們要學(xué)習(xí)的State所在敞掘,現(xiàn)在進(jìn)入正題:

了解Vue的數(shù)據(jù)選項(xiàng)的運(yùn)行機(jī)制。

Vue2.1.7源碼學(xué)習(xí)中贿讹,作者已經(jīng)非常非常非常清晰明了的幫我們分析了data的實(shí)現(xiàn)渐逃。在此基礎(chǔ)上開始好好學(xué)習(xí)其他數(shù)據(jù)選項(xiàng)的實(shí)現(xiàn)邏輯。

通過data理解mvvm

這里我通過自己的思路再來整理下項(xiàng)目中data的實(shí)現(xiàn)民褂。
注:由于這一部分已經(jīng)被各類源碼解析博客講爛了茄菊,而要把這部分講清楚要大量篇幅。所以我就不貼代碼了赊堪。還是那句話面殖,抓重點(diǎn)!我們主要研究的是data之外的實(shí)現(xiàn)方式哭廉。關(guān)于data的實(shí)現(xiàn)和mvvm的逐步實(shí)現(xiàn)脊僚,Vue2.1.7源碼學(xué)習(xí)中講的非常清晰明了。

以下是我整理的思路遵绰,有興趣的同學(xué)可以順著我的思路去看看辽幌。

在 state.js 中找到 initState,并順利找到 initData 函數(shù)椿访。initData中主要做了以下幾步操作:

  1. 獲取data數(shù)據(jù)乌企,data數(shù)據(jù)通常是一個(gè)方法,執(zhí)行方法返回data數(shù)據(jù)成玫。所以說我們要將data寫成函數(shù)方法的形式加酵。
  2. 遍歷data數(shù)據(jù),判斷是否有data與props的key同名哭当,如果沒有執(zhí)行proxy方法猪腕,該方法用于將data中的數(shù)據(jù)同步到vm對象上,所以我們可以通過 vm.name 來修改和獲取 data 中的 name 的值钦勘。
  3. 執(zhí)行observe方法陋葡,監(jiān)聽data的變化。

重點(diǎn)在 observe 方法彻采,于是我們根據(jù) import 關(guān)系找到 src/core/observer/index.js 文件脖岛。observe 方法通過傳入的值最終返回一個(gè)Observer類的實(shí)例對象朵栖。
找到Observer類,在構(gòu)造函數(shù)中為當(dāng)前類創(chuàng)建Dep實(shí)例柴梆,然后判斷數(shù)據(jù)陨溅,如果是數(shù)組,觸發(fā) observeArray 方法绍在,遍歷執(zhí)行 observe 方法门扇;如果是對象,觸發(fā)walk方法偿渡。
找到walk方法臼寄,方法中遍歷了數(shù)據(jù)對象,為對象每個(gè)屬性執(zhí)行 defineReactive 方法溜宽。
找到 defineReactive 方法吉拳,該方法為 mvvm 數(shù)據(jù)變化檢測的核心。為對象屬性添加 set 和 get 方法适揉。重點(diǎn)來了留攒, vue 在 get 方法中執(zhí)行 dep.depend() 方法,在 set 方法中執(zhí)行 dep.notify() 方法嫉嘀。這個(gè)先不多講炼邀,最后進(jìn)行聯(lián)結(jié)說明。
找到同目錄下的 dep.js 文件剪侮,文件不長拭宁。定義了 Dep 類和pushTargetpopTarget 方法瓣俯。在 Dep 類中有我們之前提到的 dependnotify 方法杰标。看下兩個(gè)方法的實(shí)現(xiàn):

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }

depend 方法中彩匕,Dep.target 就是一個(gè) Watcher 實(shí)例腔剂,它的 addDep 方法最終會(huì)調(diào)用到 Dep 的 addSubs 方法。subs 是 Watcher 數(shù)組推掸。即將當(dāng)前 watcher 存到 Dep 的 subs 數(shù)組中桶蝎。
notify 方法中驻仅,將 Watcher 數(shù)組 subs 遍歷谅畅,執(zhí)行他們的 update 方法。update 最終會(huì)去執(zhí)行 watcher 的回調(diào)函數(shù)噪服。
即在 get 方法中將 watcher 添加到 dep毡泻,在 set 方法中通過 dep 對 watcher 進(jìn)行回調(diào)函數(shù)觸發(fā)。
這里其實(shí)已經(jīng)實(shí)現(xiàn)了數(shù)據(jù)監(jiān)聽粘优,接著我們來看看 Watcher仇味,其實(shí) Watcher 就是Vue中 watch 選項(xiàng)的實(shí)現(xiàn)了呻顽。說到 watch 選項(xiàng)我們都知道它用來監(jiān)聽數(shù)據(jù)變化。Watcher 就是實(shí)現(xiàn)這個(gè)過程的玩意啦~
Watcher的構(gòu)造函數(shù)最終調(diào)用了 get 方法丹墨,代碼如下:

 get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

get 方法做了如下幾步:

  1. 將當(dāng)前 Watcher 實(shí)例傳遞給 Dep 的 Dep.target廊遍。
  2. 執(zhí)行 Watcher 所監(jiān)測的數(shù)據(jù)的 getter 方法。
  3. 最終贩挣,將 Dep.target 恢復(fù)到上一個(gè)值喉前,并且將當(dāng)前 Watcher 從 Dep 的 subs 中去除。

其中要注意的是王财,在第二步中數(shù)據(jù)的 getter 方法會(huì)執(zhí)行到 dep.depend() 方法卵迂,depend 方法將當(dāng)前 watcher 加入到 subs 中。至于步驟一和三還不太理解绒净。挖個(gè)坑先~
這樣 watcher 就監(jiān)測上數(shù)據(jù)了见咒。那怎么使用呢?當(dāng)然是數(shù)據(jù)變化時(shí)使用咯挂疆。當(dāng)監(jiān)測的數(shù)據(jù)變化時(shí)改览,執(zhí)行數(shù)據(jù) setter 方法,然后執(zhí)行 dep 的 notify 方法囱嫩。由于我們之前已經(jīng)將 watcher 都收集到 dep 的 subs 中恃疯,notify 方法遍歷執(zhí)行 watcher 的 update 方法,update 方法最終遍歷執(zhí)行回調(diào)函數(shù)墨闲。

  1. 執(zhí)行 observe 方法今妄,創(chuàng)建 Observer 執(zhí)行 walk 為對象數(shù)據(jù)添加setter 和 getter
  2. 在添加 setter 和 getter 時(shí),創(chuàng)建 Dep鸳碧,在 getter 方法中執(zhí)行 dep.depend() 收集 watcher盾鳞,在 setter 方法中執(zhí)行 dep.notify() 方法,最終遍歷執(zhí)行 watcher 數(shù)組的回調(diào)函數(shù)瞻离。
  3. Dep 類似于 Watcher 和 Observer 的中間件腾仅。
  4. Watcher 用于監(jiān)聽變化,并執(zhí)行回調(diào)函數(shù)套利。
  5. 當(dāng) Watcher 實(shí)例創(chuàng)建時(shí)推励,Watcher 實(shí)例會(huì)將自身傳遞給 Dep.target
  6. Watcher 調(diào)用監(jiān)測數(shù)據(jù)的 getter方法觸發(fā) dep.depend()
  7. dep.depend()方法將當(dāng)前 Watcher(Dep.target)傳遞給Dep的subs(watcher數(shù)組)中。
  8. 當(dāng)被監(jiān)測的數(shù)據(jù)內(nèi)容發(fā)生改變時(shí)肉迫,執(zhí)行 setter 方法验辞,觸發(fā) dep.notify() 方法,遍歷 Dep 中的 subs(watcher數(shù)組)喊衫,執(zhí)行 Watcher 的回調(diào)函數(shù)跌造。

嗯……就是這樣~之后把挖的坑填上!

watch實(shí)現(xiàn)

說完了 Data 的監(jiān)聽流程,說說 watch 應(yīng)該就不難啦~
找到 src/core/instance/state.jsinitWatch 函數(shù)壳贪,該方法用來遍歷 Vue 實(shí)例中的 watch 項(xiàng)陵珍,最終所有 watch 都會(huì)執(zhí)行 createWatcher 方法。
繼續(xù)看 createWatcher 方法违施,這個(gè)方法也很簡單互纯,最終返回 vm.$watch(keyOrFn, handler, options)。我們繼續(xù)往下找~
stateMixin 方法中找到了定義 Vue 的 $watch 方法屬性磕蒲。來看看怎么實(shí)現(xiàn)的:

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      cb.call(vm, watcher.value)
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }

如果回調(diào)函數(shù) cb 是一個(gè)對象伟姐,那么返回并執(zhí)行 createWatcher 函數(shù),最終還是會(huì)走到 $watch 方法中亿卤。
否則愤兵,創(chuàng)建一個(gè) Watcher 實(shí)例,當(dāng)這個(gè)實(shí)例創(chuàng)建后排吴,目標(biāo)數(shù)據(jù)有任何變化 watch 選項(xiàng)中都能監(jiān)聽到了秆乳。如果是有 immediate 參數(shù),那么立即執(zhí)行一次Watcher的回調(diào)函數(shù)钻哩。最后返回一個(gè)解除監(jiān)聽的方法屹堰,執(zhí)行了 Watcher 的 teardown 方法。
那么問題來了街氢,為什么watch選項(xiàng)監(jiān)聽數(shù)據(jù)的方法中參數(shù)是如下寫法呢扯键?

watch: {
  a: function(val, oldVal){
    console.log(val)
  }
}

可以找到 src/core/instance/observer/watcher.js 中找到 run 方法∩核啵可以看到 this.cb.call(this.vm, value, oldValue) 這里的 cb 回調(diào)函數(shù)傳遞的參數(shù)就是 value 和 oldValue荣刑。
這里說個(gè)基礎(chǔ)知識,函數(shù)使用 call 方法執(zhí)行伦乔,第一個(gè)參數(shù)是方法的this值厉亏,之后才是真正的參數(shù)。

run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

小結(jié):watch 選項(xiàng)其實(shí)就是為指定數(shù)據(jù)創(chuàng)建 Watcher 實(shí)例烈和,接收回調(diào)函數(shù)的過程爱只。

props實(shí)現(xiàn)

接下來我們看看props,官網(wǎng)對props的定義如下:

props 可以是數(shù)組或?qū)ο笳猩玻糜诮邮諄碜愿附M件的數(shù)據(jù)恬试。

找到 initProps 方法。

function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  observerState.shouldConvert = isRoot
  for (const key in propsOptions) {
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      defineReactive(props, key, value, () => {
        if (vm.$parent && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop's ` +
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      defineReactive(props, key, value)
    }

    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  observerState.shouldConvert = true
}

可以看到疯暑,props 和 data 類似训柴。在 initProps 中無非做了兩步:defineReactiveproxy,這兩個(gè)方法我們在提到 data 的時(shí)候講過了缰儿。defineReactive 為數(shù)據(jù)設(shè)置 setter畦粮、getter散址,proxy 方法將 props 中的屬性映射到 Vue 實(shí)例 vm 上乖阵,便于我們可以用 vm.myProps 來獲取數(shù)據(jù)宣赔。
至此,我有個(gè)疑問:data與props有何不同呢瞪浸?
data使用的是observe方法儒将,創(chuàng)建一個(gè)Observer對象,Observer對象最終是執(zhí)行了defineReactive方法对蒲。而props是遍歷選項(xiàng)屬性钩蚊,執(zhí)行defineReactive方法。中間可能就多了個(gè)Observer對象蹈矮,那么這個(gè)Observer對象的作用到底在哪呢砰逻?經(jīng)過實(shí)踐props屬性改變后界面也會(huì)改變。說明mvvm對props也是成立的泛鸟。
另外蝠咆,data和props有個(gè)不同的地方就是props是不建議改變的。詳見單向數(shù)據(jù)流
小結(jié):邏輯和data類似北滥,都是監(jiān)聽數(shù)據(jù)刚操。不同之處呢……再研究研究~

computed實(shí)現(xiàn)

再來說說computed,找到初始化computed方法 src/core/instance/state.js 中的 initComputed 方法再芋,去除非關(guān)鍵代碼后看到其實(shí)主要有倆個(gè)行為菊霜,為 computed 屬性創(chuàng)建 Watcher,然后執(zhí)行 defineComputed方法济赎。

function initComputed (vm: Component, computed: Object) {
  ...
  for (const key in computed) {
    ...
    if (!isSSR) {
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } 
    ...
  }
}

defineComputed 做了兩步行為:一是定義 sharedPropertyDefinition 的 getter 和 setter鉴逞,二是將 sharedPropertyDefinition 的屬性傳給vm,即 Object.defineProperty(target, key, sharedPropertyDefinition)司训。自此华蜒,我們可以通過 vm.computedValue 來獲取計(jì)算屬性結(jié)果了。
小結(jié):computed其實(shí)也就是一個(gè)數(shù)據(jù)監(jiān)聽行為豁遭,與data和props不同之處就是在get函數(shù)中需要進(jìn)行邏輯計(jì)算處理叭喜。

methods實(shí)現(xiàn)

繼續(xù)在 state.js 中看到 initMethods 方法。顧名思義蓖谢,這是初始化methods的方法捂蕴。實(shí)現(xiàn)很簡單,代碼如下:

function initMethods (vm: Component, methods: Object) {
  const props = vm.$options.props
  for (const key in methods) {
    if (process.env.NODE_ENV !== 'production') {
      if (methods[key] == null) {
        warn(
          `Method "${key}" has an undefined value in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      }
      if (props && hasOwn(props, key)) {
        warn(
          `Method "${key}" has already been defined as a prop.`,
          vm
        )
      }
      if ((key in vm) && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
  }
}

重點(diǎn)在最后一句闪幽。前面都排除重名和空值錯(cuò)誤的啥辨,最后將 methods 中的方法傳給 vm,方法內(nèi)容如果為空則方法什么都不做盯腌。否則調(diào)用 bind 方法執(zhí)行該函數(shù)溉知。
找到這個(gè) bind 方法,位置在 src/shared/util.js 中。

export function bind (fn: Function, ctx: Object): Function {
  function boundFn (a) {
    const l: number = arguments.length
    return l
      ? l > 1
        ? fn.apply(ctx, arguments)
        : fn.call(ctx, a)
      : fn.call(ctx)
  }
  // record original fn length
  boundFn._length = fn.length
  return boundFn
}

該方法返回一個(gè)執(zhí)行 methods 中函數(shù)的方法(這種方法的執(zhí)行方式比較快)级乍。
小結(jié):將methods的方法用bind函數(shù)優(yōu)化執(zhí)行過程舌劳。然后將methods中的各個(gè)方法傳給Vue實(shí)例對象。

最后

本文純屬個(gè)人理解玫荣,如有任何問題甚淡,請及時(shí)指出,不勝感激~
最后提出一個(gè)看源碼的小心得:

我發(fā)現(xiàn)……看源碼捅厂、跟流程贯卦,盡量將注意力集中在方法的執(zhí)行類的實(shí)例化行為上。對于變量的獲取和賦值焙贷、測試環(huán)境警報(bào)提示撵割,簡略看下就行,避免逐行閱讀代碼拉低效率辙芍。

至此睁枕,Vue中的幾個(gè)數(shù)據(jù)選項(xiàng)都學(xué)習(xí)了一遍了。關(guān)鍵在于理解mvvm的過程沸手。data 理解之后外遇,props、watch契吉、computed 都好理解了跳仿。methods 和 mvvm 無關(guān)……
通過四個(gè)早上的時(shí)間把文章寫出來了~對 Vue 的理解深刻了一些,但是還是能感覺到有很多未知的知識點(diǎn)等著我去發(fā)掘捐晶。加油吧菲语!今年專注于 Vue 前端學(xué)習(xí),把 Vue 給弄懂惑灵!

Vue.js學(xué)習(xí)系列

鑒于前端知識碎片化嚴(yán)重山上,我希望能夠系統(tǒng)化的整理出一套關(guān)于Vue的學(xué)習(xí)系列博客。

Vue.js學(xué)習(xí)系列項(xiàng)目地址

本文源碼已收入到GitHub中英支,以供參考佩憾,當(dāng)然能留下一個(gè)star更好啦-
https://github.com/violetjack/VueStudyDemos

關(guān)于作者

VioletJack干花,高效學(xué)習(xí)前端工程師妄帘,喜歡研究提高效率的方法,也專注于Vue前端相關(guān)知識的學(xué)習(xí)池凄、整理抡驼。
歡迎關(guān)注、點(diǎn)贊肿仑、評論留言~我將持續(xù)產(chǎn)出Vue相關(guān)優(yōu)質(zhì)內(nèi)容致盟。

新浪微博: http://weibo.com/u/2640909603
掘金:https://gold.xitu.io/user/571d953d39b0570068145cd1
CSDN: http://blog.csdn.net/violetjack0808
簡書: http://www.reibang.com/users/54ae4af3a98d/latest_articles
Github: https://github.com/violetjack

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碎税,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子馏锡,更是在濱河造成了極大的恐慌雷蹂,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眷篇,死亡現(xiàn)場離奇詭異,居然都是意外死亡荔泳,警方通過查閱死者的電腦和手機(jī)蕉饼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玛歌,“玉大人昧港,你說我怎么就攤上這事≈ё樱” “怎么了创肥?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長值朋。 經(jīng)常有香客問我叹侄,道長,這世上最難降的妖魔是什么昨登? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任趾代,我火速辦了婚禮,結(jié)果婚禮上丰辣,老公的妹妹穿的比我還像新娘撒强。我一直安慰自己,他們只是感情好笙什,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布飘哨。 她就那樣靜靜地躺著,像睡著了一般琐凭。 火紅的嫁衣襯著肌膚如雪芽隆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天统屈,我揣著相機(jī)與錄音摆马,去河邊找鬼。 笑死鸿吆,一個(gè)胖子當(dāng)著我的面吹牛囤采,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惩淳,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蕉毯,長吁一口氣:“原來是場噩夢啊……” “哼乓搬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起代虾,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤进肯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后棉磨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體江掩,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年乘瓤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了环形。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衙傀,死狀恐怖抬吟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情统抬,我是刑警寧澤火本,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站聪建,受9級特大地震影響钙畔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜金麸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一刃鳄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钱骂,春花似錦叔锐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至解取,卻和暖如春步责,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背禀苦。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工蔓肯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人振乏。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓蔗包,卻偏偏與公主長得像,于是被迫代替她去往敵國和親慧邮。 傳聞我的和親對象是個(gè)殘疾皇子调限,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容