Vue源码阅读——框架分析

随着Vue框架状态越来越火热,不少feder已经不仅仅满足于vue框架的使用,有很多人都打算阅读vue源码来提升自己。那么提到源码阅读就不免产生了一个问题,到底要从何处开始源码阅读呢?

package的入口:

// package.json
"main": "dist/vue.runtime.common.js"

经过全局搜索文件名找到入口源码的打包配置在scripts/config.js

// scripts/config.js

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

即项目入口为:

// scripts/config.js
resolve('web/entry-runtime.js')

可以看到这个地址并非是直接目录,同文件内找到解析函数resolve:

// scripts/config.js
const aliases = require('./alias')
const resolve = p => {
     
  const base = p.split('/')[0]
  if (aliases[base]) {
     
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
     
    return path.resolve(__dirname, '../', p)
  }
}

可以看到这个地址解析函数引入了别名文件,并且取’/‘之前的作为别名检查存在性,如果不存在则整体进行解析,可以看到入口文件的’/'之前是web,打开别名文件./alias

// scripts/alias.js
module.exports = {
     
  vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
  compiler: resolve('src/compiler'),
  core: resolve('src/core'),
  shared: resolve('src/shared'),
  web: resolve('src/platforms/web'),
  weex: resolve('src/platforms/weex'),
  server: resolve('src/server'),
  sfc: resolve('src/sfc')
}

可以看到web这个别名是存在的,因此入口地址

resolve('web/entry-runtime.js')

将被解析为:

src/platforms/web/entry-runtime.js

打开该文件:

// src/platforms/web/entry-runtime.js
import Vue from './runtime/index'
export default Vue

即入口文件在此处的操作是引入./runtime/inde并暴露出去,因此核心文件的解析将从src/platforms/web/runtime/index.js开始

// src/platforms/web/runtime/index.js

/* @flow */
import Vue from 'core/index'
import config from 'core/config'
import {
      extend, noop } from 'shared/util'
import {
      mountComponent } from 'core/instance/lifecycle'
import {
      devtools, inBrowser } from 'core/util/index'

import {
     
  query,
  mustUseProp,
  isReservedTag,
  isReservedAttr,
  getTagNamespace,
  isUnknownElement
} from 'web/util/index'

import {
      patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'

// install platform specific utils
// 标签及属性使用限制
Vue.config.mustUseProp = mustUseProp
// 判定是否为html或svg标签
Vue.config.isReservedTag = isReservedTag
// 判定是否为style、class
Vue.config.isReservedAttr = isReservedAttr
// 获取tag的命名空间
Vue.config.getTagNamespace = getTagNamespace
// 判定标签是否为未定义标签
Vue.config.isUnknownElement = isUnknownElement

// install platform runtime directives & components
// 给vue原型链上进行方法挂载
// 将platformDirectives对象可枚举属性拷贝给Vue.options.directives
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
     
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
     
  setTimeout(() => {
     
    // 开发者工具init事件触发及提示
    if (config.devtools) {
     
      if (devtools) {
     
        devtools.emit('init', Vue)
      } else if (
        process.env.NODE_ENV !== 'production' &&
        process.env.NODE_ENV !== 'test'
      ) {
     
        console[console.info ? 'info' : 'log'](
          'Download the Vue Devtools extension for a better development experience:\n' +
          'https://github.com/vuejs/vue-devtools'
        )
      }
    }
    if (process.env.NODE_ENV !== 'production' &&
      process.env.NODE_ENV !== 'test' &&
      config.productionTip !== false &&
      typeof console !== 'undefined'
    ) {
     
      console[console.info ? 'info' : 'log'](
        `You are running Vue in development mode.\n` +
        `Make sure to turn on production mode when deploying for production.\n` +
        `See more tips at https://vuejs.org/guide/deployment.html`
      )
    }
  }, 0)
}

export default Vue

可以看到该入口文件只干了两件事

  • 启动提示
  • 给Vue上挂载东西

重点进入了下一个阶段,到底给Vue上挂载了啥
那先来看看引入的Vue是个啥和上面本来有啥。

import Vue from 'core/index'

根据引入语句和别名解析核心文件应当在src/core/index

// src/core/index

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'

// vue全局属性的挂载及初始化
initGlobalAPI(Vue)

