介紹
使用vue時候,要先new操作符調用,說明Vue是一個構造函數,所以我們首先要把vue的構造函數搞清楚
Vue構造函數的原型
npm run dev
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
調用scripts/config.js
// Runtime+compiler development build (Browser)
'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
}
由此可見入口文件是web/entry-runtime-with-compiler.js,輸出爲dist/vue.js,是一個umd模塊,但是在入口文件中web是哪個文件?
這其實是一個別名配置,script/alias.js文件。
const path = require('path')
const resolve = p => path.resolve(__dirname, '../', p)
module.exports = {
vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
compiler: resolve('src/compiler'),
core: resolve('src/core'),
shared: resolve('src/shared'),
web: resolve('src/platforms/web'),
weex: resolve('src/platforms/weex'),
server: resolve('src/server'),
entries: resolve('src/entries'),
sfc: resolve('src/sfc')
}
由此可見web爲 resolve(‘src/platforms/web’);
在src/platforms/web/entry-runtime-with-compiler.js文件中,看到這樣一句
import Vue from './runtime/index'
然後打開runtime文件看到
import Vue from 'core/index'
看到core來自於scripts/alias.js配置,core指向src/core,打開之後發現
import Vue from './instance/index'
打開./instance/index.js文件
// 從五個文件導入五個方法(不包括 warn)
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'
// 定義 Vue 構造函數
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)
}
// 將 Vue 作爲參數傳遞給導入的五個方法
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
// 導出 Vue
export default Vue
由此才發現這是vue的真正來源
首先重init等五個文件導入方法,然後再定義Vue的構造函數,其中還提醒你使用new來操作,最後導出Vue。
打開./init.js,找到initMixin方法,
export function initMixin(Vue: class<Component>){
Vue.prototype._init = function(option?:Object){
//..._init 方法內容
}
}
發現在vue原型上添加了_init方法,在Vue的構造函數中也使用了這個方法,打開./state.js文件,stateMixin(數據)方法
export function stateMixin (Vue: Class<Component>) {
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
}
return function unwatchFn () {
watcher.teardown()
}
}
}
在這基礎上分析,使用Obejct.defineProperty在Vue.prototype上定義兩個屬性,
這就是props,兩個屬性分別寫在dataDef和propsDef,仔細分析:
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
可以看出$data實際上是代理_data這個實例屬性,在不是生產環境中,設置set,使其只讀。
接着又在stateMixin在Vue原型上定義了三個方法:
delete,$watch
在分析eventsMixin(事件),eventsMixin在Vue原型上添加是四個個方法:
Vue.prototype.$on = function (event:string | Array<string>,fn:Function):Component{};
Vue.prototype.$once = function(event:string,fn:Function):Component{}
Vue.prototype.$off= function(event?:string | Array<string>,fn?:Function):Component{};
Vue.prototype.$emit = function(event:string):Compoent{};
下一個分析lifecycle(生命週期),lifecycle在Vue原型上添加三個方法:
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {}
Vue.prototype.$forceUpdate = function () {}
Vue.prototype.$destroy = function () {}
最後一個renderMixin,在render.js中一開始就以Vue.prototype爲參數調用了installRenderHelpers函數
export function installRenderHelpers (target: any) {
target._o = markOnce
target._n = toNumber
target._s = toString
target._l = renderList
target._t = renderSlot
target._q = looseEqual
target._i = looseIndexOf
target._m = renderStatic
target._f = resolveFilter
target._k = checkKeyCodes
target._b = bindObjectProps
target._v = createTextVNode
target._e = createEmptyVNode
target._u = resolveScopedSlots
target._g = bindObjectListeners
}
這個函數的作用就是爲Vue.prototype添加一些方法。之後又爲Vue.prototype添加兩個方法 $nextTick和_render,最後經過renderMixin之後,Vue.prototype添加如下方法
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.prototype.$nextTick = function (fn: Function) {}
Vue.prototype._render = function (): VNode {}
至此instance/index.js代碼運行完成,瞭解了*Mixin作用就是包裝Vue.prototype,