Vue.js静态成员和实例成员的初始化过程(vue.set、vue.get、vue.extend等)
首次渲染的过程
数据响应式的原理
准备
源码地址: https://github.com/vuejs/vue
结构
dist:存放打包后文件
examples:存放示例文件,例如表格的使用等
src:compile/模板编译
core:vue核心
components:存放组件,例如keep-live
global-api:存放use、mixin、extends等
instance:存放vue实例,vue生命周期,初始化等
observer:实现响应式机制
util:存放公共成员位置
vdom:vue虚拟DOM,vue增强了,可以存放组件相关
platforms:存放平台相关内容,例如web,weex
sfc:单文件组件,把组件转换成js对象
了解Flow
vue源码使用了flow声明类型,并且每个文件开头都有flow标记
官网:https://flow.org/
JS的静态类型检查器
Flow的静态类型检查错误是通过静态类型腿短实现的
· 文件开头通过 // @flow 或者 /@flow/ 声明
调试
打包工具Rollup
Vue.js源码打包使用的是Rollup,比webpack轻量
webpack将所有文件当成模块,rollup只处理js文件,更适合vue这种类库的使用
Rollup打包不会生成冗余代码
安装
npm install
设置sourcemap
package.json中的dev添加参数 --sourcemap
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",
执行dev
npm run dev 执行打包,使用rollup,-w 参数是监听文件的变化,文件变化自动重新打包
使用 npm run build 重新打包所有文件,可以在dist下查看
完整版:同时包含编译器和运行时的版本
编译器:用来将模板字符串编译成JS渲染函数的代码体积大,效率低
运行时:创建Vue实例,渲染Vnode等代码,体积小,效率高,基本就是编译的代码
UMD: 通用的模块版本,支持多种模块方式。Vue默认文件是运行时 + 编译器的UMD版本
CommonJS:用来配合老的打包工具Browserify 或 webpack1.0
ES Module:2.6之后提供两个ES Module构建文件,提供现代打包提供版本
ESM格式设计为可以被静态分析,所以攻击可以利用这点来进行“tree-shaking”,排除无用代码
Vue脚手架对webpack进行深度封装,可以通过命令行工具查看vue的配置
vue inspect
vue inspect > output.js 将vue配置输出到output.js中
查看resolve可以看到vue运行时使用的是vue.runtaime.esm.js
是运行时版本,且使用esm的方式
开发项目时会有很多单文件组件,浏览器是不支持这种方式,
vue会将单文件转换为JS对象,转换过程中会将模板转换成render函数,所以运行时不需要编译器
查看dist/vue.js的构建过程
先执行构建 npm run dev
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",
script/config.js的执行过程
作用:生成rollup构建的配置文件
使用环境变量TARGET = web-full-dev
找到
'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 // 生成每一个文件头的注释内容
},
src/platform/web/entry-runtime-with-compiler.js
// 如果同时设置template 和 render此时会渲染什么?
// 如果有render。不会执行template,如果有render,直接调用组件的mount渲染render函数
const vm = new Vue({
el: "#app",
template:"hello tempalte
",
render(h){
return h("h1","hello Render")
}
})
vue 执行过程 vue-》init -》mount
Vue.prototype.$mount 执行到mount
el不能是body 或者 html
如果没有render,将template转换成render函数
如果有render函数,直接调用mount挂载DOM
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
// 保留 Vue 实例的$mount 方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
// 非ssr情况下为false,ssr时候为true
hydrating?: boolean
): Component {
// 获取 el 对象
el = el && query(el)
/* istanbul ignore if */
// el 不能是 body 或者html
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to or - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
// 把 template/el 转换成render函数
if (!options.render) {
let template = options.template
// 如果模板存在
if (template) {
if (typeof template === 'string') {
// 如果模板是 id 选择器
if (template.charAt(0) === '#') {
// 获取对应节点的 innerHTML
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('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
const {
render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
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方法,渲染DOM
return mount.call(this, el, hydrating)
}
过程
platfroms/web 存放平台相关代码
entry-runtime-with-compiler.js 打包完整版vue入口文件
entry-runtime.js 运行时版本文件
platfroms/web/runtime/index.js 存放平台相关,运行时相关指令,注册patch以及mount方法
core/index.js initGlobalAPI挂载静态方法
core/instance/index.js 存放vue的静态函数,判断是否是vue的实例
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'
// 此处不使用 class 原因是方便后续给 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')
}
// 调用init 方法
this._init(options)
}
// 注册vm 的init方法,初始化vm
initMixin(Vue)
// 注册cm的¥data/$props/$set/$delete/$watch
stateMixin(Vue)
//初始化事件相关方法
// $on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
// 混入render
// $nextTick/_render
renderMixin(Vue)
export default Vue
四个导出Vue 的模块
src/platforms/web/entry-runtime-with-compiler.js
web平台相关的入口
重写了平台相关的$mount
注册了Vue.compile()方法,传递一个HTML字符串返回 render函数
如果同时传入template,render,会执行render函数,没有render会将template编译成render
/* @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 {
compileToFunctions } from './compiler/index'
import {
shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
// 保留 Vue 实例的$mount 方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
// 非ssr情况下为false,ssr时候为true
hydrating?: boolean
): Component {
// 获取 el 对象
el = el && query(el)
/* istanbul ignore if */
// el 不能是 body 或者html
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to or - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
// 把 template/el 转换成render函数
if (!options.render) {
let template = options.template
// 如果模板存在
if (template) {
if (typeof template === 'string') {
// 如果模板是 id 选择器
if (template.charAt(0) === '#') {
// 获取对应节点的 innerHTML
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('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
const {
render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
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方法,渲染DOM
return mount.call(this, el, hydrating)
}
/**
* 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
src/platforms/web/runtime/index.js
web平台相关
注册和平台相关的全局指令:v-model、v-show
注册和平台相关的全局组件:v-transtion、v-transition-group
全局方法:patch:把虚拟DOM转换成真实DOM、 $mount:挂载方法
/* @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 } 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
// extend是负责对象成员的功能
// 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)
}
// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
setTimeout(() => {
if (config.devtools) {
if (devtools) {
devtools.emit('init', Vue)
} else if (
process.env.NODE_ENV !== 'production' &&
process.env.NODE_ENV !== 'test'
) {
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' &&
process.env.NODE_ENV !== 'test' &&
config.productionTip !== false &&
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
src/core/index.js
与平台无关
设置了Vue的静态方法,initGlobalAPI(Vue),设置了vue的一些set方法,delete,nextTick
import Vue from './instance/index'
import {
initGlobalAPI } from './global-api/index'
import {
isServerRendering } from 'core/util/env'
import {
FunctionalRenderContext } from 'core/vdom/create-functional-component'
initGlobalAPI(Vue) //给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
})
Vue.version = '__VERSION__'
export default Vue
src/core/instance/index.js
与平台无关
定义了构造函数Vue,调用了this._init(options)方法
给Vue 中混入了常用的实例成员
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'
// 此处不使用 class 原因是方便后续给 Vue实例混入实例成员
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue) //判断是否是Vue的实例,如果不是,不是通过new调用,抛出警告
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// 调用init 方法
this._init(options)
}
// 注册vm 的init方法,初始化vm
initMixin(Vue)
// 注册cm的$data/$props/$set/$delete/$watch
stateMixin(Vue)
//初始化事件相关方法
// $on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
// 混入render
// $nextTick/_render
renderMixin(Vue)
export default Vue
全局的vue组件都存放在Vue.options.components中
全局环境存放在inBrowser中,在until/env中
Vue 初始化静态成员
vue官网的 全局API 设置的就是此处
vue-dev\src\core\global-api\index.js
初始化设置vue.config
设置Vue.util
设置静态方法 set,del,nextTick
设置Vue.options
注册component,directive,fiffifter
设置keep-live 注册use 注册Vue.mixin()
vue源码实现 directive 、component 、filter
/* @flow */
import {
ASSET_TYPES } from 'shared/constants'
import {
isPlainObject, validateComponentName } from '../util/index'
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
// 遍历ASSET_TYPES数组 ,为Vue 定义相应方法
// ASSET_TYPES包含 directive 、 filter 、 component
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
// 如果未定义 就在Vue.options上查找
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
// Vue.component('textCom':"")
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
// 把组件配置转换为组件的构造函数
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = {
bind: definition, update: definition }
}
// 全局注册,存储并赋值
// this.options['components']['textCom'] = definition
this.options[type + 's'][id] = definition
return definition
}
}
})
}
Vue 初始化实例成员
src/core/instance/index.js
调用了initMixin, 注册vm 的init方法,初始化vm
调用stateMixin,设置了 $data, $props , $ watch, d e l e t e / delete/ delete/watch
eventsMixin,通过订阅发布 模式,Vue挂载了on,once,off,emit
lifecycleMixin,初始化生命周期的混入_update/$ forceUpdate/$ destroy
renderMixin ,混入了render 和 nextTick
整体是在Vue或原型上挂载对应的方法
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'
// 此处不使用 class 原因是方便后续给 Vue实例混入实例成员
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue) //判断是否是Vue的实例,如果不是,不是通过new调用,抛出警告
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// 调用init 方法
this._init(options)
}
// 注册vm 的init方法,初始化vm
initMixin(Vue)
// 注册cm的¥data/$props/$set/$delete/$watch
stateMixin(Vue)
//初始化事件相关方法
// $on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
// 混入render
// $nextTick/_render
renderMixin(Vue)
export default Vue
init的实现
src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {
// 给Vue 实例增加了 _init() 方法
// 合并 options / 初始化操作
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++ // uid是一个唯一标识
let startTag, endTag
/* istanbul ignore if */
// 性能检测
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${
vm._uid}`
endTag = `vue-perf-end:${
vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
// 如果是vue实例不需要observe
vm._isVue = true
// merge options
// 合并 options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
// 组件执行
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {
},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// vm 的生命周期相关变量初始化
// $children / $parent / $root / $refs
initLifecycle(vm)
// vm 的事件监听变化,父组件绑定在当前组件上的事情
initEvents(vm)
// vm的编译 render初始化
// $slots /$scopedSlots / _c /$createElemt / $attrs / $listeners
initRender(vm)
// beforeCreate 的回调
callHook(vm, 'beforeCreate')
// 将inject 的成员注入到vm上
initInjections(vm) // resolve injections before data/props
// 初始化 vm 的_props/methods/_data/computed/watch
initState(vm)
// 初始化 provide
initProvide(vm) // resolve provide after data/props
// create 生命钩子的回调
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${
vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el) // 调用mount 挂载
}
}
}
initState 的实现
src\core\instance\state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props) // 初始化props,并且通过defineReactive函数将值转换为set,get
if (opts.methods) initMethods(vm, opts.methods) // 将选项中的methods注入到vue实例,
if (opts.data) {
initData(vm) //
} else {
observe(vm._data = {
}, true /* asRootData */) //转换成响应式数据
}
if (opts.computed) initComputed(vm, opts.computed) // 初始化computed
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch) // 初始化watch
}
}