手把手教你讀Vue2源碼-1

vue源碼:https://github.com/vuejs/

這里肴沫,我的調(diào)試環(huán)境為:
window10 x64
vue2.5

今天的目標(biāo):搭建調(diào)試環(huán)境醋寝,找入口文件

要學(xué)習(xí)源碼甚亭,就要先學(xué)會如何調(diào)試源碼嘹悼,所以我們第一步可以先拉取vue源碼萨醒,然后在本地配置下調(diào)試環(huán)境:

cd ./vue
npm i
npm npm i -g rollup
// 修改package.json中script中dev運行腳本艇纺,添加--sourcemap
"dev": "rollup -w -c build/config.js --sourcemap --environment TARGET:web-full-dev",

npm run dev

// 之后就會在dist目錄下生成vue.js浮毯,方便我們用于測試和調(diào)試了~

配制調(diào)試環(huán)境的坑

  1. 安裝過程可能提示找不到PhantomJS:
    PhantomJS not found on PATH
    Downloading https://github.com/Medium/phantomjs/releases/download/v2.1.1/phantomjs-2.1.1-windows.zip Saving to C:\Users\ADMINI~1\AppData\Local\Temp\phantomjs\phantomjs-2.1.1-windows.zip Receiving...
    根據(jù)提示级零,去下載壓縮包断医,放到對應(yīng)位置就好

  2. 提示以下錯誤
    Error: Could not load D:\vue-source\vue\src\core/config (imported by D:\vue-source\vue\src\platforms\web\entry-runtime-with-compiler.js): ENOENT: no such file or directory, open 'D:\vue-source\vue\src\core/config'
    查了下資料,說是rollup-plugin-alias插件中解析路徑的問題奏纪,有人提PR了(https://github.com/vuejs/vue/issues/2771)鉴嗤,尤大大說是沒有針對window10做處理造成的,解決方法是將 node_modules/rollup-plugin-alias/dist/rollup-plugin-alias.js 改為

// var entry = options[toReplace]
// 81行序调,上面那句醉锅,改為:
var entry = normalizeId(options[toReplace]);

打包后dist輸出的文件一些后綴說明

dist目錄.png
  • 有runtime字樣的:說明只能在運行時運行,不包含編譯器(也就是如果我們直接使用template模板发绢,是不能正常編譯識別的)
  • common:commonjs規(guī)范硬耍,用于webpack1
  • esm:es模塊垄琐,用于webpack2+
  • 沒帶以上字樣的: 使用umd,統(tǒng)一模塊標(biāo)準(zhǔn)默垄,兼容cjs和amd此虑,用于瀏覽器,也是之后我們要用于測試的文件

我們可以在test文件夾下口锭,創(chuàng)建我們的測試文件test1.html:


創(chuàng)建測試文件test1.html
<!-- test/test1.html -->
<head>
  <script src="../dist/vue.js"></script>
</head>
<body>
  <div id="demo">
    <h1>初始化流程</h1>
    <p>{{msg}}</p>
  </div>
  <script>
    const app = new Vue({
      el: "#demo",
      data: {
        msg: 'hello'
      }
    })
  </script>
</body>

找入口文件

  1. 在package.json中朦前,dev運行腳本中找配置文件(-c 指向配置文件):rollup -w -c build/config.js --sourcemap --environment TARGET:web-full-dev
  2. 進(jìn)入配置文件中,根據(jù)TARGET找到對應(yīng)的配置文件TARGET:web-full-dev鹃操,搜索這個環(huán)境韭寸,讓到對應(yīng)的entry入口文件
// 1. build/config.js根據(jù)target環(huán)境,來找entry入口
const builds = {
  // 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
  },
}

// 2. 查看resolve解析方法荆隘,從中看出web是在別名文件中有對應(yīng)地址
const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

// 3. 根據(jù)aliases找到alias.js文件恩伺,從中找到web對應(yīng)的相應(yīng)地址
module.exports = {
  web: resolve('src/platforms/web'),
}

// 4. 最后根據(jù)拼接規(guī)則,我們終于找到真正的對應(yīng)入口
src/platforms/web/entry-runtime-with-compiler.js

查看入口文件

帶個問題去看源碼椰拒,以下這個vue實例中晶渠,最終掛載起作用是的哪個?

// render,template,el哪個的優(yōu)先級高燃观?
const app = new Vue({
  el: "#demo",
  template: "<div>template</div>",
  render(h) {
    return h('div', 'render')
  },
  data: {
    foo: 'foo'
  }
})

// 答案:render > template > el

可以從源碼找答案褒脯,在主要的地方我已添加中文注釋(英文注釋是源碼本身的),可查看對應(yīng)注釋地方:

