这是根据我工作中解决的一个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 原理:
typeof对“未声明变量”有特殊照顾。- 函数调用时,实参会在函数体执行之前先求值。
第一幕: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 失灵了,是函数调用前的实参求值先动手了。