目标
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 参数是监听文件的变化,文件变化自动重新打包
Vue的不同构建版本
使用 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:"<h1>hello tempalte</h1>",
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 <html> or <body> - 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: