以下内容来自 拉勾教育大前端训练营 笔者在学习过程中对笔记进行的一个整理
心得体会
嘿嘿嘿~~~ 首先说说拉勾教育大前端训练营的课程视频吧,课程的质量是真的很好哦,并且已经收到了非常多的好评,在课程规划知识体系上,非常详细,对于每一个知识点都讲的非常透彻,可以说是 手摸手系列了,并且视频是学习完一章才能进入下一章学习,给我的感觉就是在打怪升级一样,学习完解锁下一章节,并且也非常期待下一章会Get
到什么的技能,从而不断的强化自己 。
以在Vue
框架源码与进阶为例,再看源码之前了,我学习了手写 Vue Router
、手写Vue
响应式实现、虚拟 DOM
和 Diff
算法,以前只是停留在会用的阶段,到目前已经深入学习了Vue的响应式原理,和如何去手写实现一个Vue Router
, Get
到了非常多的技能以及 黑魔法 。
首先回顾 Vue Router
的基本使用,以及 Hash
模式和 History
模式的区别,然后自己手写一个实现基 History
模式的前端路由,了解路由内部实现的原理;接下来在数据响应式实现原理分析中,自己动手一个简易版本的 Vue
;最后掌握虚拟 DOM
的作用,通过一个虚拟 DOM
库 Snabbdom
真正了解什么是虚拟 DOM
,以及 Diff
算法的实现和key
的作用。
除此之外呢,在学习完一个大章节都会进行一次互动直播答疑总结, 嘿嘿嘿~~~
我其实是非常期待每一次的直播的,因为每一次都是干货满满,收获很多,还有好几位助教老师在群里进行答疑,只要我们有不懂得问题,老师都会以最快的速度帮助我们解决,如果是实在解决不了的问题,熊熊老师会亲自git 你的代码,然后运行代码进行问题的定位,找到问题并解决之后也会告诉你是如果解决的,真的真的很贴心 ~ emmm 还有其实我们每章都有一个大作业,班主任老师呢会每天督促大家去完成作业,让大家都紧跟脚步,有问题,班主任老师也会及时记录下来。
再说一点吧,学习群每天都很活跃,每天大家都会遇到非常多的问题,大家只要把问题丢进去,很快就会得到其他同学的解答,包括我自己也是非常开心的帮助其他同学解决问题,在拉勾大前端训练营和大家一起学习,一起进步。
笔记将会对以下三点进行总结
Vue源码的获取
为什么分析Vue2.6
Vue3.0
的正式版本还没有发布3.0, 2.x
还有很长的一段过渡期源码目录结构
我们获取Vue
源码后,重点看src
下面的目录结构
|--src
|--compiler // 编译相关
|--core // Vue 核心库
|--platforms // 平台相关代码
|-- server // SSR,服务端渲染
|--sfc // .vue 文件编译为 js对象
|--shared // 公共的代码
compiler
编译器把模板转换成render
函数,render
函数会帮我们创建虚拟DOM
core components
中定义了keep-alive
组件,接下来是global-api
, 它定义了Vue
的静态方法, assets
extend
, mixin
, use
等方法instance
是创建Vue实例的位置,这里定义了Vue
的构造函数,以及Vue
的初始化,还有Vue
生命周期函数observer Vue
响应式核心util
公共成员vdom
虚拟DOM
platforms
平台相关代码web weex
server vue2.0
支持SSR
,服务端渲染sfc
将.vue
文件编译为 js对象shared
公共的代码了解Flow
JavaScript
的静态资源类型检测器Flow
的静态文件类型检查错误是通过静态类型推断实现的// @flow
或者 、/* @flow */
声明,如下/* @flow */
function square(n: number): number {
return n * n;
}
square("2"); // Error
如何对Vue源码进行打包和调试
打包
Rollup
Vue.js
源码的打包工具使用的是Rollup
,相比Webpack
更轻量Webpack
把所有文件当做模块,Rollup
只处理js
文件更适合在Vue.js
这样的库中使用Rollup
打包不会生成冗余的代码安装依赖
npm i
设置SourceMap
package.json
文件中的dev
脚本中添加参数 --sourcemap
方便我们调试
"dev": "rollup -w -c script/config.js --sourcemap --environment TARGET:web-full-dev"
执行dev
package.json 文件中的dev脚本中添加参数 --sourcemap,方便我们调试,出现错误可以看到具体的位置
"dev": "rollup -w -c script/config.js --sourcemap --environment TARGET:web-full-dev"
-w
是watch-c
设置配置文件 scripts/config.js
--environment
环境变量,用来打包生成不同版本Vue
执行打包命令 npm run dev
术语
JavaScript
渲染函数的代码,体积大,效率低Vue
实例、渲染并处理虚拟DOM
等代码,体积小、效率高,基本就是出去编译器的代码vue. js
默认文件就是运行时+编译器的UMD
版本CommonJS
版本用来配合老的打包工具比如Browserify
或webpack 1.
2.6
开始Vue
会提供两个ES Modules (ESM)
构建文件,为现代打包工具提供的版本。ESM
格式被设计为可以被静态分析,所以打包工具可以利用这一点来进行 tree shaking
并将用不到的代码排除出最终的包。ES6
模块与CommonJS
模块的差异vue inspect > output.js 输出文件 查看webpack配置
Vue-cli
项目中使用的Vue
版本就是vue.runtime.esm.js
运行时的版本dist/vue.js
的构建过程执行构建
npm run dev
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev"
// environment TARGET:web-full-dev 设置环境变量TARGET
script/config.js 的执行过程
rolllup
构建的配置文件TARGET = web-full-dev
// 判断环境变量是否有TARGET
// 如果有的话使用genConfig() 生成rollup 配置文件
if (process.env.TARGET) {
module.exports = genConfig(process.env.TARGET)
} else{
// 否则获取全部配置
exports.getBuild = genConfig
exports.getAllBuilds = () => object.keys(builds).map(genConfig)
}
在package.json文件中
"script": {
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
"build": "node scripts/build.js",
}
通过查看源码解决下面问题
const vm = new Vue({
el: '#app',
template: ' Hello template
',
render (h) {
return h('h4', 'Hello render')
}
})
如果传入了render函数 不处理template,直接调用mount方法
阅读源码记录
el
不能是body
或者html
标签template
转换成render
函数render
方法,直接调用mount
挂载DOM
// 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) {
...
// 2把template/el转换成render 函数
}
// 3调用mount 方法,挂载DOM
调试代码, 调试的方法
const vm = new Vue({
el: '#app',
template: ' Hello template
',
render (h) {
return h('h4', 'Hello render')
}
})
四个导出Vue的模块
1.src/platforms/wb/entry runtime with compilrjis
web
平台相关的入口$mount()
方法2.注册了Vue compile()方法,传递一个HTML字符串返回render函数
src/platforms/web/runtime/index.js
web
平台相关v-model
. v-show
v-transtion
. v-tansition-group
patch _
:把虚拟DOM
转换成真实DOM
$mount
:挂载方法
3.src/core/index.js
4. src/core/instance/index.js
Vue
中混入了常用的实例成员总结
在platforms/web/runtime/index.js
下的文件主要做了以下事情,在这个文件中所有代码都是和平台相关的,注册了平台相关的一些指令,patch
函数以及$mount
这个两个方法
import Vue from 'core/index'
导入了构造函数core/index.js
中,调用了initGlobalAPI(Vue)
方法,给Vue
的构造函数增加以下静态方法,其他内容都是调用Object.defineProperty
给Vue
增加了一些成员,还有服务端渲染SSR
,core/global-api
初始化了Vue
的静态方法instance/index.js
创建了Vue
构造函数,设置了Vue
实例成员instance·文件夹
index.js
定义了Vue
的构造函数,并且调用了initMixin(Vue), stateMixin(Vue),eventsMixin(Vue)
lifecycleMixin(Vue),renderMixin(Vue)
initMixin(Vue)
就是在Vue
的原型上挂载了_init()
方法stateMixin(Vue)
通过Object.defineProperty(Vue.proptotype, '$data', dataDef)
在Vue原型上增加了两个属性eventsMixin(Vue)
分别定义了 $on,$once,$off,$emit
事件,使用发布订阅模式lifecycleMixin(Vue)
定义了forceUpdate destory()
renderMixin
这几个函数的作用都是给Vue原型混入一些成员和属性,给Vue对象增加相应的实例成员
// 注册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)
初始化vm
的 _props/methods/_data/computed/watch
以下是insrance/state.js initState()的源码
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
在instance/state.js中,首先获取了Vue实例中的$options
,然后判断options中是否有props,methods,data
以及computed
和watch
这些属性,如果有的话,通过initProps进行初始化
initProps(vm, opts.props)
接收了两个参数,一个是Vue
实例,一个是Props
属性,我们跳转到initProps
函数中,首先给Vue
实例定义了一个_Props
对象, 并且把它存储到了常量里面
const props = vm._props = {}
紧接着,开始遍历PropsOptions
的所有属性,它其实就是initProps
方法中的第二个参数,遍历每个属性,然后通过defineReactive
注入到Props
这个对象上,这个props
其实就是vm._props
所有的成员都会通过defineReacttive
转化为get
和set
,最后在Props
对象上存储,
注意
defineReactive
把props
中的属性转化成get
和set
props
属性是否在Vue
实例中存在,不存在通过Proxy
这个函数把我们的属性注入到Vue
的实例中在Proxy
中,通过调用Object.defineProperty(target, key,sharePropertyDefinition)
总结initProps
的作用就是把我们的Props
成员转化成响应式数据,并且注入到Vue
实例里面中
initMethods
在initMethods(vm, opts.methods)
中,也是接收两个参数,Vue实例和选项中的methods
,首先获取了选项中的Props,遍历methods
所有属性,然后判断当前的环境是否是开发或者生产
开发环境会判断methods
是否是functicon
继续往下判断methods
方法的名称是否在Props对象中存在,存在就会发送一个警告,警告在属性在Props中已经存在,因为Props和methods最终都要注入到Vue实例上,不能出现同名
之后判断key是否在Vue中存在,并且调用了isReserved(key),判断我们的key是否以_开头或$开头
最后把methods
注入到Vue实例上来,注入的时候会判断是否是function
,如果不是返回noop
,是的话把函数返回bind(methods[key], vm)
总结 initMethods
作用就是把选项的methods
注入到vue
实例,在注入之前,会先判断我们命名是否在Props
中存在,并且判断了命名的规范,不建议_和$开头
initData(vm)
当options
中有data选项时,会调用initData(vm)
当没有的时候此时会给vm
初始化一个_data
属性observe(vm._data = {}, true)
,然后调用observe
函数,observe
是响应式中的一个函数
在initData
中获取了options
的data
选项,判断了data
选项是否是function
,如果是调用getData(data,vm)
接着获取data中的所有属性,同时获取了props,methods中所有的属性
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
最后做一个响应式处理
observe(data, true)
目前还没整理完成哦