Vue源码解析(一)

273 阅读3分钟

vue2源码解析(一)

前言

使用vue也已经有两年了,感觉自己一直只在使用,而没有了解其中的原理和思维,一直想做创造者而不是使用者,所以借这个机会阅读一下源码,学习一下尤大大的思维。

1.准备工作:

先去github将尤大大的vue.js git clone https://github.com/vuejs/vue.git,下载之后先看vue的结构目录:

vue
|——dist
|——examples
|——flow
|——packages
|——script
|——src
|——test
|——types

vue.js的源码都在src目录下,其他目录先不关注,主要看核心代码的目录结构:

src
|—— compiler     # 编译相关
|—— core         # 核心代码
|—— platforms    # 不同平台的支持
|—— server       # 服务端渲染
|—— sfc          # .vue文件解析
|—— shared       # 共享代码

我们主要关注这三个目录compiler, core, sfc:

compiler


compiler目录包含Vue.js所有编译相关的代码。它包括模板解析AST语法树,AST语法树的优化,代码生成等功能。它可以让你认识到Vue在编译过程中都做了什么处理和优化,然后生成我们使用的代码。

core


core目录包含了Vue.js的核心代码,包括内置组件,全局API封装,Vue实例化,观察者,虚拟DOM,工具函数等等。

sfc


这个目录下的代码逻辑会把.vue文件内容解析成一个JavaScript的对象。

2.先从入口开始:

Vue.js可以通过配置文件对构建配置进行过滤,这样可以构建出不同用途的Vue.js,有兴趣的同学可以通过package.json里的打包命令阅读它其他配置文件,我们主要了解web应用的配置入口src/platforms/web/entry-runtime-with-compiler.js

/* @flow */

import config from 'core/config'
import { warn, cached } from 'core/util/index'
import { mark, measure } from 'core/util/perf'

import Vue from './runtime/index'
import { query } from './util/index'
import { compileToFunctions } from './compiler/index'
import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'

const idToTemplate = cached(id => {
  const el = query(id)
  return el && el.innerHTML
})

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  return mount.call(this, el, hydrating)
}

/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */
function getOuterHTML (el: Element): string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}

Vue.compile = compileToFunctions

export default Vue

当我们代码执行import Vue from 'vue得时候,就是这个入门执行代码来初始化Vue,那么Vue到底是什么,它是怎么初始化的。

Vue的入口


在这个入口JS的上方我们找到Vue的来源:然后一级一级往上查找最后找到真正初始化Vue的地方,在src/core/index.js中:

import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

export default Vue

这里有2处关键的代码, import Vue from './instance/index'initGlobalAPI(Vue),初始化全局Vue API,先看src/core/instance/index.js:

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

在这里我们可以看到Vue实际上就是一个Founction实现的类,我们只能通过new Vue去实例化它。 在这里可以看到很多xxxxMixin的函数调用,它们的功能都是给Vue的prototype上拓展一些方法,在这里Vue是按功能把这些扩展分散到多个模块中去实现的,这种方式的发出是非常方便代码的维护和管理,这种编程技巧是值得我们去学习。

initGlobalAPI

Vue.js在整个初始化过程中,除了在它原型protoitype上扩展方法外,还会为Vue这个对象本身扩展全局的静态方法,它的模块定义在src/global-api/index.js:

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

这里就是Vue上一些全局方法的扩展,Vue官网上的全局API基本在这可以全部找到。