// 判定是否为服务端渲染
Object.defineProperty(Vue.prototype, '$isServer', {
     
  get: isServerRendering
})

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

// expose FunctionalRenderContext for ssr runtime helper installation
// 暴露FunctionalRenderContext给ssr运行时
Object.defineProperty(Vue, 'FunctionalRenderContext', {
     
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

export default Vue

可以看到这个文件依然是引入Vue对象然后对Vue对象进行初始化包括原型链的方法挂载,那么我们先继续追查这个Vue对象给是如何定义初始化的。跟随定义可知目录为 src/core/instance/index

// src/core/instance/index

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

function Vue (options) {
     
  // 实例检测如果不是Vue实例 
  // 且不在生产环境下进行警告
  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)
}

// 向Vue原型链挂载_init方法
initMixin(Vue)
// 将_data及_props、$watch挂载到vue原型对象当中
stateMixin(Vue)
// 挂载$on事件绑定
eventsMixin(Vue)
// 向Vue原型对象上挂载_update、$forceUpdate、$destroy方法
// $forceUpdate 迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件
lifecycleMixin(Vue)
// 向Vue原型对象上挂载_render和$nextTick方法
renderMixin(Vue)

export default Vue


可以看到,在这里首先是有个构造函数,然后在构造函数的原型链上添加方法及属性,包括实例基础属性的初始化、数据系统、时间系统、声明周期系统、渲染系统等。下一个部分来看看初始化的部分都做了什么。进入src/core/instance/init.js来看看。

可以看到在实例初始化的过程当中,在vue的原型链当中挂载了_init方法,该方法当中进行了uid递增,
performance标记标记,_isVue赋值,
判定实例化是否传入组件化参数,是则进行内部组件初始化,否则进行传入参数覆盖式合并构造器参数的数据。
在非生产环境对实例进行代理,在生产环境则将实例自身赋值给_renderProxy
将实例赋值给_self属性,并进行一系列初始化操作
非生产环境下存在性能属性则进行监测
实例的$options对象存在el属性则挂载

// src/core/instance/init.js

/* @flow */

import config from '../config'
import {
      initProxy } from './proxy'
import {
      initState } from './state'
import {
      initRender } from './render'
import {
      initEvents } from './events'
import {
      mark, measure } from '../util/perf'
import {
      initLifecycle, callHook } from './lifecycle'
import {
      initProvide, initInjections } from './inject'
import {
      extend, mergeOptions, formatComponentName } from '../util/index'

let uid = 0

export function initMixin (Vue: Class<Component>) {
     
  Vue.prototype._init = function (options?: Object) {
     
    // 将当前实例复制给vm
    const vm: Component = this
    // a uid
    // 为vm赋值递增的uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    // 生产环境下,且开启performance、并支持mark时,进行标记
    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 {
     
      // 否则将自身赋值给_renderProxy属性
      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)
    }
  }
}