// 保存原來的$mount
const mount = Vue.prototype.$mount
// 覆蓋默認(rèn)的$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  // 如果看到有以下這樣注釋的缆毁,一般用于調(diào)試階段輸出一些警告信息番川,我們在學(xué)習(xí)時為了簡單點,可以直接忽略的部分
  /* 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)
          //...
        }
      } 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)
    }

    // 如果存在模板颁督,執(zhí)行編譯
    if (template) {
      // ...

      // 編譯得到渲染函數(shù)
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

    }
  }
  // 最后執(zhí)行掛載,可以看到使用的是父級原來的mount方式掛載
  return mount.call(this, el, hydrating)
}

從以上1浇雹,2沉御,3步驟中,我們就可以得出剛剛的答案了昭灵。

src/platforms/web/entry-runtime-with-compiler.js文件作用:入口文件嚷节,覆蓋$mount,執(zhí)行模板解析和編譯工作

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

這里主要找Vue的構(gòu)造函數(shù)虎锚,中間路過一些文件會寫一些大概的作用硫痰,但主線不會偏離我們的目標(biāo)

  1. 在入口文件entry-runtime-with-compiler.js中,可以查看Vue引入文件
import Vue from './runtime/index'
  1. /runtime/index.js文件
import Vue from 'core/index'

// 定義了patch補洞芑ぁ:將虛擬dom轉(zhuǎn)為真實dom
Vue.prototype.__patch__ = inBrowser ? patch : noop

// 定義$mount
// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
  1. core/index.js文件
import Vue from './instance/index'

// 定義了全局API
initGlobalAPI(Vue)
  1. src/core/instance/index.js文件
    終于找到了Vue的構(gòu)造函數(shù)效斑,它只做了一件事,就是初始化柱徙,這個初始化方法是通過minxin傳送到這個文件的缓屠,所以我們接下來是要去查看Init的方法奇昙,這也是我們以后要常看的一個文件
// 構(gòu)造函數(shù)
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)  // _init方法是通過mixin傳入的储耐,從這里可以找到初始化方法
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

初始化方法定義的文件 src/core/instance/init.js

劃重點,比較重要滨溉,可以看出初始化操作主要做以下事件:

initLifecycle(vm) // 初始化生命周期什湘,聲明$parten,$root,$children(空的),$refs
initEvents(vm)  // 對父組件傳入的事件添加監(jiān)聽
initRender(vm)  // 聲明$slot,$createElement()
callHook(vm, 'beforeCreate') // 調(diào)用beforeCreate鉤子
initInjections(vm) // 注入數(shù)據(jù) resolve injections before data/props
initState(vm) // 重中之重:數(shù)據(jù)初始化晦攒,響應(yīng)式
initProvide(vm) // 提供數(shù)據(jù) resolve provide after data/props
callHook(vm, 'created') // 調(diào)用created鉤子
// 定義初始化方法
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    // ...

    // a flag to avoid this being observed
    vm._isVue = true

    // 合并選項闽撤,將用戶設(shè)置的options和vue默認(rèn)設(shè)置的options,做一個合并處理
    // 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 {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    //...
    
    // 重點在這里脯颜,初始化的一堆操作哟旗!
    // expose real self
    vm._self = vm
    initLifecycle(vm) // 初始化生命周期,聲明$parten,$root,$children(空的),$refs,這里說明創(chuàng)建組件是自上而下的
    initEvents(vm)  // 對父組件傳入的事件添加監(jiān)聽
    initRender(vm)  // 聲明$slot栋操,$createElement()
    callHook(vm, 'beforeCreate') // 調(diào)用beforeCreate鉤子
    initInjections(vm) // 注入數(shù)據(jù) resolve injections before data/props
    initState(vm) // 重中之重:數(shù)據(jù)初始化闸餐,響應(yīng)式
    initProvide(vm) // 提供數(shù)據(jù) resolve provide after data/props
    callHook(vm, 'created') // 調(diào)用created鉤子

    // ...

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

如有錯誤之處,還望指出哈

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末矾芙,一起剝皮案震驚了整個濱河市舍沙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蠕啄,老刑警劉巖场勤,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戈锻,死亡現(xiàn)場離奇詭異歼跟,居然都是意外死亡,警方通過查閱死者的電腦和手機格遭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門哈街,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拒迅,你說我怎么就攤上這事骚秦。” “怎么了璧微?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵作箍,是天一觀的道長。 經(jīng)常有香客問我前硫,道長胞得,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任屹电,我火速辦了婚禮阶剑,結(jié)果婚禮上跃巡,老公的妹妹穿的比我還像新娘。我一直安慰自己牧愁,他們只是感情好素邪,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著猪半,像睡著了一般兔朦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上办龄,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天烘绽,我揣著相機與錄音,去河邊找鬼俐填。 笑死安接,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的英融。 我是一名探鬼主播盏檐,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼驶悟!你這毒婦竟也來了胡野?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤痕鳍,失蹤者是張志新(化名)和其女友劉穎硫豆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笼呆,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡熊响,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了诗赌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汗茄。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖铭若,靈堂內(nèi)的尸體忽然破棺而出洪碳,到底是詐尸還是另有隱情,我是刑警寧澤叼屠,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布瞳腌,位于F島的核電站,受9級特大地震影響镜雨,放射性物質(zhì)發(fā)生泄漏嫂侍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吵冒。 院中可真熱鬧纯命,春花似錦、人聲如沸痹栖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揪阿。三九已至疗我,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間南捂,已是汗流浹背吴裤。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留溺健,地道東北人麦牺。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像鞭缭,于是被迫代替她去往敵國和親剖膳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

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