Vue2.5源码阅读笔记01—代码结构与初始化

1. 前言

Vue作为当下最流行的渐进式的js框架,其渐进式的思想、虚拟DOM的运用、组件化的开发模式、响应式数据侦听原理值得开发者进行探索学习,其中运用的代码组织的技巧,对平时的开发大有裨益。本系列笔记初步分为8个章节,记录本人源码的阅读过程。

2. 构建准备

2.1 Flow静态类型检查

虽然Vue源码打包压缩后只有20kb,足够小巧轻量,但是源码还是相对复杂,开发者为了增强源码的可读性和可维护性,在ES2015和ESLint基础上,引入Flow做静态类型检查。Flow和TypeScript极为相似,前者由Facebook出品,后者则是微软的杰作。

Flow的类型检查分2种方式:

  • 类型推断:通过变量的使用上下文推断变量类型并检查,不需要添加任何代码进行改造
  • 类型注释:通过代码中开发者写的类型注释检查,需要开发者写明类型注释

具体的语法和细节可以阅读官方文档:flow.org

2.2 Rollup构建工具

Vue.js 源码是基于 Rollup 构建的,Rollup和webpack类似,但是Rollup相比webpack更加轻量,没有对图片等文件的打包处理,适合js框架的打包,构建相关配置在scripts目录下。最终的打包版本分为 Runtime OnlyRuntime + Compiler,显然Runtime Only版本会更加轻量,需要webpack在开发环境对vue文件预编译为js文件,由于前端浏览器进行template编译会消耗大量性能,因此开发更加推荐Runtime Only方式,此处学习过程参考的是 Runtime + Compiler

3. 目录结构

Vue.js 的源码存放在 src 目录下,整个目录结构十分清晰,目录结构如下所示:

src
├── compiler    # 编译
├── core        # 核心代码
├── platforms   # 平台支持,web & weex
├── server      # 服务端渲染
├── sfc         # vue文件解析
├── shared      # 共享代码

4. 从入口文件寻找Vue定义

从配置文件夹script下的config.js可以看到,入口文件为平台目录下的 src/platforms/web/entry-runtime.js,在入口文件中能够找到引用了 src/core/index.js 下的Vue,最终确定Vue的定义在 src/core/instance/index.js

'web-runtime-cjs': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.js'),
    format: 'cjs',
    banner
}

Vue的定义非常清晰,Vue是一个 Function 类,只能通过 new Vue 实例化。Vue的定义下面有多个Mixin方法,可以给 Vue 的 prototype 扩展方法,这种开发技巧能够使得代码结构更加清晰、易于维护,但这种方式ES6的Class难以实现,因此此处使用的是ES5的Funtion构建类

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

5. new Vue 实例化

Vue类使用 Function 实现,只能通过 new 关键字初始化,在上面的Vue定义中可以看到,Vue的初始化主要是通过 this._init 方法,传入options参数,_init方法在 src/core/instance/init.js 中定义。Vue 初始化进行了合并配置、初始化生命周期、初始化事件、初始化渲染、初始化 data/props/computed/watcher 等,都是通过调用函数执行,这些函数分别封装在不同的js文件中。初始化最后,检测到如果有 el 属性,则调用 vm.$mount 方法挂载 vm,即把模板渲染成最终的 DOM。

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券