前端-Vue 2.x源码学习(一)-Vue函数初始化与实例化

271 阅读5分钟

本来想只针对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

从上面的入口文件(src/core/index.js)可知:

  • 从core/instance/index.js导出Vue函数
  • 调用global-api/index的initGlobalAPI函数初始化Vue
  • 其他的三句代码是在定义了两个Vue原型属性以及一个Vue函数属性。从名字上看,这三个属性都是服务端渲染相关的,我们暂时不在这里深究它。

src/core/instance/index.js

从上面两张图分别是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

希望这篇文章能对大家刚开始阅读源码有所帮助。