源码地址:https://github.com/vuejs/vue

文件结构

image.png

源码目录:
image.png

调试环境搭建

  • 安装依赖:npm i
  • 安装rollup:npm i -g rollup
  • 修改dev脚本,添加--sourcemap,package.json

    1. "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web- full-dev",
  • 运行开发命令: npm run dev

  • 引入前面创建的 vue.js,samples/commits/index.html
    1. <script src="../../dist/vue.js"></script>

    runtime:仅包含运行时,不包含编译器 common:cjs规范,用于webpack1 esm:ES模块,用于webpack2+ umd: universal module defifinition,兼容cjs和amd,用于浏览器

入口文件:

  • dev脚本中-c scripts/config.js 指明配置文件所在
  • 参数TARGET:web-full-dev 指明输出文件配置项,line:123
    1. // Runtime+compiler development build (Browser)
    2. {
    3. 'web-full-dev':
    4. {
    5. entry: resolve('web/entry-runtime-with-compiler.js'), // 入口
    6. dest: resolve('dist/vue.js'),// 目标文件
    7. format: 'umd', // 输出规范
    8. env: 'development',
    9. alias: { he: './entity-decoder' },
    10. banner,
    11. },
    12. }

    初始化流程

    从入口文件开始找Vue的构造函数

整体流程

  • new Vue()
    • _init()
  • $mount()
  • mountComponent()
    • updateComponent()
      • render()
      • update()
    • new Watcher()

image.png

