formatComponentName 函数

let formatComponentName = (noop: any)

if (process.env.NODE_ENV !== 'production') {
  formatComponentName = (vm, includeFile) => {
    if (vm.$root === vm) {
      return '<Root>'
    }
    const options = typeof vm === 'function' && vm.cid != null
      ? vm.options
      : vm._isVue
        ? vm.$options || vm.constructor.options
        : vm || {}
    let name = options.name || options._componentTag
    const file = options.__file
    if (!name && file) {
      const match = file.match(/([^/\\]+)\.vue$/)
      name = match && match[1]
    }

    return (
      (name ? `<${classify(name)}>` : `<Anonymous>`) +
      (file && includeFile !== false ? ` at ${file}` : '')
    )
  }
}

如果在开发环境中,formatComponentName 函数什么也不做,因为在顶级作用域下,它的值为 noop 函数。

function noop (a?: any, b?: any, c?: any) {}

如果不在开发环境中,变量 formatComponentName 被重置为一个全新的函数,接收 vmincludeFile 作为参数。

Vue 文档里解释 vm.$root 是“当前组件树的根 Vue 实例。如果当前实例没有父实例,值就是它自身”。所以,当 if 语句检测到 vm.$root 严格等于 vm,即检测到 vm 没有父实例,因此就是当前组件树的根 Vue 实例。如果当前 Vue 实例不存在父实例,函数返回 <Root>


为了更好地理解源码,你需要了解这两件事。

第一,Vue 实例上的属性和方法(而不是用户自定义的属性和方法)上添加前缀 $ ,是为了清晰地区别于用户自定义的。因此,vm.$root是一个实例属性。

第二,

一个 Vue 应用由一个通过 new Vue 创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成。所有的 Vue 组件都是 Vue 实例,并且接受相同的选项对象 (一些根实例特有的选项除外)。

-Vue.js API


所以,对于 formatComponentName 函数的目的,首先 if 语句通过判断 vm.$root 这个实例属性是否严格等于所传递的实例,只检查 vm 参数是根 Vue 实例或者一个 Vue 组件。

接着,初始化一个 options 变量,赋值为嵌套的三元运算符的结果:

const options = typeof vm === 'function' && vm.cid != null
  ? vm.options
  : vm._isVue
    ? vm.$options || vm.constructor.options
    : vm || {}

如果 vm 是一个函数,且 vm.cid 不等于 null,options 设置为 vm.options


函数 initExtend 中的注释解释了 cid 属性:“每一个实例的构造函数,包括 Vue ,有一个唯一的 cid。这允许我们创建子构造函数的集合,以实现原型继承,并能够缓存他们”。


如果 vm 不是一个函数或者 vm.cidnull,接下来我们检查 vm._isVue 这个标记是否为 true。你应该还记得,在 Vue.prototype._init 中设置 vm._isVue 为 true。如果成立,变量 options 的值被赋值为 vm.$options 或者 vm.constructor.options。如果 vm._isVue 这个标记为 false,变量 options 被赋值为 vm 或者空对象。

vm._isVue
  ? vm.$options || vm.constructor.options
  : vm || {}

接下来,初始化name 变量,如果 options.name 存在,赋值为 options.name,若不存在,赋值为 options._componentTag。初始化 file 变量,赋值为 options.__file

let name = options.name || options._componentTag
const file = options.__file

然后,我们检查下 name 变量是否为 false,且file 变量是否为 true。如果都成立,调用 match 方法,参数为一个正则表达式,返回值变量赋值为变量 match。这个正则表达式查找以 .vue 结尾的文件名。

正则表达式的括号里,最前面有一个 ^ 符号,表示是一个反向字符集

一个反向字符集。也就是说, 它匹配任何没有包含在方括号中的字符。

因此,match 将会是任何值,除了值为斜杠或反斜杠(用两个反斜杠是因为第一个是用来转义的)。如果 match 值存在,变量 name 被赋值为 match[1]

如果有困惑的话,不妨看下 match 方法的返回值

如果有正则表达式匹配到了某字符,它将会返回数组,这个数组的第一个元素为匹配到字符,其次是符合括号里条件的任何字符。如果什么都没匹配到,返回 null

所以,返回数组的第一个元素是匹配到的整个字符,第二个元素是仅仅是括号的子字符串匹配到的。


现在,我们回到后面的代码里:

return (
  (name ? `<${classify(name)}>` : `<Anonymous>`) +
  (file && includeFile !== false ? ` at ${file}` : '')
)

显然,返回的是一个拼接后的字符串。我们一次性的将连接字符串的每个部分。首先,如果 name 判断为 true,调用 classify 函数(下一章里讲解这个函数),传的参数为 name,拼接尖括号后返回出去。

如果 name 强制转换后不为 true,函数 formatComponentName 返回 '<Anonymous>' 和后面两个选项之一。如果 file 判断为 true,且 includeFiles 不严格等于 false,然后,拼接 ' at ' 和 file。否则,拼接是空字符串。

下一章里,我们将简单看一下 classify 函数。

上次更新: 2/15/2019, 3:19:52 AM