目录
根据不同功能把代码拆分不同文件夹(提高可读性可维护性)
compiler转换render函数
core核心
globalApi Vue的静态方法
instance 创建vue实例
observer 响应式实现(重点讲)
vueDom 重写了snabdom
server 服务端渲染
sfc 单文件组件 把单文件组件转成js对象
了解flow即可 vue3已经全面TS开发
flow可以使js有java搬的开发体验,运行前检查。
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web- full-dev"
examples 的示例中引入的 vue.min.js 改为 vue.js
打开 Chrome 的调试工具中的 source
一路f11调试。
(开启sourmap可以根据src不同模块不同功能调试,不开启会直接用dist/vue.js 里面一万多行代码,易读性差)
full-完整版, runtime-only-运行时
前俩行完整版代码
后俩行压缩版本
编译器的作用是把template转换成render渲染函数(转Vnode)。
编译器代码三千多行。
vue inspect > output.js
// Compiler
// 需要编译器,把 template 转换成 render 函数
// const vm = new Vue({ // el: '#app', // template: '{
{ msg }}
', // data: { // msg: 'Hello Vue' // } // }) // Runtime // 不需要编译器 const vm = new Vue({ el: '#app', render (h) { return h('h1', this.msg) },data: { msg: 'Hello Vue' } }) 123
注意: *.vue 文件中的模板是在构建时预编译的,最终打包后的结果不需要编译器,只需要运行时版本即可
查看 dist/vue.js 的构建过程
npm run dev
script/config.js 的执行过程
src/platform/web/entry-runtime-with-compiler.js
如何找到$mount方法何时调用?
方法:如下
先打包出带sourcemap的vue文件
向下依次点击会顺序执行
el 不能是 body 或者 html 标签
如果没有 render,把 template 转换成 render 函数
如果有 render 方法,直接调用 mount 挂载 DOM
完整版比较复杂
这里的核心就是把template转为render
终于找到vue构造函数
这个文件主要做了:
1创建vue构造函数
2设置vue实例的成员
注意:关于vue构造函数
此处不用 class 的原因是因为方便后续给 Vue 实例混入实例成员(通过原型挂载,用类的话就在语法上很不搭配)
总结
问题2
解决前
下载
解决
虽然安装了上面的插件 但没有点击跳转的功能(无大碍跳过即可)
globalApi中
runtime/index中
继续看 globalApi
extend(Vue.options.components, builtInComponents)//在注册全局组件 这个builtInComponents是全局组件
builtInComponents ↓
回到globalApi中,下面的函数都在global文件夹中
initUse的实现
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
// 这里的this 谁调用用指向谁
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
// 把arguments转换数组,去掉数组中的第一个元素(plugin)去除
const args = toArray(arguments, 1)
// 把this(Vue)插入第一个元素的位置
args.unshift(this)
if (typeof plugin.install === 'function') {
// 这里的apply为了展开参数
// 调用插件方法 传递参数
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}
initMixin
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
//拷贝
this.options = mergeOptions(this.options, mixin)// this 是vue
return this
}
}
initExtend
核心就是返回组件的构造函数
太长了,后面放源码自己看
initAssetRegisters(Vue)
// 注册 Vue.directive()、 Vue.component()、Vue.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、 component、filter
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
// 如果未定义找到之前定义好的
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
// Vue.component('comp', { template: '' })
// 判断是否组件 isPlainObject 是否原始对象
// export function isPlainObject (obj: any): boolean {
// return _toString.call(obj) === '[object Object]'
// }
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
// 把组件配置转换为组件的构造函数
definition = this.options._base.extend(definition) //this.options._base = vue extend把对象转换成组件构造函数
}
// 如果是指令,俩种情况,第一种如果是对象直接执行最后一句,第二种函数进入if分支
if (type === 'directive' && typeof definition === 'function') {
// 这里的bind和update 是方法
definition = {
bind: definition, update: definition }
}
// 全局注册,存储资源并赋值
// this.options['components']['comp'] = definition
this.options[type + 's'][id] = definition //如果直接传组件构造函数会直接执行这句话
return definition
}
}
})
}
总结 定义vue.config,set,delete,observable等,初始化options,继续注册keepalive,use,mixin,extent等,以上都是静态成员
这段代表都是给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) //判断是否生成环境和是否new出来的
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// 调用 _init() 方法 这里的是下面的initMixin注册的
this._init(options)
}
// 注册 vm 的 _init() 方法,初始化 vm
initMixin(Vue)
// 注册 vm 的 $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
这里开始就不一一贴代码了,自己一个一个点进去看会笔记快,源码我都写了注释
先看initMixin
这里的init方法相当于整个vue的入口所有事情都从这里开始
看源码快捷键 按ctrl和鼠标进入后 alt+← 可以快速回去
stateMixin(Vue)
// 注册 vm 的 d a t a / data/ data/props/ s e t / set/ set/delete/$watch
eventsMixin(Vue)
// 初始化事件相关方法
// o n / on/ on/once/ o f f / off/ off/emit
注意这里的初始化的方法 很不错
// 初始化生命周期相关的混入方法
// _update/ f o r c e U p d a t e / forceUpdate/ forceUpdate/destroy
静态成员和实例成员初始化完后会调用vue的构造函数,然后调用init,init是在initMixin中初始的
下面的都直接看源码 都写注释了
以下方法都一个个点进去看源码都写注释了
依赖注入原理
主要调试vue导出的四个文件
继续找到俩个跟平台相关的
patch把vdom转成dom,然后mount挂载到页面
继续找第四个断点,打包文件后的入口,此文件中重写了mount方法,增加了把模板变成render的功能。
开始调试 f5
进入instance/index
最开始只有默认成员和默认构造函数
观察initMixin执行完后 prototype的变化。
按f10跳过此函数,发现prototype中增加了 init方法
继续f10跳过stateMinx,发先多了 d a t a / data/ data/props/ s e t / set/ set/delete/$watch但都是undefined,后面通过选项赋值
继续f10跳过eventsMixin,发现多了事件相关的 o n / on/ on/once/ o f f / off/ off/emit
继续f10跳过lifecycleMixin,发现多了_update/ f o r c e U p d a t e / forceUpdate/ forceUpdate/destroy,其中update调用了patch,把vdom转成dom
继续f10跳过renderMixin,发现多了_开头的方法,等把模板转换成render的时候调用这些方法
还多了nexttick和render(调用用户的render或者把模板转成render)。
这里不看keepalive的执行过程 直接f8到下一个断点
core/index中
f11进入globalApi
按f10执行到
接下来又初始化了util然后是set和delte等
继续一步一步到
初始化option 此时的空对象(啥都没有连proto都没有),此option存储全局指令和组件
foreach结束后
到base这里,base主要存储vue的构造函数
初始化第一个组件keepalive组件
开始初始静态方法
开始注册全局组件和过滤器
初始完成静态成员的过程结束。f8跳到下一个断点
web/runtime/index 此时的代码都是和平台相关的
开始注册平台相关方法
点击f10 执行到
继续下一步,初始化patch和mount,这里在vue的proto上挂载了,但未执行,会在vue的init中调用
f10 到
发现已经添加到vue的proto中
观察结束,f8进入下一个
到了打包文件的入口
执行完后 又给vue挂载了compile方法(作用是手工转换成render函数)
调式完毕四个文件,可以发现vue构造函数的变化,以及初始化静态和实例成员的过程。
下面开始调试init方法,也就是首次渲染的过程。
重点调试init,观察首次渲染的过程
f8进入下一个文件
core/index
f8进入下一个文件
runtime/index
f8进入下一个文件
f8再f11进入init
f10一步一步往下走
此时不是组件,开始合并option
合并前
合并后
f11进入initProxy,大概先判断当前环境是否支持proxy,然后通过proxy代理
跳出initproxy,调试结束,下面的init等方法先不看,现在重点看如何渲染的。
新增断点,然后f8,然后f11进入mount
f10执行到
一直f10如果有方法就f11跳进去看实现
最后到mount方法,返回runtime/index中重写的mount
按f11进入mount
这里重新获取el是因为,如果是运行时版本的话需要获取el,但现在用的是完整版的,已经在之前的方法获取了
进入mountComponent(vue核心代码)
一直下一步
这里定义了但还没执行_update,是在
继续调试到 创建watch,在这里才执行updateCompenent(准确的说是在watcher中的get方法调用了)
f8 f11进入watch
observe文件中的代码都是和响应式相关的
补充 vue中一般有三种watvh 1渲染2计算属性的3侦听器的
lazy的作用是延迟执行,因为当前是首次渲染需要立即更新。
如果是计算属性会true,数据更新后才更新视图。
继续往下走
继续走,这里会判断是否lazy 如果不是就立即执行 get方法
观察get
存入当前watch入栈(每个组件对应每个watch》watch去渲染视图》如果组件嵌套则先渲染内部的》所以需要保存父组件的watch)
get中最关键的一句话,调用刚刚存的getter(这里的getter就是刚刚穿过来的updateComponent)
继续设置断点,f8,f11进入get
一直执行到
f11进入后,到了updateComponent,updateComponent中的update执行完后就会渲染到页面上。f10跳过回到get方法,发现页面已经渲染完完毕
执行结束会继续执行watchr,watchr执行完后 回到lifecircle里
继续f10,把lifecircle执行完毕回到
继续f10 又会会到入口文件》又回到init 》 构造函数,首次渲染结束。
最后触发mounted页面挂载完毕。
init相当vue的入口
第一个mount(entry-runtime-with-compiler)是把模板编译成render函数
第二个mount(runtime/index)重新获取el,然后调用mountComponent。
mountComponent
添加链接描述