在一般情况下,我们在构建vue项目时,都是一上来就直接new Vue(...),但是实际上,在你引入vue之后,一些有趣的事情就已经发生了......让我们一起来了解一下。
在阅读本篇文章前,你需要先了解的一些js基础知识有:js原型、修改对象属性默认特性的方法defineProperty(defineProperties)、bind、es6相关知识等......
我这里拿的是node_modules下的vue包,先看一下项目的目录结构:
src
|──compiler(编译vue模板)
|──core(vue核心逻辑)
|──platforms(vue跨平台的逻辑)
|──server(vue服务端渲染)
|──sfc(vue单文件组件 Single-File Component )
|——shared(vue的一些公共方法和常量)
这里先知道有这么几个文件夹就行了,先混个眼熟。然后我们开始吧......
我们常用的是vue入口文件的位置是:src/platforms/web/entry-runtime.js
里面啥逻辑都没写......除了导出一个从./runtime/index中引入的Vue。可以忽略。
那我们就看下./runtime/index.js中写了啥。
第一行:
import Vue from 'core/index'
好家伙。又引入了一个从core/index中导出的Vue。有点绕啊,不过问题不大。直接进去看看写了啥。
第一行:
import Vue from './instance/index'
又来!行吧。再看一下。进入'./instance/index'文件。
终于找到Vue构造定义的位置了。先整个鸡腿奖励下自己。
开始正题!
先贴一下src/core/instance/index.js下的入口源码
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构造函数的方式(window环境下可以直接set NODE_ENV=development将环境变量改成developoment或者production),然后就是传入用户输入的options,执行原型上的_init方法.这里的options一般就是我们用脚手架生成的main.js中传入的参数,像下面的这个数据结构一样:
{
router,
store,
render:h=>(App)
}
当然这里只是声明Vue的构造函数,在我们没有使用new关键字调用构造函数前,this._init(options)方法 自然也不会执行。
继续往下面看......
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
这里依次执行了5个核心的函数(很重要),接下来我们依次介绍一下每个函数内部都干了些什么。
initMixin(Vue)
还是先看一下源码
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
//...初始化逻辑
}
}
initMixin方法
这里的initMixin方法本质是只是给Vue的原型对象定义了一个_init方法(就是new Vue()的时候需要执行的那个方法),将初始化的内容单独拆分出来独立成一个文件,也可以说是模块化的一种实现。这里删除了代码的具体实现部分,因为本章只谈引入Vue后发生了什么。后面的章节会一一介绍......
剩下的几个mixin方法也是类似的套路......
stateMixin(Vue)
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 (newData: Object) {
}
propsDef.set = function () {
}
}
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 watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
}
return function unwatchFn () {
watcher.teardown()
}
}
}
可以看到stateMinx方法主要是给Vue的原型对象定义
$data属性---------值为this._data
$props属性--------值this._props
$set属性----------值为set
$delete属性----------值为del
$watch属性--------值为一个函数
需要注意的是$watcher执行后会new Wactcher实例,并且返回一个unwatchFn函数,解除监听。
eventsMixin
export function eventsMixin (Vue: Class<Component>) {
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 {
//触发事件逻辑
}
}
给Vue原型定义$on、$once、$off、$emit四个事件相关的函数
lifecycleMixin
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
//讲虚拟dom更新成实际dom
}
Vue.prototype.$forceUpdate = function () {
//强制页面刷新
}
Vue.prototype.$destroy = function () {
//摧毁组件
}
}
给Vue原型对象定义_update、$forceUpdate、$destory函数
renderMixin
export function renderMixin (Vue: Class<Component>) {
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
//将代码添加到异步队列
}
Vue.prototype._render = function (): VNode {
//生成虚拟dom
}
}
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
target._d = bindDynamicKeys
target._p = prependModifier
}
给Vue原型对象定义$nextTick、_render函数以及一些运行时的辅助方法
当src/core/instance/index.js中的代码执行完毕后,我们又回到了core/index中,现在来看看这里面干了啥......
贴一下src/core/index.js的源码
initGlobalAPI(Vue)
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
})
initGlobalAPI
看下源码
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
//添加options属性
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
//标识一些基础构造器
Vue.options._base = Vue
//注册一个全局的抽象组件keep-alive
extend(Vue.options.components, builtInComponents)
initUse(Vue) //添加Vue.use方法
initMixin(Vue)//添加Vue.mixin方法
initExtend(Vue)//添加Vue.extend方法
initAssetRegisters(Vue)//添加注册组件 指令 过滤器的方法
}
相对于前一个core/instance/index的代码逻辑,这里主要是给Vue构造函数上添加各种属性和方法;
好了经过这一番操作下来。Vue会变成什么样子?如果你对前面的内容记得不清楚,可以直接看下面的截图:
再看一眼Vue原型上有哪些新鲜的玩意。
最后我们回到最初的'./runtime/index.js'
贴一下'./runtime/index.js'的源码
// install platform specific utils
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.options上定义了两个默认指令v-show和v-model,注册两个默认的组件Transition、TransitionGroup。然后给原型对象添加一个__patch__和$mount方法。 看下代码结果:
总结
可以很清楚的看到在你new Vue实例前,Vue已经在自己的构造函数和原型对象上添加了很多的方法和属性。至于这些方法和属性有什么用。我们后面再一一说。