1、vue源码中的函数、变量、关键字的解读
Vue:Vue构造函数,用于创建Vue实例。Vue实例:通过Vue构造函数创建的实例,它具有一些内置的属性和方法。模板(template):Vue中用于渲染视图的声明性HTML模板。渲染函数(render function):在Vue中,将模板转化为虚拟DOM的JavaScript函数。虚拟DOM(Virtual DOM):Vue内部使用的一种表示DOM节点树的JavaScript对象。生命周期钩子函数(lifecycle hooks):Vue实例在创建、更新、销毁等不同阶段触发的一系列回调函数,可以在这些回调函数中执行一些特定的逻辑。指令(directive):在模板中使用的带有v-前缀的特殊属性,用于在DOM元素上添加特殊的行为。计算属性(computed property):Vue实例中用于派生其他属性的属性,可以缓存计算结果以提高性能。监听器(watcher):用于监听Vue实例中数据变化的对象,当数据变化时执行相应的回调函数。组件(component):在Vue中封装了一些可重用的UI组件或功能模块,可以用来构建更复杂的用户界面。
2、方法或者属性定义,根据单词分析大概含义再带着目的阅读
lifecycle:生命周期相关的一些方法和钩子函数,包括生命周期的触发顺序、生命周期函数的具体作用等state:Vue 实例状态相关的一些方法,包括数据劫持相关的方法、watch 监听相关的方法等render:与渲染相关的一些方法和函数,包括 Vue 的渲染过程、虚拟 DOM 相关的方法、渲染函数的使用等events:与事件相关的一些方法和函数,包括事件绑定、事件触发、事件冒泡等directive:指令相关的一些方法和函数,包括自定义指令的注册、使用和实现等filter:过滤器相关的一些方法和函数,包括自定义过滤器的注册、使用和实现等mixin:混入相关的一些方法和函数,包括混入对象的合并、覆盖规则等component:组件相关的一些方法和函数,包括组件注册、组件通信等template:模板相关的一些方法和函数,包括模板解析、模板编译、模板渲染等compiler:编译相关的一些方法和函数,包括模板编译器的实现、AST 抽象语法树的生成等observer:观察者相关的一些方法和函数,包括数据的双向绑定实现原理、响应式数据的实现等
3、/platforms/web/entry-runtime-with-compiler.js和/platforms/web/runtime/index.js中的$mount是什么关系?
- 问题1: entry-runtime-with-compiler.js为什么要重新创建$mount?
- 问题2: 那entry-runtime-with-compiler.js和web/runtime/index.js中的$mount是什么关系?
/platforms/web/entry-runtime-with-compiler.js
/* @flow */
import config from 'core/config'
import { warn, cached } from 'core/util/index'
import { mark, measure } from 'core/util/perf'
import Vue from './runtime/index'
import { query } from './util/index'
import { shouldDecodeNewlines } from './util/compat'
import { compileToFunctions } from './compiler/index'
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// 判断 el 是否存在,并将其转换为 DOM 元素(两者需都满足)
el = el && query(el)
/**
* 这段代码的作用是防止将 Vue 实例挂载到 document.body 或 document.documentElement 上。
* 这是因为 Vue 在挂载时会将模板编译成真正的 DOM 元素,并替换挂载点的内容,如果将 Vue 实例挂载到 <body> 或 <html> 上,可能会破坏原有的文档结构。
* 因此,这里进行了判断,如果挂载点是 <body> 或 <html>,则会发出警告,并返回当前 Vue 实例。
*/
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`不要将 Vue 挂载到 <html> 或 <body> 元素上 - 而是挂载到普通元素。`
)
return this
}
/**
* 这行代码是获取 Vue 实例的配置对象,也就是 new Vue(options) 中传递的参数,
* 它在 Vue 实例化时就会被保存在 $options 中,后续可以通过 this.$options 访问这些参数。
* 在这个方法中,获取 $options 的目的是为了检测 Vue 实例是否包含 render 函数并赋值给 vm.$options.render,以便后续使用。
*/
const options = this.$options
/**
*
下面这段代码主要是用于将 template 或 el 转化为 render 函数(在Vue中,将模板转化为虚拟DOM的JavaScript函数。)。
首先会判断 options 中是否已经有了 render 函数,如果有,则直接使用该函数,否则会继续处理 template 和 el。
*/
if (!options.render) {
let template = options.template
if (template) {
// 如果有 template,则会进行处理,将其转换为 render 函数,转换的过程包括了将 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('无效的模版选项:' + template, this)
}
return this
}
} else if (el) {
// 如果没有 template,但是有 el,则会根据 el 获取到对应的 DOM 元素,然后再对该元素的 innerHTML 进行编译,将其转换为渲染函数并进行缓存。
template = getOuterHTML(el)
}
// 最后,如果成功获取到了渲染函数
if (template) {
// 则将其保存到 options 中的 render 属性中。
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
/**
* 这段代码的作用是将 template 编译为 render 函数,
* 然后将编译得到的 render 函数添加到 Vue 实例的 options 对象中,以便后续使用。
* 具体来说,这段代码首先调用 compileToFunctions 方法
* 将 template 编译为 render 函数和 staticRenderFns 数组,
* 其中 render 函数表示模板的渲染函数,
* 而 staticRenderFns 数组表示静态节点的渲染函数。
* 接着,将 render 函数和 staticRenderFns 数组添加到 Vue 实例的 options 对象中,
* 以便在渲染组件时使用。
* 这样,当 Vue 实例渲染模板时,就可以直接使用编译得到的 render 函数进行渲染了。
*/
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
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')
}
}
}
// 最后,调用 mount 方法,将 Vue 实例挂载到指定的 DOM 元素上。
return mount.call(this, el, hydrating)
}
/**
* 该函数用于获取一个元素的outerHTML,即该元素的HTML标签及其内容。
* 如果元素的outerHTML属性存在,则直接返回该属性值,
* 如果元素的outerHTML属性不存在,创建一个div元素作为容器,将该元素的克隆节点附加到容器中,最后返回容器的innerHTML。
*/
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
/platforms/web/runtime/index.js
/* @flow */
import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser, isChrome } from 'core/util/index'
import {
query,
mustUseProp,
isReservedTag,
isReservedAttr,
getTagNamespace,
isUnknownElement
} from 'web/util/index'
import { patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'
// 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.js 中的 $mount 方法。
* 它是 Vue.js 应用程序的入口点之一,用于将 Vue 实例挂载到一个 DOM 元素上,从而启动应用程序的渲染过程。
* 具体来说,这个方法会将 el 参数转化为一个 DOM 元素,并将其作为挂载点传递给 mountComponent 方法。
* mountComponent 方法是实际执行 Vue 实例挂载的方法。
* 它会执行一系列的初始化操作:
* 包括创建虚拟 DOM、编译模板、生成渲染函数、执行渲染函数等。
* 最终,它会将渲染得到的 DOM 元素插入到挂载点上,完成 Vue 实例的挂载。
*/
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
// devtools global hook
/* istanbul ignore next */
Vue.nextTick(() => {
if (config.devtools) {
if (devtools) {
devtools.emit('init', Vue)
} else if (process.env.NODE_ENV !== 'production' && isChrome) {
console[console.info ? 'info' : 'log'](
'Download the Vue Devtools extension for a better development experience:\n' +
'https://github.com/vuejs/vue-devtools'
)
}
}
if (process.env.NODE_ENV !== 'production' &&
config.productionTip !== false &&
inBrowser && typeof console !== 'undefined'
) {
console[console.info ? 'info' : 'log'](
`You are running Vue in development mode.\n` +
`Make sure to turn on production mode when deploying for production.\n` +
`See more tips at https://vuejs.org/guide/deployment.html`
)
}
}, 0)
export default Vue
问题1:(entry-runtime-with-compiler.js为什么要重新创建$mount?)
在 entry-runtime-with-compiler.js 中重新创建 Vue.prototype.$mount 函数,主要是为了在有编译器的情况下,能够编译模板并生成 render 函数,并将其挂载到选项中,最终调用 mountComponent 函数来挂载组件。
这是因为在有编译器的情况下,我们可以通过将模板编译成渲染函数的方式来优化应用程序的性能。在这种情况下,我们需要在挂载组件之前先编译模板并生成渲染函数,然后将其挂载到选项中。
重新创建 $mount 函数可以确保我们能够在组件实例上使用 $mount 方法来挂载组件,而不需要手动编译模板和生成渲染函数。同时也允许用户在运行时动态编译模板并将其挂载到组件实例上。
问题2:(那entry-runtime-with-compiler.js和web/runtime/index.js中的$mount是什么关系?)
entry-runtime-with-compiler.js 和 web/runtime/index.js 中的 $mount 具有相同的功能,但实现方式略有不同。
entry-runtime-with-compiler.js 中的 $mount 方法主要是用于编译器生成的带有模板(template)的 Vue 应用程序,它会首先将模板(template)编译成渲染函数(render function),然后再执行 web/runtime/index.js 中的 $mount 方法。因此,entry-runtime-with-compiler.js 中的 $mount 实际上是对 web/runtime/index.js 中 $mount 方法的封装。
web/runtime/index.js 中的 $mount 方法则是用于运行时版本的 Vue 应用程序,它接收一个 el 参数和一个可选的 hydrating 参数,然后将 Vue 实例挂载到 el 元素上。
简单来说,entry-runtime-with-compiler.js 中的 $mount 方法会在编译过程中将模板(template)编译成渲染函数(render function),而 web/runtime/index.js 中的 $mount 方法则是直接将 Vue 实例挂载到 DOM 上。
4、new Vue() 都发生了什么?
instance/index.js
- new Vue() 从
instance/index.js开始看,看见了Vue的庐山面目本质上是个构造函数? - instance/index.js 初始化一些混合函数,如
initMixin、stateMixin、lifecycleMixin等,向Vue原型上混入一些属性和方法。 - 调用
_init(option)方法(Vue构造函数中 instalce/init.js)。
instalce/init.js
- 向vm上定义uid
- 合并配置:将传入的option和本身的配置进行合并,传入的option类似 {el: '#app', data: {message: 'msg'}}。
- 初始化工作:initLifecycle(vm)、initEvents(vm)、initRender(vm)、执行beforCreate、
initState(vm)、initProvide(vm)、执行created。 - 进行挂载dom:vm.options.el),在这行代码执行完,页面上能看见渲染的真实dom。
接下来重点分析initState()具体做了哪些事情
instalce/state.js
- 初始化工作:initProps、initMethods、
initData、initComputed、initWatch 初始化这些我们常用的属性和方法。 - initData():首先data进行检测必须是function或者object。其次要确定method和props中没有和data中定义重复的key,如果有抛出警告,如果没有重复的key,检测当前key是不是保留字($或_开头的),不是的话,将key代理到vm实例上。最后使用observe(data, true /* asRootData */)将data设置为响应式对象,以便data属性更新时,对应更新视图。
总结:new Vue时调用了init方法,进行配置的合并,还有一些生命周期、事件、state等初始化工作。初始化完成后,会执行$mount,将组件挂载,渲染成真实dom。
为什么可以在mounted中使用this获取到data中的属性?
因为,在init方法中,调用initState(vm)时,初始化了initData。将option.data中的所有非保留字的key通过proxy代理到了vm实例上。而mounted生命周期是在initData()方法执行之后,$mount挂载dom之后执行的,this指向了vue实例vm,vm在initData()时代理了data中所有符合规范的key。所以说,mounted中可以使用this获取到data中的属性。