generateComponentTrace 函数(1)
上一篇中已介绍,如果一个 Vue 实例传递给了 warn 函数,就会调用 generateComponentTrace 函数来设置变量 trace 。上一篇里,我们主要中心放在了 warn 函数的具体实现上,忽略了 generateComponentTrace 函数。这次,我们将回到 generateComponentTrace 函数中:
generateComponentTrace = vm => {
if (vm._isVue && vm.$parent) {
const tree = []
let currentRecursiveSequence = 0
while (vm) {
if (tree.length > 0) {
const last = tree[tree.length - 1]
if (last.constructor === vm.constructor) {
currentRecursiveSequence++
vm = vm.$parent
continue
} else if (currentRecursiveSequence > 0) {
tree[tree.length - 1] = [last, currentRecursiveSequence]
currentRecursiveSequence = 0
}
}
tree.push(vm)
vm = vm.$parent
}
return '\n\nfound in\n\n' + tree
.map((vm, i) => `${
i === 0 ? '---> ' : repeat(' ', 5 + i * 2)
}${
Array.isArray(vm)
? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)`
: formatComponentName(vm)
}`)
.join('\n')
} else {
return `\n\n(found in ${formatComponentName(vm)})`
}
}
默认情况下,generateComponentTrace 函数是一个没有任何作用的函数:
function noop (a?: any, b?: any, c?: any) {}
// [. . . .]
let generateComponentTrace = (noop: any) // work around flow check
但是,如果不是在生产环境中的话,generateComponentTrace 函数将会赋予其他作用:
if (process.env.NODE_ENV !== 'production') {
// [. . . .]
generateComponentTrace = function (vm) {
// [. . . .]
}
}
这个函数接收一个参数 vm 。然后检查 vm 是不是一个 Vue 实例。你应该还记得在 Vue.prototype._init 中,属性 ._isVue 被设置为 true :
function initMixin (Vue) {
Vue.prototype._init = function (options) {
// [. . . .]
// a flag to avoid this being observed
vm._isVue = true
// [. . . .]
}
}
然后检查 vm.$parent 经过强制类型转换后是否为 true。因为这是我们第一次介绍 vm.$parent,那就让我们好好熟悉下它。Vue.js API 对我们很有帮助,API 里解释了: vm.$parent 是 Vue 实例的一个属性,表示“父实例,如果当前实例有的话。”当父实例作为属性时,API 中解释为:
在两个组件之间建立父子关系。子组件可以通过
vm.$parent来访问父组件,子组件将被追加到父组件的$children数组里。
我们将会在讨论 initLifecycle 函数时,深入探讨下父子组件关系,以及如果通过 $parent 访问父组件。这里我们只需要知道这个语句检查了 Vue 实例是否存在一个 $parent 属性。
如果以上条件都满足了,generateComponentTrace 函数会先初始化一个变量 tree,并赋值为一个空数组,然后定义一个 currentRecursiveSequence 变量,设置为0。
while 循环看起来有点奇怪?这个循环怎么停止呢?vm 如何变为其他值,而不再是 vm?答案在循环的结尾处。每一次循环结尾,Vue 实例被放到 tree 数组里,vm 设置为当前实例的父实例。
while (vm) {
// [ . . . .]
tree.push(vm)
vm = vm.$parent
}
换句话说,这个循环不断递归 Vue 实例的 .$parent 属性,直到某一实例不存在 .$parent 属性时。
所以,我们继续看循环的内部吧。首先,检查 tree 数组的长度是否大于0,如果大于0,设置变量 last 为数组的最后一个元素。因为在 Javascript 中,数组是零基础(zero based)的——即数组的索引从0开始——一个数组的长度总是比数组最后一项的索引大1。因此你可以通过数组长度减1的索引拿到数组的最后一项:
const last = tree[tree.length - 1]
接下来,检查下 last.constructor 是否严格等于 vm.constructor。如果是,currentRecursiveSequence 自增一次,且 Vue 实例被赋值为它的父实例,continue 语句“终止循环中的当前迭代,继续执行循环的下一次迭代”。
currentRecursiveSequence++
vm = vm.$parent
continue
换句话说,在 continue 之后,下面语句不会执行:
tree.push(vm)
vm = vm.$parent
如果 last.constructor严格等于 vm.constructor 不成立,且 currentRecursiveSequence 大于0,tree 的最后一项设置为一个带有两个元素的数组:last 和 currentRecursiveSequence,currentRecursiveSequence 重置为0:
} else if (currentRecursiveSequence > 0) {
tree[tree.length - 1] = [last, currentRecursiveSequence]
currentRecursiveSequence = 0
}
最后,Vue 实例被追加到 tree 中——正如前面提过的——Vue 实例重赋值为 Vue 实例的属性 .$parent 的值:
tree.push(vm)
vm = vm.$parent
这个循环结束时,是在某一实例不存在 .$parent 属性时。
接下来,将是一个冗长的返回语句,我们将在下一章里讲解。