export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
     
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) {
     
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

export function resolveConstructorOptions (Ctor: Class<Component>) {
     
  let options = Ctor.options
  if (Ctor.super) {
     
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
     
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
     
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
     
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
     
  let modified
  const latest = Ctor.options
  const sealed = Ctor.sealedOptions
  for (const key in latest) {
     
    if (latest[key] !== sealed[key]) {
     
      if (!modified) modified = {
     }
      modified[key] = latest[key]
    }
  }
  return modified
}

关于参数与构造器参数的合并过程:

进行子操作项的序列化过程(将props,inject,directive等可以进行简化参数传递的进行标准序列化)
如果子选项存在混入或者拓展,则将其混入和拓展的部分继续与其父操作项进行合并,
直到子项为基本数据项
对父操作项与子操作项进行数据域合并后,用子操作项覆盖式合并父操作项

// src/core/util/options.js

/* @flow */

import config from '../config'
import { warn } from './debug'
import { set } from '../observer/index'
import { unicodeRegExp } from './lang'
import { nativeWatch, hasSymbol } from './env'

import {
  ASSET_TYPES,
  LIFECYCLE_HOOKS
} from 'shared/constants'

import {
  extend,
  hasOwn,
  camelize,
  toRawType,
  capitalize,
  isBuiltInTag,
  isPlainObject
} from 'shared/util'

/**
 * Option overwriting strategies are functions that handle
 * how to merge a parent option value and a child option
 * value into the final value.
 */
const strats = config.optionMergeStrategies

/**
 * Options with restrictions
 */
if (process.env.NODE_ENV !== 'production') {
  strats.el = strats.propsData = function (parent, child, vm, key) {
    if (!vm) {
      warn(
        `option "${key}" can only be used during instance ` +
        'creation with the `new` keyword.'
      )
    }
    return defaultStrat(parent, child)
  }
}

/**
 * Helper that recursively merges two data objects together.
 */
function mergeData (to: Object, from: ?Object): Object {
  if (!from) return to
  let key, toVal, fromVal

  const keys = hasSymbol
    ? Reflect.ownKeys(from)
    : Object.keys(from)

  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    // in case the object is already observed...
    if (key === '__ob__') continue
    toVal = to[key]
    fromVal = from[key]
    if (!hasOwn(to, key)) {
      set(to, key, fromVal)
    } else if (
      toVal !== fromVal &&
      isPlainObject(toVal) &&
      isPlainObject(fromVal)
    ) {
      mergeData(toVal, fromVal)
    }
  }
  return to
}

/**
 * Data
 */
export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    // in a Vue.extend merge, both should be functions
    if (!childVal) {
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }
    // when parentVal & childVal are both present,
    // we need to return a function that returns the
    // merged result of both functions... no need to
    // check if parentVal is a function here because
    // it has to be a function to pass previous merges.
    return function mergedDataFn () {
      // 子选项覆盖式合并其父选项
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this, this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      )
    }
  } else {
    return function mergedInstanceDataFn () {
      // instance merge
      const instanceData = typeof childVal === 'function'
        ? childVal.call(vm, vm)
        : childVal
      const defaultData = typeof parentVal === 'function'
        ? parentVal.call(vm, vm)
        : parentVal
      if (instanceData) {
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }
  }
}

strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {
      process.env.NODE_ENV !== 'production' && warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.',
        vm
      )

      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }

  return mergeDataOrFn(parentVal, childVal, vm)
}

/**
 * Hooks and props are merged as arrays.
 */
function mergeHook (
  parentVal: ?Array,
  childVal: ?Function | ?Array
): ?Array {
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
  return res
    ? dedupeHooks(res)
    : res
}

function dedupeHooks (hooks) {
  const res = []
  for (let i = 0; i < hooks.length; i++) {
    if (res.indexOf(hooks[i]) === -1) {
      res.push(hooks[i])
    }
  }
  return res
}

LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

/**
 * Assets
 *
 * When a vm is present (instance creation), we need to do
 * a three-way merge between constructor options, instance
 * options and parent options.
 */
function mergeAssets (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): Object {
  const res = Object.create(parentVal || null)
  if (childVal) {
    // 非生产环境且childVal类型为对象
    process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
    return extend(res, childVal)
  } else {
    return res
  }
}

ASSET_TYPES.forEach(function (type) {
  strats[type + 's'] = mergeAssets
})

/**
 * Watchers.
 *
 * Watchers hashes should not overwrite one
 * another, so we merge them as arrays.
 */

 // 监测合并
strats.watch = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  // work around Firefox's Object.prototype.watch...
  if (parentVal === nativeWatch) parentVal = undefined
  if (childVal === nativeWatch) childVal = undefined
  /* istanbul ignore if */
  if (!childVal) return Object.create(parentVal || null)
  if (process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = {}
  extend(ret, parentVal)
  for (const key in childVal) {
    let parent = ret[key]
    const child = childVal[key]
    // 父参数存在且其不为数组,则将其赋值为数组
    if (parent && !Array.isArray(parent)) {
      parent = [parent]
    }
    // 父参数存在则将其子参数进行合并,否则返回子参数的数组形式
    ret[key] = parent
      ? parent.concat(child)
      : Array.isArray(child) ? child : [child]
  }
  return ret
}

/**
 * Other object hashes.
 */
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  // 不在生产环境时对子选项的类型进行断言,不为对象时抛出警告
  if (childVal && process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  // 父选项不存在时返回子选项
  if (!parentVal) return childVal
  const ret = Object.create(null)
  extend(ret, parentVal)
  if (childVal) extend(ret, childVal)
  // 用子选项覆盖式拓展父选项
  return ret
}
strats.provide = mergeDataOrFn

