Vue源码主线
Vue构造函数位于src/core/instance/index.js
文件中instance
是存放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' // 错误警告提示相关代码
// 定义Vue构造函数
function Vue (options) {
// 安全模式处理告诉开发者必须使用 new 操作符调用 Vue。
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// 将参数options 传入进_init()
this._init(options)
}
// 引入依赖,定义Vue构造函,然后以Vue构造函数为参数调用这5个方法
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
// 最后导出Vue
export default Vue
以上对应的5个方法其实就是Vue原型prototype上挂载的方法或属性
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构造函,然后以Vue构造函数为参数调用这5个方法
/**
* 初始化相关
* Vue.prototype._init = function (options?: Object) {}
* */
initMixin(Vue)
/*
* 状态相关
* // Vue 实例观察的数据对象。Vue 实例代理了对其 data 对象属性的访问。
* Vue.prototype.$data
*
* // 设置对象的属性。如果对象是响应式的,确保属性被创建后也是响应式的,同时触发视图更新。这个方法主要用于避开 Vue 不能检测属性被添加的限制。
* Vue.prototype.$set = set
*
* // 删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到属性被删除的限制,但是你应该很少会使用它。
* Vue.prototype.$delete = del
*
* // 回调函数得到的参数为新值和旧值。表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代。
* Vue.prototype.$watch = function (){}
* */
stateMixin(Vue)
/**
* 事件相关
* // 监听当前实例上的自定义事件。事件可以由vm.$emit触发。回调函数会接收所有传入事件触发函数的额外参数。
* Vue.prototype.$on = function (event: string, fn: Function): Component {}
*
* // 监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器
* Vue.prototype.$once = function (event: string, fn: Function): Component {}
*
* // 移除自定义事件监听器
* Vue.prototype.$off = function (event?: string, fn?: Function): Component {}
*
* // 触发当前实例上的事件。附加参数都会传给监听器回调
* Vue.prototype.$emit = function (event: string): Component {}
* */
eventsMixin(Vue)
/*
* 生命周期相关
* // 挂载 beforeMount和mounted生命钩子
* Vue.prototype._mount = function (el?: Element | void,hydrating?: boolean): Component {}
*
* // 挂载 beforeUpdate和updated生命钩子
* Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {}
*
* // 更新props、propsData、$listeners、$forceUpdate、$slots、$parent、$children等详细请参见该方法
* Vue.prototype._updateFromParent = function (propsData: ?Object,listeners: ?Object,parentVnode: VNode,renderChildren: ?Array<VNode>) {}
*
* // 迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
* Vue.prototype.$forceUpdate = function () {}
*
* // 完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。
* // 触发 beforeDestroy 和 destroyed 的钩子
* Vue.prototype.$destroy = function () {}
* */
lifecycleMixin(Vue)
/*
* 渲染相关
*
* // 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
* Vue.prototype.$nextTick = function (fn: Function) {}
*
* // 字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。该渲染函数接收一个 createElement 方法作为第一个参数用来创建 VNode。
* Vue.prototype._render
*
* // toString方法
* Vue.prototype._s = _toString
*
* // 将文本转换为vnode
* Vue.prototype._v = createTextVNode
*
* // number转换
* Vue.prototype._n = toNumber
*
* // 创建一个空节点
* Vue.prototype._e = createEmptyVNode
*
* // 检查两个值是否宽松相等 —— 也就是说,如果它们是纯对象,它们是否具有相同的形状?
* Vue.prototype._q = looseEqual
*
* // 寻找该数组对应值的索引值,如果找到了,返回索引值;否则返回 -1
* Vue.prototype._i = looseIndexOf
*
* // 渲染静态节点
* Vue.prototype._m = function renderStatic (index: number,isInFor?: boolean): VNode | Array<VNode> {}
*
* // 将节点标记为静态(v-once)只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
* Vue.prototype._o = function markOnce () {}
*
* // 过滤器 filters
* Vue.prototype._f = function resolveFilter (id) {}
*
* // 渲染v-for
* Vue.prototype._l = function renderList (){}
*
* // 渲染Slot
* Vue.prototype._t = function (){}
*
* // 调用 v-bind 方法
* Vue.prototype._b = function bindProps (data: any,tag: string,value: any,asProp?: boolean): VNodeData {}
*
* // 检查 v-on 建码
* Vue.prototype._k = function checkKeyCodes (
* */
renderMixin(Vue)
// 最后导出Vue
export default Vue
根据我们之前寻找 Vue 的路线,这只是刚刚开始,我们追溯路线往回走,那么下一个处理 Vue 构造函数的应该是src/core/index.js
// 导入已经在原型上加载了方法和属性后的Vue
import Vue from './instance/index'
// 导入全局API
import { initGlobalAPI } from './global-api/index'
// 导入服务渲染
import { isServerRendering } from 'core/util/env'
/*
* 该方法作用是在Vue构造函数上面挂载静态属性和方法
* // 导出Vue的全部配置
* Vue.config
*
* // 一些工具方法
* Vue.util
*
* // 在一个对象上设置一个属性。 添加新的属性和
* Vue.set = set
*
* // 删除一个属性,并在必要时触发更改。
* Vue.delete = del
*
* // 推迟任务以异步执行它。
* Vue.nextTick = util.nextTick
*
* // 组件可以拥有的资源列表。包含 Vue 实例
* Vue.options = {
components: {
KeepAlive
},
directives: {},
filters: {},
_base: Vue
}
*
* // 安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入
* Vue.use
*
* // 全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。
* Vue.mixin
*
* Vue.cid = 0
*
* // 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
* Vue.extend
*
* // 注册或获取全局组件。注册还会自动使用给定的id设置组件的名称
* Vue.component = function(){}
*
* // 注册或获取全局指令。
* Vue.directive = function(){}
*
* // 注册或获取全局过滤器。
* Vue.filter = function(){}
*
* // 当前 Vue 实例是否运行于服务器。
* Vue.prototype.$isServer
*
* // 版本号
* Vue.version = '__VERSION__'
* */
initGlobalAPI(Vue)
// 在Vue.prototype上挂载 $isServer
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
// 挂载版本属性
Vue.version = '__VERSION__'
export default Vue
下一个就是web-runtime.js
文件了,web-runtime.js
文件主要做了三件事儿:
1、覆盖 Vue.config 的属性,将其设置为平台特有的一些方法
2、Vue.options.directives 和 Vue.options.components 安装平台特有的指令和组件
3、在 Vue.prototype 上定义 patch 和 $mount
代码如下
一些引用
import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
import { devtools, inBrowser, isEdge } from 'core/util/index'
import { patch } from 'web/runtime/patch'
import platformDirectives from 'web/runtime/directives/index'
import platformComponents from 'web/runtime/components/index'
import {
query,
isUnknownElement,
isReservedTag,
getTagNamespace,
mustUseProp
} from 'web/util/index'
/*
* install platform specific utils
*
* 覆盖Vue.config的值
*
* isUnknownElement 获取元素
* isReservedTag 获取标签
* getTagNamespace 获取标签名称
* mustUseProp 获取标签定义
* */
Vue.config.isUnknownElement = isUnknownElement
Vue.config.isReservedTag = isReservedTag
Vue.config.getTagNamespace = getTagNamespace
Vue.config.mustUseProp = mustUseProp
/*
* 安装平台运行时指令和组件
* install platform runtime directives & components
*
* 包含 Vue 实例可用指令的哈希表。
* Vue.options.directives(componentUpdated、inserted、bind、unbind、update)
*
* 包含 Vue 实例可用组件
* Vue.options.components(KeepAlive、Transition、TransitionGroup)
*
* Vue.options变化为
*
* Vue.options = {
components: {
KeepAlive,
Transition,
TransitionGroup
},
directives: {
model,
show
},
filters: {},
_base: Vue
}
* */
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
/*
* 安装补丁功能
* install platform patch function
* */
Vue.prototype.__patch__ = inBrowser ? patch : noop
/*
* 手动地挂载一个未挂载的实例。
* wrap mount
* */
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// 根据浏览器环境决定用不用 query(el)获取元素
el = el && inBrowser ? query(el) : undefined
// 然后将 el 作为参数传递给 this._mount()
return this._mount(el, hydrating)
}
/*
* 配置是否允许 vue-devtools 检查代码。开发版本默认为 true,生产版本默认为 false。生产版本设为 true 可以启用检查。
* devtools global hook
* */
/* istanbul ignore next */
setTimeout(() => {
if (config.devtools) {
if (devtools) {
devtools.emit('init', Vue)
} else if (
process.env.NODE_ENV !== 'production' &&
inBrowser && !isEdge && /Chrome\/\d+/.test(window.navigator.userAgent)
) {
console.log(
'Download the Vue Devtools for a better development experience:\n' +
'https://github.com/vuejs/vue-devtools'
)
}
}
}, 0)
export default Vue
最后一个处理 Vue 的文件就是入口文件web-runtime-with-compiler.js
了,该文件做了两件事
1、缓存来自web-runtime.js
文件的 $mount 函数
const mount = Vue.prototype.$mount
然后覆盖覆盖了 Vue.prototype.$mount
2、在 Vue 上挂载 compile,compileToFunctions 函数的作用,就是将模板 template 编译为render函数。
Vue.compile = compileToFunctions
具体代码如下
// 一些引入
import Vue from './web-runtime'
import { warn, cached } from 'core/util/index'
import { query } from 'web/util/index'
import { shouldDecodeNewlines } from 'web/util/compat'
import { compileToFunctions } from 'web/compiler/index'
// 缓存Template模板
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
// 缓存来自 web-runtime.js 文件的 $mount 函数 =》获取到对应元素的内容
const mount = Vue.prototype.$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
// 解析 模板/el 并转换为渲染函数
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
// 如果有传入 template模板
if (template) {
// 判断是否 template 是否是字符串
if (typeof template === 'string') {
// 如果模板template第一个字符是 # 就返回错误
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
)
}
}
}
// 如果不是字符串就获取template里面的内容 :如<template></template>
else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
}
// 如果没有传入template模板,就把 el节点 转换成模板字符串
else if (el) {
template = getOuterHTML(el)
}
// Vue 上挂载 compile
// compileToFunctions 函数的作用,就是将模板 template 编译为render函数。
if (template) {
const { render, staticRenderFns } = compileToFunctions(template, {
warn,
shouldDecodeNewlines,
delimiters: options.delimiters
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
}
}
return mount.call(this, el, hydrating)
}
/**
* 获取元素的外部HTML,注意
* IE中的SVG元素
* 输出外部HTML
* Get outerHTML of elements, taking care
* of SVG elements in IE as well.
*/
function getOuterHTML (el: Element): string {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
Vue.compile = compileToFunctions
export default Vue
至此,我们还原了 Vue 构造函数,总结一下:
1、Vue.prototype 下的属性和方法的挂载主要是在 src/core/instance 目录中的代码处理的
2、Vue 下的静态属性和方法的挂载主要是在 src/core/global-api 目录下的代码处理的
3、web-runtime.js 主要是添加web平台特有的配置、组件和指令
4、web-runtime-with-compiler.js 给Vue的 $mount 方法添加 compiler 编译器,支持 template