platforms/web/entry-runtime-with-compiler.js

  • 扩展默认$mount方法:处理templateel选项
  • 优先级:render>template>el
  • 编译:获取渲染函数
  1. const mount = Vue.prototype.$mount
  2. // 扩展了$mount方法
  3. Vue.prototype.$mount = function (
  4. ...
  5. //用户的配置选项 data
  6. const options = this.$options
  7. //render不存在时才考虑el和template
  8. if (!options.render) {
  9. ...
  10. // 获取模板之后,编译
  11. if (template) {
  12. // 将template字符串转换为render函数
  13. const { render, staticRenderFns } = compileToFunctions(template, {
  14. ...
  15. options.render = render
  16. //执行默认的挂载功能
  17. return mount.call(this, el, hydrating)

platforms/web/runtime/index.js

  • 安装web平台特有指令和组件
  • 定义__patch__:补丁函数,执行patching算法进行更新
  • 定义$mount:挂载vue实例到指定宿主元素(获得dom并替换宿主元素)
  1. // 声明了patch方法
  2. Vue.prototype.__patch__ = inBrowser ? patch : noop
  3. // 定义$mount方法
  4. Vue.prototype.$mount = function (
  5. el?: string | Element,
  6. hydrating?: boolean
  7. ): Component {
  8. el = el && inBrowser ? query(el) : undefined
  9. //挂载执行
  10. return mountComponent(this, el, hydrating)
  11. }

core/index.js

  • 初始化全局api
    1. // 初始化全局api
    2. initGlobalAPI(Vue)
  1. import { initGlobalAPI } from'./global-api/index'
  1. Vue.set = set
  2. Vue.delete = del
  3. Vue.nextTick = nextTick
  4. initUse(Vue) // 实现Vue.use函数
  5. initMixin(Vue) // 实现Vue.mixin函数
  6. initExtend(Vue) // 实现Vue.extend函数
  7. initAssetRegisters(Vue) // 注册实现Vue.component/directive/filter

core/instance/index.js

  • Vue构造函数定义
  • 定义Vue实例方法API ```javascript //真正的构造函数 function Vue (options) { // 构造函数仅执行了_init this._init(options) }

//实例方法的初始化 initMixin(Vue) // 实现init函数 stateMixin(Vue) // 状态相关api $data,$props,$set,$delete,$watch eventsMixin(Vue)// 事件相关api $on,$once,$off,$emit lifecycleMixin(Vue) // 生命周期api _update,$forceUpdate,$destroy renderMixin(Vue)// 渲染api _render,$nextTick

  1. **core/instance/init.js**
  2. - 初始化过程:组件属性、事件等初始化、声明周期、数据响应式
  3. ```javascript
  4. //init.js
  5. ...
  6. // 合并 options
  7. if (options && options._isComponent) {
  8. //核心初始化逻辑
  9. initLifecycle(vm) // 初始化$parent,$root,$children,$refs
  10. initEvents(vm) // 处理父组件传递的监听器
  11. initRender(vm) // $slots, $scopedSlots,_c(),$createElement()
  12. callHook(vm, 'beforeCreate') //组件创建之前的钩子
  13. initInjections(vm) // 注入祖辈传递的数据
  14. initState(vm) // 重要:初始化组件中 props、methods、data、computed、watch
  15. initProvide(vm) // resove provide after data/props
  16. callHook(vm, 'created')
  17. ...
  18. // 如果用户设置el选项,自动执行$mount
  19. if (vm.$options.el) {
  20. vm.$mount(vm.$options.el)
  21. }

$mount

  • mountComponent

执行挂载,获取vdom并转换为dom

  • new Watcher()

创建组件渲染watcher

  • updateComponent()

执行初始化或更新

  • update()

初始化或更新,将传入vdom转换为dom,初始化时执行的是dom创建操作

  • render() src\core\instance\render.js

渲染组件,获取vdom

  1. //lifecycle.js
  2. // 定义组件更新函数
  3. // _render()执行可以获得虚拟dom,VNode 在renderMixin()
  4. // _update()将虚拟dom转换为真实dom
  5. updateComponent = () => {
  6. vm._update(vm._render(), hydrating)
  7. }
  8. new Watcher(vm, updateComponent, noop, {

整体流程捋一捋
new Vue() => _init() => $mount() => mountComponent() =>
new Watcher() => updateComponent() =>render() => _update()

思考一道相关面试题:谈谈vue生命周期 概念:组件创建、更新和销毁过程

用途:生命周期钩子使我们可以在合适的时间做合适的事情

分类列举:

  • 初始化阶段:beforeCreate、created、beforeMount、mounted

  • 更新阶段:beforeUpdate、updated

  • 销毁阶段:beforeDestroy、destroyed

应用:

  • created时,所有数据准备就绪,适合做数据获取、赋值等数据操作

  • mounted时,$el已生成,可以获取dom;子组件也已挂载,可以访问它们

  • updated时,数值变化已作用于dom,可以获取dom最新状态

  • destroyed时,组件实例已销毁,适合取消定时器等操作

数据响应式

数据响应式是MVVM框架的一大特点,通过某种策略可以感知数据的变化。Vue中利用了JS语言特性 Object.defineProperty(),通过定义对象属性getter/setter拦截对属性的访问。
具体实现是在Vue初始化时,会调用initState,它会初始化data,props等,这里着重关注data初始
化,
整体流程

  1. initState (vm: Component) src\core\instance\state.js

初始化数据,包括props、methods、data、computed和watch

initData核心代码是将data数据响应化

  1. function initData (vm: Component) {
  2. // 执行数据响应化
  3. observe(data, true /* asRootData */)
  4. }

core/observer/index.js
observe方法返回一个Observer实例

core/observer/index.js
Observer对象根据数据类型执行对应的响应化操作
defineReactive定义对象属性的getter/setter,getter负责添加依赖,setter负责通知更新

core/observer/dep.js
Dep负责管理一组Watcher,包括watcher实例的增删及通知更新

Watcher
Watcher解析一个表达式并收集依赖,当数值变化时触发回调函数,常用于$watch API和指令中。
每个组件也会有对应的Watcher,数值变化会触发其update函数导致重新渲染

  1. export default class Watcher {
  2. constructor () {}
  3. get () {}
  4. addDep (dep: Dep) {}
  5. update () {}
  6. }

数组响应化

数组数据变化的侦测跟对象不同,我们操作数组通常使用push、pop、splice等方法,此时没有办法得
知数据变化。所以vue中采取的策略是拦截这些方法并通知dep。

src\core\observer\array.js
为数组原型中的7个可以改变内容的方法定义拦截器

Observer中覆盖数组原型

  1. if (Array.isArray(value)) {
  2. // 替换数组原型
  3. protoAugment(value, arrayMethods) // value.__proto__ = arrayMethods
  4. this.observeArray(value)
  5. }