在開始以前,閱讀源碼你須要有紮實的基本功,還要有耐心,能把握全局,不要扣細節!html
├── build --------------------------------- 構建相關的文件
├── dist ---------------------------------- 構建後文件的輸出目錄
├── examples ------------------------------ 存放使用Vue開發的的例子
├── flow ---------------------------------- 類型聲明,使用開源項目 [Flow](https://flowtype.org/)
├── package.json -------------------------- 項目依賴
├── test ---------------------------------- 包含全部測試文件
├── src ----------------------------------- 這個是咱們最應該關注的目錄,包含了源碼
│ ├──platforms --------------------------- 包含平臺相關的代碼
│ │ ├──web ----------------------------- 包含了不一樣構建的包的入口文件
│ │ | ├──entry-runtime.js ---------------- 運行時構建的入口,輸出 dist/vue.common.js 文件,不包含模板(template)到render函數的編譯器,因此不支持 `template` 選項,咱們使用vue默認導出的就是這個運行時的版本。你們使用的時候要注意
│ │ | ├── entry-runtime-with-compiler.js -- 獨立構建版本的入口,輸出 dist/vue.js,它包含模板(template)到render函數的編譯器
│ ├── compiler -------------------------- 編譯器代碼的存放目錄,將 template 編譯爲 render 函數
│ │ ├── parser ------------------------ 存放將模板字符串轉換成元素抽象語法樹的代碼
│ │ ├── codegen ----------------------- 存放從抽象語法樹(AST)生成render函數的代碼
│ │ ├── optimizer.js ------------------ 分析靜態樹,優化vdom渲染
│ ├── core ------------------------------ 存放通用的,平臺無關的代碼
│ │ ├── observer ---------------------- 反應系統,包含數據觀測的核心代碼
│ │ ├── vdom -------------------------- 包含虛擬DOM建立(creation)和打補丁(patching)的代碼
│ │ ├── instance ---------------------- 包含Vue構造函數設計相關的代碼
│ │ ├── global-api -------------------- 包含給Vue構造函數掛載全局方法(靜態方法)或屬性的代碼
│ │ ├── components -------------------- 包含抽象出來的通用組件
│ ├── server ---------------------------- 包含服務端渲染(server-side rendering)的相關代碼
│ ├── sfc ------------------------------- 包含單文件組件(.vue文件)的解析邏輯,用於vue-template-compiler包
│ ├── shared ---------------------------- 包含整個代碼庫通用的代碼複製代碼
使用 new
操做符來調用 Vue
,Vue
是一個構造函數,瞭解了目錄結構,下面來看下下入口文件vue
打開package.jsonnode
當咱們運行npm run dev,看看幹了啥,rollup也是相似webpack的打包工具,根據react
TARGET=web-full-devwebpack
打開入口文件找到了web/entry-runtime-with-compiler.js
web
依照以上查找路徑,咱們找到了Vue構造函數算法
定義了構造函數,引入依賴,調用初始化函數,最後導出Vueexpress
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue複製代碼
打開這五個文件,找到相應的方法,你會發現,這些方法的做用,就是在 Vue 的原型 prototype 上掛載方法或屬性npm
Vue.prototype._init = function (options) {}
複製代碼
Vue.prototype.$data
Vue.prototype.$props
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function(){}
複製代碼
Vue.prototype.$on
Vue.prototype.$once
Vue.prototype.$off
Vue.prototype.$emit
複製代碼
Vue.prototype._update
Vue.prototype.$forceUpdate
Vue.prototype.$destroy 複製代碼
Vue.prototype.$nextTick
Vue.prototype._render
Vue.prototype._o = markOnce
Vue.prototype._n = toNumber
Vue.prototype._s = toString
Vue.prototype._l = renderList
Vue.prototype._t = renderSlot
Vue.prototype._q = looseEqual
Vue.prototype._i = looseIndexOf
Vue.prototype._m = renderStatic
Vue.prototype._f = resolveFilter
Vue.prototype._k = checkKeyCodes
Vue.prototype._b = bindObjectProps
Vue.prototype._v = createTextVNode
Vue.prototype._e = createEmptyVNode
Vue.prototype._u = resolveScopedSlots
Vue.prototype._g = bindObjectListeners
複製代碼
引入依賴,在Vue上掛載靜態方法和屬性json
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
initGlobalAPI(Vue)
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode && this.$vnode.ssrContext
}
})
Vue.version = '__VERSION__'
export default Vue複製代碼
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__'
複製代碼
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement
// 安裝平臺特定的 指令 和 組件
Vue.options = {
components: {
KeepAlive,
Transition,
TransitionGroup
},
directives: {
model,
show
},
filters: {},
_base: Vue
}
Vue.prototype.__patch__
Vue.prototype.$mount複製代碼
web-runtime.js
文件的 $mount
函數,const mount = Vue.prototype.$mountcompile
而後覆蓋覆蓋了 Vue.prototype.$mount
template
編譯爲render函數。到這整個Vue構造函數就還原了
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue.js grid component example</title>
</head>
<body>
<div id="app">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
<Child></Child>
</div>
</body>
</html>
複製代碼
grid.js
let Child = {
data: function() {
return {child: '你好哈'}
},
template: '<div>{{child}}</div>'
}
new Vue({
el: '#app',
data: {
todos: [
{text: '學習 JavaScript'},
{text: '學習 Vue'},
{text: '整個牛項目'}]
},
components: {'Child': Child}
})複製代碼
Vue.prototype._init = function (options) {
const vm= this
vm._uid = uid++
let startTag, endTag
vm._isVue = true
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._renderProxy = vm
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
複製代碼
_init()
方法在一開始的時候,在 this
對象上定義了兩個屬性:_uid
和 _isVue
,而後判斷有沒有定義 options._isComponent
這裏會走 else
分支,也就是這段代碼:
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)mergeOptions使用策略模式合併傳入的options和Vue.options複製代碼
initLifecycle
、
initEvents
、
initRender、initState
,且在
initState
先後分別回調了生命週期鉤子
beforeCreate
和
created,
看到這裏,也就明白了爲何 created 的時候不能操做DOM了。由於這個時候尚未渲染真正的DOM元素到文檔中。
created
僅僅表明數據狀態的初始化完成。
重點看下initState()
initState (vm) {
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)
}
}複製代碼
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
proxy(vm, `_data`, key)
}
observe(data, true /* asRootData */)
}
複製代碼
proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}複製代碼
class Observer {
constructor(value) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value)
} else {
this.walk(value)
}
}
walk(obj: Object) {
const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
observeArray(items) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
複製代碼
在 Observer
類中,咱們使用 walk
方法對數據data的屬性循環調用 defineReactive
方法,defineReactive
方法很簡單,僅僅是將數據data的屬性轉爲訪問器屬性,並對數據進行遞歸觀測,不然只能觀測數據data的直屬子屬性。這樣咱們的第一步工做就完成了,當咱們修改或者獲取data屬性值的時候,經過 get
和 set
即能獲取到通知。
function defineReactive (
obj,
key,
val,
customSetter,
shallow
) {
const dep = new Dep()
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
val = newVal
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
複製代碼
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}export const arrayMethods = Object.create(arrayProto)
;[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
.forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
複製代碼
Vue.prototype._init = function (options) {
const vm= this
vm._uid = uid++
let startTag, endTag
vm._isVue = true
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._renderProxy = vm
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}複製代碼
進入$mount會先獲取掛載el節點,而後先判斷有沒有傳入render方法,沒有在去找有沒有傳入template,
Vue.prototype.$mount = function (
el,
hydrating
){
el = el && query(el)
const options = this.$options
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)
}
if (template) {
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`${this._name} compile`, 'compile', 'compile end')
}
}
}
return mount.call(this, el, hydrating)
}
複製代碼
function() {
with (this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('ol', _l((this.todos), function(todo) {
return _c('li', [_v("\n " + _s(todo.text) + "\n ")])
})), _v(" "), _c('child')], 1)
}
}複製代碼
function mountComponent (
vm,
el,
hydrating
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
}
callHook(vm, 'beforeMount')
let updateComponent = () => {
vm._update(vm._render(), hydrating)
}
vm._watcher = new Watcher(vm, updateComponent, noop)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
複製代碼
查看Watcher代碼
class Watcher {
constructor (
vm,
expOrFn,
cb,
options
) {
this.vm = vm
vm._watchers.push(this)
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
this.value = this.lazy
? undefined
: this.get()
}
get () {
pushTarget(this)
let value
const vm = this.vm
value = this.getter.call(vm, vm)
return value
}
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
evaluate () {
this.value = this.get()
this.dirty = false
}
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
}
複製代碼
執行構造函數因爲 this.lazy=false; this.value = this.lazy ? undefined : this.get();
執行get方法 其中pushTarget(this),給Dep.target添加靜態屬性this(當前new Watcher()實例 )
function pushTarget (_target) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
get () {
pushTarget(this)
let value
const vm = this.vm
value = this.getter.call(vm, vm)
return value
}
複製代碼
接着執行 this.getter.call(vm, vm)複製代碼
this.getter就是
updateComponent = () => {
vm._update(vm._render(), hydrating)
} 複製代碼
Vue.prototype._render = function (){
const vm = this
const {
render,
staticRenderFns,
_parentVnode
} = vm.$options
let vnode = render.call(vm._renderProxy, vm.$createElement)
return vnode
}
複製代碼
開始執行以前編譯好的render函數了,在執行render函數時,經過獲取todos屬性等,觸發相應
function() {
with (this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('ol', _l((this.todos), function(todo) {
return _c('li', [_v("\n " + _s(todo.text) + "\n ")])
})), _v(" "), _c('child')], 1)
}
}複製代碼
的get方法,這個時候Dep.target已經存在靜態屬性,Watcher實例了
因此相應的dep實例就會收集對應的Watcher實例了
複製代碼
執行完以後返回vnode,
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
其中vm._render()執行render函數返回vnode做爲參數
接下來執行vm._update
這是首次渲染,因此執行
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false,
vm.$options._parentElm,
vm.$options._refElm
)複製代碼
Vue.prototype._update = function (vnode, hydrating) {
const vm: Component = this
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
const prevEl = vm.$el
const prevVnode = vm._vnode
const prevActiveInstance = activeInstance
activeInstance = vm
vm._vnode = vnode
if (!prevVnode) {
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false,
vm.$options._parentElm,
vm.$options._refElm
)
vm.$options._parentElm = vm.$options._refElm = null
} else {
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}
複製代碼
若是尚未 prevVnode
說明是首次渲染,直接建立真實DOM。若是已經有了 prevVnode
說明不是首次渲染,那麼就採用 patch
算法進行必要的DOM操做。這就是Vue更新DOM的邏輯。只不過咱們沒有將 virtual DOM 內部的實現。
當改變屬性值時,會觸發對應的屬性的set方法,因爲以前執行render的時候觸發了get,收集了對應的Watcher,因此改變值時觸發set,通知以前收集的Watcher實例執行,從新計算render方法進行patch操做
最後盜取一張圖:
寫了半天,實在寫不下去了,之後有好的語言,再來整理吧!