vue 原始碼(一)
1. vue 目錄結構 (src)
|-- dist 打包後的vue版本 |-- flow 型別檢測,3.0換了typeScript // 使用flow定義和檢測型別,增強程式碼健壯性 |-- script 構建不同版本vue的相關配置 |-- src 原始碼 |-- compiler 編譯器 // 將template編譯成render函式 |-- core 不區分平臺的核心程式碼 |-- components 通用的抽象元件 |-- global-api 全域性API |-- instance 例項的建構函式和原型方法 |-- observer 資料響應式 // vue檢測資料變化並更新檢視的實現 |-- util 常用的工具方法 |-- vdom 虛擬dom相關 // 將render函式轉為vnode從而patch為真實dom以及diff演算法的程式碼實現 |-- platforms 不同平臺不同實現 |-- server 服務端渲染 |-- sfc .vue單檔案元件解析 |-- shared 全域性通用工具方法 |-- test 測試
和執行時版本的關鍵所在,完整版就自帶這個編譯器,而執行時版本就沒有,
(面試題) 請問 runtime compiler 和 runtime-only 這兩個版本的區別?
檔案大小的區別,帶編譯的版本要比不帶的版本檔案要大6kb左右
編譯時機不同:編譯執行時版本會在執行時進行編譯,效能會有一定損耗;執行時版本藉助於 vue-loader 做的離線編譯,執行速度更快。
2.vue入口 首先是建構函式 core/instance/index.js
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方法。 stateMixin(Vue):定義資料相關的方法$set,$delete,$watch方法。 eventsMixin(Vue):定義事件相關的方法$on,$once,$off,$emit。 lifecycleMixin(Vue):定義_update,及生命週期相關的$forceUpdate和$destroy。 renderMixin(Vue):定義$nextTick,_render將render函式轉為vnode。
這些初始化後還會定義一些全域性的API,initGlobalAPI(),裡面包括
initUse(Vue) // Vue.use() initMixin(Vue) // Vue.mixins() initExtend(Vue) // Vue.extends() initAssetRegisters(Vue) // Vue.component,Vue.directive,Vue.filter方法
this._init(options) 是 core/instance/init.js中的掛載到vue的proptype原型鏈上的。
此處的options是傳進來的Vue例項,eg:
new Vue( { el: '#app', data: { message: 'hello world' }, components: {} } ) Vue.prototype._init = function (options?: Object) { // 如果options裡沒有傳components就開始解析構造器,傳了就初始化這個元件 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 ) }} 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')
所以在beforeCreate生命週期前vue 初始化了生命週期、事件處理機制
在created之前vue 初始化事件通訊、options相關的
vue/src/core/instance/index.js stateMixin(Vue)
state.js
function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) } // proxy data on instance 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] if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { process.env.NODE_ENV !== 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) { proxy(vm, `_data`, key) } } // observe data observe(data, true /* asRootData */) }
這是初始化data函式的方法,其中將props和methods中的物件和函式與data中的比較,避免重複。這裡只在data裡傳了一個message欄位,走到until/lang.js isReserved()
export function isReserved (str: string): boolean { const c = (str + '').charCodeAt(0) return c === 0x24 || c === 0x5F }
0x24和0x5F是$和_對應的ASCII碼值,isReserved函式是判斷data裡的欄位是否帶有$和_,為true會走到 proxy(vm, `_data`, key)這裡,這裡使用了代理模式
export function proxy (target: Object, sourceKey: string, key: string) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }
這裡是Object.defineProperty的實現,set和get, 然後實現observe(data, true /* asRootData */)
../observer/index.js
xport function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }