在學習vue源碼以前須要先了解源碼目錄設計(瞭解各個模塊的功能)丶Flow語法。html
src
├── compiler # 把模板解析成 ast 語法樹,ast 語法樹優化,代碼生成等功能。
├── core # 核心代碼 Vue.js 的靈魂
├── platforms # 不一樣平臺的支持 web 和 weex
├── server # 服務端渲染這部分代碼是跑在服務端的 Node.js
├── sfc # .vue 文件解析
├── shared # 工具方法
複製代碼
flow語法能夠參照 v-model源碼學習中提到的flow語法介紹
,以及到官網瞭解更多。vue
vue 本質上就是一個用 Function 實現的 Class,而後它的原型 prototype 以及它自己都擴展了一系列的方法和屬性node
在 src/core/instance/index.js 中git
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
複製代碼
經過源碼咱們能夠看到,它實際上就是一個構造函數。咱們日後看這裏有不少 xxxMixin 的函數調用,並把 Vue 當參數傳入,它們的功能都是給 Vue 的 prototype 上擴展一些方法。github
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
// A-> ..... 表明後面省略的代碼從A-> 處接下去
}
複製代碼
1.這段代碼首先緩存了原型上的$mount 方法,再從新定義該方法
爲了對比先後方法的差異,咱們能夠先看web
query
方法轉換成DOM對象,在瀏覽器環境下咱們不須要傳第二個參數,它是一個可選參數。
// <-A ..... 表明接前面的代碼繼續寫
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
// A-> ..... 表明後面省略的代碼從A-> 處接下去
複製代碼
首先對 el 作了限制,Vue 不能掛載在 body、html 這樣的根節點上。若是是其中一個則返回this。this就是vue實例自己 數組
// <-A ..... 表明接前面的代碼繼續寫
if (!options.render) {
let template = options.template
if (template) {
// B-> ..... 表明後面省略的代碼從B-> 處接下去
}else if(el){
// C-> ..... 表明後面省略的代碼從C-> 處接下去
}
if (template) {
// D-> ..... 表明後面省略的代碼從D-> 處接下去
}
return mount.call(this, el, hydrating)
}
複製代碼
// <-B ..... 表明接前面的代碼繼續寫
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
}
}
複製代碼
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
複製代碼
接受一個參數,對這個參數進行query方法,前面提到query是將字符串轉化成DOM,而且返回DOM的innerHTML瀏覽器
// <-C ..... 表明接前面的代碼繼續寫
template = getOuterHTML(el)
複製代碼
若是沒有render和template的狀況下,使用getOuterHTML方法從新定義template緩存
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
}
}
複製代碼
render: function (createElement) {
return createElement(
'h' + this.level, // 標籤名稱
this.$slots.default // 子元素數組
)
},
複製代碼
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
複製代碼
開始和compiler
版本的$mount
實現相同,只不過多加了一個inBrowser判斷是否在瀏覽器環境下。
$mount 方法實際上會去調用 mountComponent 方法,這個方法定義在 src/core/instance/lifecycle.js 文件中bash
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
}
callHook(vm, 'beforeMount')
// A-> ..... 表明後面省略的代碼從A-> 處接下去
}
複製代碼
// <-A ..... 表明接前面的代碼繼續寫
let updateComponent
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
}else{
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
vm._watcher = new Watcher(vm, updateComponent, noop)
// A-> ..... 表明後面省略的代碼從A-> 處接下去
複製代碼
vm._render
方法先生成虛擬 Node 將 vm._update方法做爲返回值賦值給updateComponent
Watcher
構造函數,將updateComponen
t做爲回調函數
,也就是說在實例化Watcher後最終調用vm._update
更新 DOM。vm._update
更新 DOM。// <-A ..... 表明接前面的代碼繼續寫
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
複製代碼
這裏vm.$vnode的值是什麼,文件定義在src/core/instance/render.js 中,這裏只關注vm.$vnode因此貼出相關代碼
export function renderMixin (Vue: Class<Component>) {
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
vm.$vnode = _parentVnode
}
}
複製代碼
renderMixin函數接收Vue實例參數,在vue原型上的內部_render方法須要返回一個VNode,而且經過結構賦值的方法取出實例中$options的屬性和方法。
咱們來看看vm.$options對象具體有些什麼