在開始以前,閱讀源碼你須要有紮實的基本功,還要有耐心,能把握全局,不要扣細節!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操做
最後盜取一張圖:
寫了半天,實在寫不下去了,之後有好的語言,再來整理吧!