本来想只针对Vue几个重点功能进行整理,发现篇章越铺越开,自己又很不喜欢又长又臭的博客,所以就把最近的内容拆成独立的模块去总结。主线还是沿着这五个主题数据响应式,编译器,渲染,事件机制以及服务端渲染实现原理去梳理总结,偶尔有些觉得有干货的知识点会再单拎出来总结。
源码结构
既然学习源码,这里列一下github上vue项目的源码目录结构以及对应的功能,有一些知识粗略过了一下,后续读完之后如果有变的话,再回来更新。
- compiler: 编译器模块,编译器主要负责的是把template转换成ast再转成可执行的js代码
- core:核心模块,vue函数初始化/实例化代码这一块(包含整个数据响应式系统的实现),vue框架的入口
- platforms:放置不同平台兼容代码
- server:服务端渲染相关代码
- sfc:单文件vue组件处理相关代码
- shared:常量以及一些公共工具函数代码
这里提一下,建议学习vue源码,最好去src目录下,找到入口,一步一步去读。我一开始就是直接把dist下的vue.js文件拿来读,一万多行,跳来跳去,导致学习效率很低。
核心模块代码结构
vue的数据响应式系统所有实现的代码都在核心模块下,这里具体列一下这个目录下的代码。
- components: vue自带的组件,目前只有keep-alive组件
- global-api: vue函数属性初始化
- instance:vue对象实例化逻辑
- observer: 观察器模块,vue内置的观察器,实现了一套观察者模式。响应式系统就是基于这套观察器实现的。
- util: 初始化vue函数或实例的一些公共函数
- vdom:未知,后续补上
- config.js :vue组件配置项,即默认的options参数
- index.js:入口文件
学习分析核心模块的代码,可以从上面的index.js文件读起。
Vue函数结构分析
从上面的入口文件(src/core/index.js)可知:
- 从core/instance/index.js导出Vue函数
- 调用global-api/index的initGlobalAPI函数初始化Vue
- 其他的三句代码是在定义了两个Vue原型属性以及一个Vue函数属性。从名字上看,这三个属性都是服务端渲染相关的,我们暂时不在这里深究它。
从上面两张图分别是core/instance/index.js和initGlobalAPI函数的逻辑,从图一可以看出,Vue函数只通过调用原型上的_init函数去初始化Vue实例。
鉴于篇幅问题,这里就不一一贴代码了。这里先总结一下。
代码中分别调用了initMixin,statusMixin,eventMixin,lifecycleMixin,renderMixin函数对Vue的原型对象进行初始化,而initGlobalAPI函数则是对Vue的函数属性进行初始化。
Vue原型对象初始化逻辑
Vue原型对象初始化逻辑都在这个文件下src/core/instance/index.js,下面是调用函数的过程,这里大致列一下每个函数对应的功能。
- initMixin: 初始化原型上_init函数,这个函数用于初始化
- statusMixin: 初始化原型上的一些公共属性,$data,$props,$set,$delete,$watch。
- eventMixin:初始化原型上事件通信相关的函数(内置的EventBus),$on,$once,$off,$emit
- lifecycleMixin: 初始化原型上跟生命周期用到的函数。_update,$forceUpdate,$destroy
- renderMixin: 初始化了渲染相关的函数。$nextTick,_render以及一些处理渲染节点的公共函数,这里就不列出来了。
Vue原型对象属性分析(Vue.prototype)
- _init:实例化vue实例函数,所有vue的实例都是调这个函数去初始化
- $data:只读属性,getter返回当前作用域的_data属性(实例化后data数据存储的位置)
- $props:只读属性,getter返回当前作用域的_props属性(实例化后props数据存储的位置)
- $set:指向全局set函数,用于设置一个可响应的属性。一般用于新增属性到data或其他需要跟踪的对象。
- $delete:指向全局del函数,与$set相对应,用于删除数据上某个属性,若该属性是响应式属性,则触发相应的变化
- $watch:用于监听器初始化,所有的vue实例的监听器都是通过该函数初始化的
- $on:对当前实例监听某个事件,将回调函数加入到事件队列
- $once:对当前实例监听某个事件,将回调函数加入到事件队列,调用一次就移除
- $off: 移除当前实对某个事件的监听
- $emit: 触发某个事件
- _update:用于更新当前实例dom节点内容
- $forceUpdate:刷新当前实例dom节点内容(触发渲染监听器重新计算)
- $destroy:移除当前实例dom节点内容,清空与父组件的关联以及清空组件监听器列表的依赖
- $nextTick:指向util下面的nextTick函数,作用是将某个函数回调压入微任务或宏任务栈中。优先级是Promise > setImmediate > setTimeout,具体使用哪个,根据当前运行环境来选择。
- _render:用于计算当前实例的vnode实例
部分渲染函数没有列出,感兴趣可以到src/core/instance/render-helpers.js下查看
Vue函数属性初始化逻辑
Vue函数属性初始化逻辑都在initGlobalAPI函数中,下面是该函数初始化的逻辑。
- 初始化config属性
- 初始化util属性
- 初始化set,delete,nextTick属性
- 初始化observable属性
- 初始化Vue.options属性
- initUse:初始化use属性
- initMixin:初始化mixin属性
- initExtend:初始化extend属性(函数)
- initAssetRegisters:初始化component / directive / filter属性(函数)
Vue函数属性分析
- config:对象,只读属性,Vue内置的默认配置,指向src/core/config.js导出的值。
- util:对象,包含了一些工具函数。warn(打印报错),extend(组件继承),mergeOptions(合并配置),defineReactive(定义一个可响应数据)
- set:函数,同Vue.prototype.$set
- delete:函数,同Vue.prototype.$delete
- nextTick:函数,同Vue.prototype.$delete
- observable:函数,递归将一个对象下所有属性转成可响应数据
- options:对象,存储全局的配置,所有vue实例初始化,都需要与这个属性进行合并
- use:函数,用于全局引入插件,插件系统这块后续单独展开讲
- mixin:函数,用于全局引入混入,实际上将传入的参数属性合并到Vue.options中,再由Vue.options生效到所有的Vue实例
- component:函数,用于对当前调用的对象引入组件,生效的范围是该组件所有的实例
- directive:函数,用于对当前调用的对象引入指令,生效的范围是该组件所有的实例
- filter:函数,用于对当前调用的对象引入过滤器,生效的范围是该组件所有的实例
- extend:函数,用于组件间继承。所有自定义组件,都是通过Vue.extend而来。继承意味着继承了父类所有的属性,返回的是一个新的Vue组件函数。
上面啰里八嗦整理了这么多,其实很多都跟官网的api重合了,不用特地去记,因为是第一篇,所以我自己就做了一次归纳。还有一个点是,Vue一般带$,_符号开头的变量,都是原型对象上的属性,而函数属性则走的是小驼峰命名。
Vue实例化逻辑
Vue实例化经过下面几个步骤(_init函数逻辑):
- 生成id
- 计算生成实例的$options属性
- 初始化生命周期属性($parent,$root,$children,$refs,_watcher,_inactive,_directInactive,_isMounted,_isDestroyed,_isBeingDestroyed)
- 初始化渲染属性(_vnode,_staticTrees,$slots,$scopedSlots, _c,$createElement,$attrs,$listeners)
- 回调生命周期钩子beforeCreate
- 初始化inject配置,挂载到vue实例下(根据vm.$options.inject 初始化 vm.inject )
- 初始化基本的数据属性(初始化顺序依次是props,methods,data,computed, watch)
- 初始化Provide属性
- 回调生命周期钩子created
- 判断$options.el属性是否存在,有则调用$mount进行挂载实例
总结
- 应从src目录阅读源码
- Vue源码入口在src/core/index.js
- 原型属性命名格式一般为$, _开头,函数下的属性则是使用小驼峰命名。这个实践还是蛮不错的
- Vue组件实例化时,基本的数据/函数属性初始化顺序分别是:inject,props,methods,data,computed, watch,provide
希望这篇文章能对大家刚开始阅读源码有所帮助。