/**
 * Default strategy.
 */
// 子项存在则返回否则返回父项
const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}

/**
 * Validate component names
 */
function checkComponents (options: Object) {
  for (const key in options.components) {
    validateComponentName(key)
  }
}

export function validateComponentName (name: string) {
  if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
    warn(
      'Invalid component name: "' + name + '". Component names ' +
      'should conform to valid custom element name in html5 specification.'
    )
  }
  if (isBuiltInTag(name) || config.isReservedTag(name)) {
    warn(
      'Do not use built-in or reserved HTML elements as component ' +
      'id: ' + name
    )
  }
}

/**
 * Ensure all props option syntax are normalized into the
 * Object-based format.
 */
function normalizeProps (options: Object, vm: ?Component) {
  const props = options.props
  if (!props) return
  const res = {}
  let i, val, name
  if (Array.isArray(props)) {
    i = props.length
    while (i--) {
      val = props[i]
      if (typeof val === 'string') {
        name = camelize(val)
        res[name] = { type: null }
      } else if (process.env.NODE_ENV !== 'production') {
        warn('props must be strings when using array syntax.')
      }
    }
  } else if (isPlainObject(props)) {
    for (const key in props) {
      val = props[key]
      name = camelize(key)
      res[name] = isPlainObject(val)
        ? val
        : { type: val }
    }
  } else if (process.env.NODE_ENV !== 'production') {
    warn(
      `Invalid value for option "props": expected an Array or an Object, ` +
      `but got ${toRawType(props)}.`,
      vm
    )
  }
  options.props = res
}

/**
 * Normalize all injections into Object-based format
 */
function normalizeInject (options: Object, vm: ?Component) {
  const inject = options.inject
  if (!inject) return
  const normalized = options.inject = {}
  // 如果是数组则解析为对象形式
  if (Array.isArray(inject)) {
    for (let i = 0; i < inject.length; i++) {
      normalized[inject[i]] = { from: inject[i] }
    }
  } else if (isPlainObject(inject)) {
    // 如果inject本身为对象,则进行遍历解析
    for (const key in inject) {
      const val = inject[key]
      normalized[key] = isPlainObject(val)
        ? extend({ from: key }, val)
        : { from: val }
    }
  } else if (process.env.NODE_ENV !== 'production') {
    warn(
      `Invalid value for option "inject": expected an Array or an Object, ` +
      `but got ${toRawType(inject)}.`,
      vm
    )
  }
}

/**
 * Normalize raw function directives into object format.
 */
function normalizeDirectives (options: Object) {
  const dirs = options.directives
  if (dirs) {
    for (const key in dirs) {
      const def = dirs[key]
      // 如果键值为function类型,则将该函数同时绑定给bind和update两个钩子函数
      if (typeof def === 'function') {
        dirs[key] = { bind: def, update: def }
      }
    }
  }
}

function assertObjectType (name: string, value: any, vm: ?Component) {
  if (!isPlainObject(value)) {
    warn(
      `Invalid value for option "${name}": expected an Object, ` +
      `but got ${toRawType(value)}.`,
      vm
    )
  }
}

/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  // 非生产环境进行组件检测
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }
  // 子组件为函数,则将其赋值为其options
  if (typeof child === 'function') {
    child = child.options
  }
  
  // 将props序列化为对象形式,进行赋值解析
  normalizeProps(child, vm)
  // inject序列化
  normalizeInject(child, vm)
  // directive序列化,解析其bind和upload的简写形式
  normalizeDirectives(child)

  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  if (!child._base) {
    // 如果子项_base为false且存在extends,则遍历解析父项
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    // 如果不为基础项,且存在混入,则遍历后递归解析父项
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }


  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    // 数据域合并并赋值
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  // 返回合并后数据
  return options
}

/**
 * Resolve an asset.
 * This function is used because child instances need access
 * to assets defined in its ancestor chain.
 */
export function resolveAsset (
  options: Object,
  type: string,
  id: string,
  warnMissing?: boolean
): any {
  /* istanbul ignore if */
  if (typeof id !== 'string') {
    return
  }
  const assets = options[type]
  // check local registration variations first
  if (hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitalize(camelizedId)
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  // fallback to prototype chain
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    warn(
      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
      options
    )
  }
  return res
}

