小码相亲记之"typeof"

19 阅读6分钟

这是根据我工作中解决的一个bug让AI生成的文章 (小广告: 我厂大量招工友, 欢迎私聊)

开场:程序员的第一次相亲

周末晚上,程序员小码被同事拉去相亲。

同事拍着胸口说:“放心,这次给你介绍一位美女,代号 meinv。”

小码一听,职业习惯当场发作。别人相亲先问兴趣爱好,他先打开浏览器控制台,准备确认一下这位 meinv 是啥性格。

于是他直接输入:

meinv

控制台像红娘突然摔门一样,立刻甩出一句:

Uncaught ReferenceError: meinv is not defined

翻译成人话就是:

没有meinv这个人, 你是不是来找茬的

小码愣住了:“不是说有美女吗?怎么我直接问 meinv 是啥性格,控制台就急了?”

但程序员不会轻易放弃。他换了个更委婉的方式,不直接点名,而是先打听一下:

typeof meinv === 'function'

控制台这次没有发脾气,只是淡定地回了一个:

false

小码眼睛一亮:“咦?直接喊 meinv 会报错,用 typeof 打听却没事。这个技巧可以啊!”

于是他写了一个相亲辅助函数:

function isFunc(fn) {
  return typeof fn === 'function'
}

然后自信满满地运行这个函数:

isFunc(meinv)

下一秒,控制台又红了:

Uncaught ReferenceError: meinv is not defined

小码当场沉默。

明明函数里也是 typeof fn === 'function',为什么直接写 typeof meinv 不报错,传进 isFunc(meinv) 就翻车?

这场相亲事故背后,其实藏着两个 JavaScript 原理:

  1. typeof 对“未声明变量”有特殊照顾。
  2. 函数调用时,实参会在函数体执行之前先求值。

第一幕:typeof 是相亲角的保安

在 JavaScript 中,如果你直接访问一个从未声明过的变量:

meinv

引擎会沿着当前作用域链一路查找 meinv

就像小码在相亲现场打听美女性格:全局作用域问一遍,局部作用域问一遍,外层作用域再问一遍。

最后发现:没人能回答 meinv 是谁,更别说她是什么性格。

于是 JavaScript 抛出:

ReferenceError: meinv is not defined

这里的重点是:meinv 不是“值为 undefined”,而是“这个变量绑定根本不存在”。

typeof 很特殊。

当你写:

typeof meinv

即使 meinv 从来没声明过,typeof 也不会让控制台爆红,而是返回:

"undefined"

所以这句:

typeof meinv === 'function'

实际比较的是:

"undefined" === "function"

结果当然是:

false

你可以把 typeof 想象成相亲角门口的保安。只要小码不是直接冲进去喊人,而是先问保安:

请问 meinv 是啥性格?

保安会很克制地回答:

性格没打听到,按 undefined 处理。

这就是 typeof meinv 不报错的原因。

第二幕:函数调用不是“先进去再说”

真正让小码翻车的是这句:

isFunc(meinv)

很多人第一眼会以为,meinv 会先进入 isFunc,然后在函数内部被判断:

typeof fn === 'function'

但 JavaScript 的流程不是这样的。

函数调用发生时,引擎会先计算所有实参表达式,然后才会进入函数体。

也就是说:

isFunc(meinv)

大致可以理解成:

const temp = meinv
isFunc(temp)

问题就出在第一步。

meinv 根本没有声明过,所以在 const temp = meinv 这一刻,程序就已经抛出了:

ReferenceError: meinv is not defined

于是小码精心准备的 isFunc 函数,连找到 meinv 的机会都没有。

不是 typeof fn 判断失败了,而是 meinv 在去函数的路上就已经“性格没问到,人也没对上”了。

第三幕:美女没来,和美女来了但没说话,是两回事

这里还有一个非常容易混淆的点:undefined 和“未声明”不是一回事。

比如你提前把这位相亲对象介绍进当前作用域:

let meinv

这表示:meinv 这个名字已经能被找到,只是目前还没拿到具体性格信息。

这时再调用:

isFunc(meinv)

不会报错。

因为它等价于:

isFunc(undefined)

最终返回:

false

这就像红娘说:“人是介绍过来了,但性格资料还没填。”

而从未声明的情况是:

isFunc(meinv)

如果 meinv 这个名字从未出现在任何作用域里,那就不是“性格资料为空”,而是“你连正确的相亲对象都还没找到”。

所以错误是:

ReferenceError: meinv is not defined

一句话区分:

let meinv

表示人已经能被找到,但性格信息是 undefined

而:

meinv

如果从未声明,表示连这个相亲对象的名字都还没有进入作用域。

第四幕:怎么优雅地打听 meinv

如果你只是想确认某个全局函数是否存在,最直接的写法是:

typeof meinv === 'function'

这种写法适合判断一个可能不存在的全局标识符。

在浏览器里,也可以从 window 上打听:

typeof window.meinv === 'function'

对象属性不存在时不会抛 ReferenceError,只会返回 undefined

所以:

window.meinv

如果没有这个属性,结果就是:

undefined

如果你写的是跨环境代码,比如既可能运行在浏览器,也可能运行在 Node.js,可以使用:

typeof globalThis.meinv === 'function'

globalThis 是现代 JavaScript 提供的统一全局对象入口。

第五幕:相亲辅助函数该怎么用?

封装 isFunc 没有问题,问题在于传进去的实参必须能被正常求值。

比如:

function isFunc(fn) {
  return typeof fn === 'function'
}

let meinv

isFunc(meinv) // false

这没问题,因为 meinv 已经声明过。

再比如:

function meinv() {
  console.log('你好,我是一个函数')
}

isFunc(meinv) // true

这也没问题,因为 meinv 真的是一个函数。

但如果你从未声明过 meinv,下面这句就不行:

isFunc(meinv)

因为错误发生在传参阶段,而不是函数内部。

小码以为自己把美女带到了餐厅预定的座位,实际上人还没到门口,JavaScript 引擎就已经说:

还没问清 meinv 是谁,本次相亲取消。

结尾:记住这句相亲箴言

typeof 确实可以安全打听一个未声明变量:

typeof meinv

但它只能保护直接站在它身边的那个标识符。

当你写成:

isFunc(meinv)

meinv 会先作为函数实参被求值。只要它从未声明,程序就会在进入函数前抛出 ReferenceError

所以这场相亲小剧场的核心台词是:

typeof 能帮程序员礼貌打听 meinv 是啥性格,但帮不了已经在传参路上但“人还没对上”的情况。

下次再看到 typeof xxx 不报错,而 someFn(xxx) 报错时,你就知道了:不是 typeof 失灵了,是函数调用前的实参求值先动手了。