Vue Implementation 01
Constructor of Vue
We know, when we are using Vue
, we need to use the new
operator to invoke it. It demonstrates that Vue
should be a constructor. So what we need to do first is: to make clear of the constructor of Vue
.
The Prototype of Vue Constructor
In Getting into the project of Vue, we mentioned we will dive in it by npm run dev
.
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
When we run npm run dev
, according to the configuration file in 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
}
It shows that the entry file is web/entry-runtime-with-compiler.js
, and finally products dist/vue.js
. It is a umd
module.
Therefore, let's begin with entry file, and find Vue
's constructor and make it clearer.
But now there is a question: in web/entry-runtime-with-compiler.js
, which directory is web
indicated? It is a alias setting, we can open scripts/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')
}
There is a line:
web: resolve('src/platforms/web')
So web points to src/platforms/web
. Along with that, alias.js
includes more other aliases, so we can refer to this file to find the according directory without other description.
Then we can get into the topic. Open src/platforms/web/entry-runtime-with-compiler.js
, we can see the following line:
import Vue from './runtime/index'
It shows that it is not the source of the constructor of Vue
, and the Vue
of this file is introduced in ./runtime/index
. So we open the runtime
folder in current directory, and we can see in index.js
:
import Vue from 'core/index'
Same, it indicated that runtime/index.js
is not the source of the constructor of Vue
neither. You should open core/index.js
as well. In the configuration of scripts/alias.js
, core
pointed to src/core
. We can see in src/core/index.js
:
import Vue from './instance/index'
By the same method, open ./instance/index.js
:
// import five functions from five different files (excluding 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'
// define the constructor of 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)
}
// pass Vue as parameter to five functions
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
// export Vue
export default Vue
We can see, this is the souce of the constructor of Vue. The code above is all in ./instance/index.js
and it is relatively short.
- First, it imports five functions from
init.js
,state.js
,render.js
,events.js
andlifecycle.js
, which isinitMixin
,stateMixin
,renderMixin
,eventsMixin
andlifecycleMixin
. - Second, it defines the constructor of
Vue
, and it checks that if you are inproduction
mode, you can only use thenew
operator to invokeVue
. - Finally, it passes
Vue
as parameter info the imported functions and exportVue
.
So what do these functions do? Let's deep into initMixin
first. Open ./init.js
and find initMixin
, we can see:
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
// ... body of _init, omitted.
}
}
This function aimed to add _init
function to the prototype of Vue
. The _init
function seems like a internal initialization function. Actually, we met this function once at instance/index.js
:
// define Vue constructor
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')
}
// over here
this._init(options)
}
In the Vue's constructor, there is a line: this._init(options)
. This shows that when we invoke new Vue()
, this._init(option)
will be invoked.
Then we open ./state.js
and look into stateMixin
. At the beginning of this function, there is a part of code:
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 (newData: Object) {
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)
Let's dive into last two lines first. It uses Object.defineProperty
to define two properties in Vue.prototype
, which is our familiar $data
and $props
. The definition of these properties is written in dataDef
and propsDef
. Let's go further and look at the definition of these two objects. First is get
:
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
We can see, what $data
proxing is the instance property of _data
, and what $props
proxing is the instance property of _props
. And then there is a check of it is in production environment. If it's not in production, it will define set
for $data
and $props
. Actually, it just used to warn you that you should not mutate it directly.
That is, $data
and $props
are both readonly properties. So, if you need to implement a readonly property in Javascript, you know how to do now.
Then stateMixin
define three functions in Vue.prototype
:
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
// ...
}
$set
, $delete
and $watch
is what you already have known about at Vue
's instance properties page.
Next is eventsMixin
. This function is defined in ./events.js
. We can open this file and find eventsMixin
. This function add four functions to Vue.prototype
, which is:
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): Component {}
Lastly, the renderMixin
function. It is included in render.js
. This function invoked installRenderHelpers
function with Vue.prototype
as parameter. This function is introduced in render.js
which is located in render-helpers/index.js
. Open this file and find installRenderHelpers
function:
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
}
All above is the code of installRenderHelpers
. We can see, what this function do is to add a set of functions to Vue.prototype
. We don't need to make clear of what do these functions do and we will talk about these.
After execution of installRenderHelpers
, renderMixin
added two more functions to Vue.prototype
, which is $nextTick
and _render
. Finally, Vue.prototype
is attached by these functions after renderMixin
:
// in installRenderHelpers
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 {}
Now, instance/index.js
finished its run in npm run dev
. We know that every *Mixin
is used to decorate Vue.prototype
and mount some properties and functions to it. Next, we need to do a very important thingis to combine all of content above into a seperate place for searching. We made a appendix here: // TODO. When we talk about it later, we can find that function fast and keep it clear.