Vue框架對於前端來講有多重要就很少提了,三天前決定看看源碼,奈何本身是個菜雞,只能慢慢的一點一點啃,進行掃蕩式學習,初有收穫,特將筆記所記內容記下,邏輯略亂,各位客官以爲亂或者有問題的話請評論說下,我會從新組織語言並回答您。html
本文爲小白從頭掃蕩式教程,我都能懂你確定也能的~前端
好的,下面開始。vue
首先你要去github把源碼下載下來啦-- https://github.com/vuejs/vue node
首先咱們進入package.json文件 在咱們 npm run dev後執行這個語句ios
"scripts": {
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
-w爲watch,監聽,-c修改執行文件路徑,進入scripts/config.js文件內,最底部。git
if (process.env.TARGET) { module.exports = genConfig(process.env.TARGET) } else { exports.getBuild = genConfig exports.getAllBuilds = () => Object.keys(builds).map(genConfig) }
process.env.TARGET存在,執行github
function genConfig (name) { const opts = builds[name] const config = { input: opts.entry, external: opts.external, sourceMap:true, plugins: [ replace({ __WEEX__: !!opts.weex, __WEEX_VERSION__: weexVersion, __VERSION__: version }), flow(), buble(), alias(Object.assign({}, aliases, opts.alias)) ].concat(opts.plugins || []), output: { file: opts.dest, format: opts.format, banner: opts.banner, name: opts.moduleName || 'Vue' } } if (opts.env) { config.plugins.push(replace({ 'process.env.NODE_ENV': JSON.stringify(opts.env) })) } Object.defineProperty(config, '_name', { enumerable: false, value: name }) return config }
上面方法爲把builds相同屬性的內容賦給opts,因此咱們的opts以下web
'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 },
接下來看到入口文件爲resolve('web/entry-runtime-with-compiler.js') npm
const aliases = require('./alias') 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) } }
resolve方法將傳入參數通過aliases方法改變,因此最後咱們獲得的入口路徑爲src/platforms/web/entry-runtime-with-compiler.js。json
在該文件內,Vue由./runtime/index導入,進入後,Vue由core/index導入,進入後,Vue由./instance/index導入(instance有實例的意思),進入後,便可發現Vue的構造函數。
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
Vue構造函數中,前五行判斷開發者必須用NEW實例化vue,
new vue時,其中的el等值傳到option內,而後調用_init方法。
構造函數下方,調用initMixin(),進入該文件內
1 let uid = 0 2 3 export function initMixin (Vue: Class<Component>) { 4 Vue.prototype._init = function (options?: Object) { 5 const vm: Component = this 6 // a uid 7 vm._uid = uid++ 8 9 let startTag, endTag 10 /* istanbul ignore if */ 11 if (process.env.NODE_ENV !== 'production' && config.performance && mark) { 12 startTag = `vue-perf-start:${vm._uid}` 13 endTag = `vue-perf-end:${vm._uid}` 14 mark(startTag) 15 } 16 17 // a flag to avoid this being observed 18 vm._isVue = true 19 // merge options 20 if (options && options._isComponent) { 21 // optimize internal component instantiation 22 // since dynamic options merging is pretty slow, and none of the 23 // internal component options needs special treatment. 24 initInternalComponent(vm, options) 25 } else { 26 console.log(Vue.options) 27 vm.$options = mergeOptions( 28 resolveConstructorOptions(vm.constructor), 29 options || {}, 30 vm 31 ) 32 } 33 /* istanbul ignore else */ 34 if (process.env.NODE_ENV !== 'production') { 35 initProxy(vm) 36 } else { 37 vm._renderProxy = vm 38 } 39 // expose real self 40 vm._self = vm 41 initLifecycle(vm) 42 initEvents(vm) 43 initRender(vm) 44 callHook(vm, 'beforeCreate') 45 initInjections(vm) // resolve injections before data/props 46 initState(vm) 47 initProvide(vm) // resolve provide after data/props 48 callHook(vm, 'created') 49 50 /* istanbul ignore if */ 51 if (process.env.NODE_ENV !== 'production' && config.performance && mark) { 52 vm._name = formatComponentName(vm, false) 53 mark(endTag) 54 measure(`vue ${vm._name} init`, startTag, endTag) 55 } 56 57 if (vm.$options.el) { 58 vm.$mount(vm.$options.el) 59 } 60 } 61 }
第一行爲被調用方法,第二行在vue構造函數中掛載了_init方法,因此在構造函數或實例內均可以直接調用。
參數寫法爲flow,第一行參數意思爲傳入參數類型爲component,第二行爲傳入參數類型爲Object,加問號意思爲有無皆可,但類型必須爲Object,問號在後同理。繼續
函數內第一行的this指向被調用函數,即爲Vue實例化的對象,而後將函數掛載兩個屬性:_uid,_isVue,而後判斷options是否存在且其中是否有_isComponent,在尋找構造函數Vue時,core/index中將構造函數掛載了options,下圖爲core/index.js,
import Vue from './instance/index' import { initGlobalAPI } from './global-api/index' import { isServerRendering } from 'core/util/env' import { FunctionalRenderContext } from 'core/vdom/create-functional-component' initGlobalAPI(Vue) console.log(Vue.prototype) Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering }) Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext } }) // expose FunctionalRenderContext for ssr runtime helper installation Object.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext }) console.log(Vue.options) Vue.version = '__VERSION__' export default Vue
在initGlobalAPI(下圖)方法中,前十行將config值賦給configDef,並命名爲config到Vue上,且不容許修改configDef的值。而後又加了util,set,delete,nexttick。
export function initGlobalAPI (Vue: GlobalAPI) { // config const configDef = {} configDef.get = () => config if (process.env.NODE_ENV !== 'production') { configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } } Object.defineProperty(Vue, 'config', configDef) // exposed util methods. // NOTE: these are not considered part of the public API - avoid relying on // them unless you are aware of the risk. Vue.util = { warn, extend, mergeOptions, defineReactive } Vue.set = set Vue.delete = del Vue.nextTick = nextTick Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // this is used to identify the "base" constructor to extend all plain-object // components with in Weex's multi-instance scenarios. Vue.options._base = Vue extend(Vue.options.components, builtInComponents) initUse(Vue) initMixin(Vue) initExtend(Vue) initAssetRegisters(Vue) }
下圖爲被加的config值
export type Config = { // user optionMergeStrategies: { [key: string]: Function }; silent: boolean; productionTip: boolean; performance: boolean; devtools: boolean; errorHandler: ?(err: Error, vm: Component, info: string) => void; warnHandler: ?(msg: string, vm: Component, trace: string) => void; ignoredElements: Array<string | RegExp>; keyCodes: { [key: string]: number | Array<number> }; // platform isReservedTag: (x?: string) => boolean; isReservedAttr: (x?: string) => boolean; parsePlatformTagName: (x: string) => string; isUnknownElement: (x?: string) => boolean; getTagNamespace: (x?: string) => string | void; mustUseProp: (tag: string, type: ?string, name: string) => boolean; // legacy _lifecycleHooks: Array<string>; };
繼續initGlobalAPI方法,Vue加了options方法,並循環ASSET_TYPES的值末尾加s做爲鍵,值爲null,後加了options._base,再用extend方法將components對象中添加KeepAlive,extend方法目前理解爲將第二個值複製給第一個值,(keepalive是一個內容不少的重要文件,在src/core/components中,標記之後看),最下面四個方法分別加了use,mixin,extend(和上邊的不是一個),並在extend中使Vue.cid=0,最後一個方法被直接調用,以下圖二。
export const ASSET_TYPES = [ 'component', 'directive', 'filter' ]
import { ASSET_TYPES } from 'shared/constants' import { isPlainObject, validateComponentName } from '../util/index' export function initAssetRegisters (Vue: GlobalAPI) { /** * Create asset registration methods. */ ASSET_TYPES.forEach(type => { Vue[type] = function ( id: string, definition: Function | Object ): Function | Object | void { if (!definition) { return this.options[type + 's'][id] } else { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && type === 'component') { validateComponentName(id) } if (type === 'component' && isPlainObject(definition)) { definition.name = definition.name || id definition = this.options._base.extend(definition) } if (type === 'directive' && typeof definition === 'function') { definition = { bind: definition, update: definition } } this.options[type + 's'][id] = definition return definition } } }) }
該方法中,將Vue[component]加上了方法,因爲沒有definition值,options內也無id,因此返回的也是空(該過程僅僅是將Vue添加了三個空方法)。在initglobalapi方法經歷一番後,vue以下(僅爲在initglobalapi方法內所加)。
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(){}
let _isServer export const isServerRendering = () => { if (_isServer === undefined) { /* istanbul ignore if */ if (!inBrowser && !inWeex && typeof global !== 'undefined') { // detect presence of vue-server-renderer and avoid // Webpack shimming the process _isServer = global['process'].env.VUE_ENV === 'server' } else { _isServer = false } } return _isServer }
最後回到initglobalapi方法調用處,第二,三個皆爲在Vue.prototype調用引號內屬性時,使用get內方法,isServerRendering方法(上圖)爲當調用時使當前環境變爲server,第三個同理(暫時不知道$vnode是什麼),第四個在Vue加上了FunctionalRenderContext屬性名及調用該方法的值(無參數,)最後Vue加上了版本。
再往前回到initMixin方法中,options存在但沒有options._isComponent,走else,
export function resolveConstructorOptions (Ctor: Class<Component>) { let options = Ctor.options if (Ctor.super) { const superOptions = resolveConstructorOptions(Ctor.super) const cachedSuperOptions = Ctor.superOptions if (superOptions !== cachedSuperOptions) { // super option changed, // need to resolve new options. Ctor.superOptions = superOptions // check if there are any late-modified/attached options (#4976) const modifiedOptions = resolveModifiedOptions(Ctor) // update base extend options if (modifiedOptions) { extend(Ctor.extendOptions, modifiedOptions) } options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) if (options.name) { options.components[options.name] = Ctor } } } return options }
resolveConstructorOptions方法中,參數爲vm.constructor,即爲Vue構造函數,暫無super值,直接返回options,調用mergeOptions方法,傳入三個參數,分別爲Vue.options,實例化時所傳參數,和Vue實例化對象。在mergeOptions方法中,
export function mergeOptions ( parent: Object, //Vue.options child: Object, //實例化傳入參數 vm?: Component //Vue實例化對象 ): Object { if (process.env.NODE_ENV !== 'production') { checkComponents(child) } if (typeof child === 'function') { child = child.options } normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child) const extendsFrom = child.extends if (extendsFrom) { parent = mergeOptions(parent, extendsFrom, vm) } if (child.mixins) { for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } const options = {} let key for (key in parent) { //循環Vue.options的鍵 mergeField(key) } for (key in child) { //循環實例化參數的鍵 if (!hasOwn(parent, key)) { //若是當前的鍵與Vue.options全部的鍵不重複 mergeField(key) } } function mergeField (key) { const strat = strats[key] || defaultStrat; //strats[key]尋找方法,strat定義方法 //defaultStrat爲哪一個有值返回哪一個 options[key] = strat(parent[key], child[key], vm, key); //左邊在options定義相同的鍵名,右邊調用上邊的方法 //至關於調starts.key方法並傳參 } //console.log(parent) //console.log(child) //console.log(options) return options }
function checkComponents (options: Object) { for (const key in options.components) { validateComponentName(key) } } export function validateComponentName (name: string) { if (!/^[a-zA-Z][\w-]*$/.test(name)) { warn( 'Invalid component name: "' + name + '". Component names ' + 'can only contain alphanumeric characters and the hyphen, ' + 'and must start with a letter.' ) } if (isBuiltInTag(name) || config.isReservedTag(name)) { warn( 'Do not use built-in or reserved HTML elements as component ' + 'id: ' + name ) } }
checkComponents方法爲檢查實例化所傳參數components的值是否合法(兩個方法分別爲檢查是否與內置或保留元素重名)。而後檢查child是否爲function,傳入參數的prop.inject,directives,以及兩個判斷。
而後定義options,循環options內的名及傳入參數的名,進入方法。
function mergeAssets ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): Object { const res = Object.create(parentVal || null) if (childVal) { process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm) console.log(extend(res, childVal)) return extend(res, childVal) } else { return res } } ASSET_TYPES.forEach(function (type) { strats[type + 's'] = mergeAssets })
strats[屬性名]對應相應方法,ASSET_TYPES中有三個,可爲這三個添加方法,其他在該js上有獨自方法,options[key]直接調用mergeAssets或defaultStrat或本頁js上定義好的方法,最後合成options,從新梳理:循環Vue.options的鍵(爲父,實例化參數爲子)並定義同名鍵至新options,循環調用方法,ASSET_TYPES中有三個調用mergeAssets,該方法爲若是同鍵名的子屬性有值,則返回子屬性的值,沒有則返回空,添加至新的options,當沒有strats.key方法時,則調用defaultStrat方法,該方法爲若是子屬性有值則返回子屬性的值,沒有則返回副屬性的值,並添加至新options,調用strats.data時,有單獨的方法,該方法返回了一個方法,最後返回options。總結:mergeOptions方法新建options對象,將ASSET_TYPES中三個參數設爲新options的鍵並將值設爲空,若是實例化傳入的參數有相同的鍵則傳該值,而後將Vue.options其他值添加鍵名至新options並單獨處理其值,實例化傳參的對象也一樣,最後合併成一個擁有全部的鍵但值被處理過的對象。
繼續回溯,咱們在尋找Vue構造函數時在src/platforms/web/runtime/index.js停留過一會,如今看它,
Vue.config.mustUseProp = mustUseProp Vue.config.isReservedTag = isReservedTag Vue.config.isReservedAttr = isReservedAttr Vue.config.getTagNamespace = getTagNamespace Vue.config.isUnknownElement = isUnknownElement // install platform runtime directives & components extend(Vue.options.directives, platformDirectives) extend(Vue.options.components, platformComponents) // install platform patch function Vue.prototype.__patch__ = inBrowser ? patch : noop // public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
前五行爲咱們以前Vue內的config內的鍵賦新值,而後將Vue.options內的兩個對象加新值,掛載patch,和$mount方法。query方法爲獲取該元素(獲取不到則報錯)。再回溯看
entry-runtime-with-compiler.js,
const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* 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) /* 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 } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } return mount.call(this, el, hydrating) }
一樣掛載了$mount方法,但咱們先執行的是這個而後纔是上一個,而後在最下方加上了Vue.compile。compileToFunctions 函數的做用,就是將模板 template 編譯爲render函數。
下面回到_init()方法內...本週就到這裏
{
下面爲小貼士~
在script/config.js內genConfig方法中,加上框內的可在瀏覽器調試(出現src目錄),接下來就能夠盡情的debugger了
}