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

前言

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

開篇

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


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

分析

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

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

├── src ----------------------------------- 這個(gè)是我們最應(yīng)該關(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 ------------------------------ 存放通用的,平臺(tái)無關(guān)的代碼
│   │   ├── observer
│   │   ├── vdom
│   │   ├── instance ---------------------- 包含Vue構(gòu)造函數(shù)設(shè)計(jì)相關(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ù)在哪吧唆垃,之前的項(xiàng)目結(jié)構(gòu)里面我們也可以看到core文件夾有個(gè)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

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

然后options被傳進(jìn)了Vue原型里面的_init方法里面渐尿。options回顧一下就是之前的


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

init.js

這個(gè)文件里面主要內(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里面的其中一個(gè)函數(shù)隘擎,基本上是初始化生命周期的一些變量,refs也是這個(gè)階段初始化的凉夯,這個(gè)我們后面會(huì)有一章單獨(dú)講生命周期货葬。

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其中一個(gè)函數(shù),暫時(shí)我還沒讀完這一部分的代碼劲够,不是很懂具體是干什么的震桶,我系列寫完會(huì)回來重新補(bǔ)充的。

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

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

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

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

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

然而typeof null為object隘截,可null又不可能是個(gè)對象,也沒proto指針,很神奇的東西婶芭。

initRender

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

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的一個(gè)函數(shù)惰赋,這個(gè)我也沒看,后面補(bǔ)充呵哨。赁濒。。

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的一個(gè)函數(shù)孟害,可以看到props,methods,data,computed,watch都是這個(gè)時(shí)候初始化的拒炎。

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里面的一個(gè)函數(shù),這個(gè)也后面補(bǔ)充吧挨务。击你。

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的生命周期鉤子實(shí)現(xiàn)丁侄。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谷誓,隨后出現(xiàn)的幾起案子绒障,更是在濱河造成了極大的恐慌,老刑警劉巖捍歪,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件户辱,死亡現(xiàn)場離奇詭異,居然都是意外死亡糙臼,警方通過查閱死者的電腦和手機(jī)庐镐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來变逃,“玉大人必逆,你說我怎么就攤上這事±柯遥” “怎么了名眉?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凰棉。 經(jīng)常有香客問我损拢,道長,這世上最難降的妖魔是什么撒犀? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任福压,我火速辦了婚禮掏秩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘荆姆。我一直安慰自己蒙幻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布胆筒。 她就那樣靜靜地躺著邮破,像睡著了一般。 火紅的嫁衣襯著肌膚如雪仆救。 梳的紋絲不亂的頭發(fā)上决乎,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機(jī)與錄音派桩,去河邊找鬼构诚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛铆惑,可吹牛的內(nèi)容都是我干的范嘱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼员魏,長吁一口氣:“原來是場噩夢啊……” “哼丑蛤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起撕阎,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤受裹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后虏束,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棉饶,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年镇匀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了照藻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汗侵,死狀恐怖幸缕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晰韵,我是刑警寧澤发乔,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站雪猪,受9級特大地震影響栏尚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浪蹂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一抵栈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坤次,春花似錦古劲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至滑绒,卻和暖如春闷堡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背疑故。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工杠览, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纵势。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓踱阿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钦铁。 傳聞我的和親對象是個(gè)殘疾皇子软舌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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