前言
之前看了Vue的源码视频,看完了想着总结一下,写的有点粗略,可以把它当作大纲,浅看~
老实说,好希望疫情快点过去啊
异步组件
异步组件的本质是2次渲染,先渲染成注释节点,然后当组件加载成功后,再通过forceRender重新渲染;异步组件实现方式中,高级异步组件通过简单的配置实现了loading,resolve,reject,timeout四种状态,有一说一跟promise好像
响应式对象
我说的是vue2的哦,核心就是用object.defineProperty给数据添加了getter和setter,目的就是为了在我们访问数据以及读写的时候能自动执行一些逻辑;
getter:依赖收集
收集依赖的目的是为了当这些响应式数据发生变化触发对应的setter时,可以知道应该通知哪些订阅者去做相应的逻辑处理,其中的Watcher和Dep就是一个典型的观察者模式。
setter:派发更新
当数据发生变化的时候触发setter逻辑,把在依赖过程中订阅的所有观察者,也就是watcher触发它们的update过程,这个过程又利用了队列做了进一步的优化,在nextTick后执行所有watcher的run,最后执行它们的回调函数
nextTick
数据的变化到dom的重新渲染是一个异步过程,发生在下一个tick,平时从服务端接口去获取数据的时候,数据做了修改,如果我们的某些方法去依赖数据修改后的dom变化就要在nexttick后执行
- nexttick是要把执行的任务推入一个队列中,在下一个tick同步执行
- 数据改变后触发渲染watcher的update,重新渲染是异步的
计算属性和侦听属性
computed:只有值发生变化才会重新渲染,否则不做任何操作;计算属性的本质是computedwatcher,计算属性适合用在模版渲染中,某个值是依赖了其他的响应式对象或计算属性计算而来
侦听属性的本质是userwatcher,它还支持deep,sync,immediate,侦听属性适用于观测某个值的变化做一些操作
组件更新
组件更新的过程,核心就是新旧vnode diff,对新旧节点相同以及不同的情况分别做不同的处理
新旧节点的更新流程是创建新节点-->更新父占位符节点-->删除旧节点,而新旧节点相同的更新流程是去获取它们的children,根据不同情况做不同的更新逻辑,最复杂的情况是新旧节点相同且它们都存在子节点,那么会执行update children逻辑
原理图
不管是webpack、vue还有巴拉巴拉的编译,都是parse、optimize、generator
编译
vue在不同的平台下都会有编译的过程,因此编译过程中的依赖配置baseOptions会有所不同;vue利用了函数柯里化(柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数 (最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术)的技巧很好的实现了baseOptions的参数保留,同样vue也是利用此方法把baseCompile函数抽出来,把真正编译的过程和其他逻辑比如编译配置处理、缓存处理等剥离开;
1. Parse
编译过程是对模版的解析,生成AST(抽象语法树)整个过程会用到大量的正则表达式对字符串进行解析,递归深度遍历JS对象
2. Optimize
- optimize的目标是通过标记静态根的方式,优化重新渲染过程中对静态节点的处理逻辑
- 过程就是深度遍历ast树,先标记静态节点,再标记静态根,当我们的template经过parse过程后,会生成ast树然后optimize优化
3. code-generate
生成的目的就是把ast树转换成代码字符串,通过深度遍历ast树根据不同条件生成不同代码的过程,根据一个具体的case走完一条主线
event
- event在编译阶段生成相关的data,对于整个dom事件在patch的过程中的创建阶段和更新阶段,执行updateDomListeners生成dom事件,对于自定义事件,会在组件初始化阶段通过initEvents创建
- 原生dom事件的自定义事件主要的区别在于添加和删除事件的方式不一样,并且自定义事件的派发是往当前实例上派发,但是可以利用在父组件环境定义的回调函数来实现父子组件的通讯
插槽
- 普通插槽 在父组件编译和渲染阶段生成vnodes,所以数据的作用域是父组件实例,子组件渲染的时候直接拿到这些渲染好的vnodes(父组件环境)
- 作用域插槽 父组件在编译的渲染阶段并不会直接生成vnodes,而是在父节点的data中保留一个scopedSlots对象,存储着不同名称的插槽以及它们对应的渲染函数,只有在渲染子组件阶段才会执行这个渲染函数生成vnodes,延迟渲染(子组件环境)
keep-alive
keep-alive组件是一个内置抽象组件,它的实现是通过自定义render函数和插槽;其渲染分为首次渲染和缓存渲染,当命中缓存,则不会再执行created和mounted钩子函数,而会执行active的钩子函数,销毁时也不会执行destroyed而是deactived,递归地去执行所有子组件的钩子函数
transition
内置抽象组件也是基于插槽实现的,核心方法是enter(v-show也会调用这个方法),做一些增删类名的操作
transitionGroup
列表专用,它会渲染成真实的元素,当我们去增删改列表的数据时,会触发相应元素的过渡动画除此之外还实现了move的过渡效果
vue-Router
路由初始化的时机是在组件初始化阶段,执行到beforeCreate的时候执行router.init()方法,然后会执行history.transitionTo方法做路由过渡,
- matcher
- createMatcher的初始化就是根据路由的配置描述创建映射表,包括路径、名称到路由record的映射关系
- match会根据传入的位置和路径计算出新的位置,并匹配到对应的路由record,然后根据新的位置和record创建新的路径并返回
Vue-Router的路由模式有两种:hash和history,这两种模式的监听方法不一样,路由始终会维护当前的线路,路由切换的时候会把当前的线路切换到目标线路,切换过程中会执行一系列的导航守卫钩子函数,会更改URL,同样也会渲染对应的组件,切换完毕后会把目标线路更新替换当前线路,这样就会作为下一次的路径切换的依据
vuex
vuex是通过全局注入store对象来实现组件间的状态共享,store就是一个数据仓库,为了更方便的管理仓库,store会拆成modules,整个modules就是一个树形结构。每个module又分别定义了state,getters,mutations,actions,然后通过递归遍历模块的方式完成了初始化。为了module具有更高的封装度和复用性定义了namespace。
最后想说,真的写的非常的简单,有问题的话欢迎提出来😁