开发环境进行实例对象的代理,会对不合理的数据调用进行错误抛出,具体过程如下:

如果存在内置对象Proxy,则对实例对象进行代理,否则返回实例对象
如果参数中存在render,则进行取值拦截,否则进行遍历拦截
遍历代理的句柄为:如果值存在于目标对象上且不为保留属性,则返回,否则根据情况报错
取值代理的句柄为:如果属性存在于 d a t a 中 则 抛 出 保 留 前 缀 错 误 , 取 不 到 且 不 存 在 data中则抛出保留前缀错误,取不到且不存在 datadata中则进行不存在报错,可以取值时直接返回

// src/core/instance/proxy.js

/* not type checking this file because flow doesn't play well with Proxy */

import config from 'core/config'
import {
      warn, makeMap, isNative } from '../util/index'

let initProxy

if (process.env.NODE_ENV !== 'production') {
     
  const allowedGlobals = makeMap(
    'Infinity,undefined,NaN,isFinite,isNaN,' +
    'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
    'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
    'require' // for Webpack/Browserify
  )

  const warnNonPresent = (target, key) => {
     
    warn(
      `Property or method "${
       key}" is not defined on the instance but ` +
      'referenced during render. Make sure that this property is reactive, ' +
      'either in the data option, or for class-based components, by ' +
      'initializing the property. ' +
      'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',
      target
    )
  }

  const warnReservedPrefix = (target, key) => {
     
    warn(
      `Property "${
       key}" must be accessed with "$data.${
       key}" because ` +
      'properties starting with "$" or "_" are not proxied in the Vue instance to ' +
      'prevent conflicts with Vue internals' +
      'See: https://vuejs.org/v2/api/#data',
      target
    )
  }

  const hasProxy =
    typeof Proxy !== 'undefined' && isNative(Proxy)

  if (hasProxy) {
     
    // 将字符串中的数据转为数组
    const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact')
    config.keyCodes = new Proxy(config.keyCodes, {
     
      // 赋值拦截
      set (target, key, value) {
     
        if (isBuiltInModifier(key)) {
     
          // 如果赋值的属性在isBuiltInModifier中则进行报错
          warn(`Avoid overwriting built-in modifier in config.keyCodes: .${
       key}`)
          return false
        } else {
     

          // 成功赋值
          target[key] = value
          return true
        }
      }
    })
  }

  const hasHandler = {
     
    // 遍历拦截
    has (target, key) {
     
      // 判断key是否存在于目标当中
      const has = key in target
      // 如果为保留的属性 或者为开头为_且目标$data中不存在的属性
      const isAllowed = allowedGlobals(key) ||
        (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
      // 既不在目标对象及其原型链上,也不是被允许的key
      if (!has && !isAllowed) {
     
        // 如果key存在于目标$data, 抛出保留前缀警告
        if (key in target.$data) warnReservedPrefix(target, key)
        // 抛出非存在性警告
        else warnNonPresent(target, key)
      }
      // 存在且不为保留属性的可被遍历
      return has || !isAllowed
    }
  }

  const getHandler = {
     
    // 取值拦截
    get (target, key) {
     
      // 如果数据key为字符串,且在目标中无法取得
      if (typeof key === 'string' && !(key in target)) {
     
        // 如果key存在于目标$data, 抛出保留前缀警告
        if (key in target.$data) warnReservedPrefix(target, key)
        // 抛出非存在性警告
        else warnNonPresent(target, key)
      }
      // 取出数据
      return target[key]
    }
  }

  initProxy = function initProxy (vm) {
     
    // Proxy为内置对象
    if (hasProxy) {
     
      // determine which proxy handler to use
      const options = vm.$options
      // render且render的_withStripped属性存在 则拦截句柄为取值拦截,否则为遍历拦截
      const handlers = options.render && options.render._withStripped
        ? getHandler
        : hasHandler
      // 对vm进行代理
      vm._renderProxy = new Proxy(vm, handlers)
    } else {
     
      // proxy不存在则将_renderProxy属性设置为其vm实例自身
      vm._renderProxy = vm
    }
  }
}

export {
      initProxy }

对以上内容做一总结:

Vue源码阅读——框架分析_第1张图片

你可能感兴趣的:(vue,vue)