我为 CRM 实现了一项新功能,在 Safari (macOS) 上一切正常,但在我们测试的所有其他浏览器(Chrome、Firefox、Edge)上都会抛出
Uncaught TypeError: X is not a function
。这是罪魁祸首的代码片段:
if (window.changeLabel === 'undefined') {
function changeLabel() {
// Do something
}
changeLabel();
} else {
changeLabel();
}
为什么它只能在 Safari 上运行?为什么即使我检查了
changeLabel
是否存在,它也不是一个函数?这不是检查函数是否存在的方法吗?
window.changeLabel === 'undefined'
都会返回 false
,因为您将属性值与字符串 'undefined'
进行比较,而不是与值 undefined
进行比较。
在 Safari 的草率模式下,以下内容会将函数声明“泄漏”到声明的块之外。
if(false) { function foo() {} }
console.log(foo) // function foo() {} in Safari, undefined in Chrome-based browsers
Chrome 的草率模式由于对规范(Web 兼容性附件 IIRC)的不同解释而表现不同,这意味着该功能不会“泄漏”,并且仍然未声明。
【更多详情见文末👇】
因此,在 Safari 中,您的代码会意外地泄漏函数声明,使其在块之外可见。
要做两件事:
typeof
用于检查未声明的标识符的预期目的if (typeof changeLabel !== 'function') {
window.changeLabel = function() {
// Do something.
}
}
// Now you can always use changeLabel...
更多详情
造成所有这些混乱的原因是,在 JavaScript 的早期版本中,块内函数声明的范围从未真正正确指定(当时它只是一种函数范围的语言),因此浏览器供应商做了自己不同的事情。当 TC-39 最终决定在 ES2015 中标准化此行为时,“不要破坏 Web”要求意味着这种边缘情况的草率模式规范最终变得极其复杂且违反直觉,导致两种不同的实现(只有一种)其中正确的是)。
这不是您通常注册 Polyfill 的方式。通常,你会这样做:
if (window.changeLabel === 'undefined') {
window.changeLabel = function() {
// Do something
}
}
changeLabel();
注意我们在那里不需要
else
块。
您可以探测
window.changeLabel
是否存在,如果在一行中不存在则分配它,例如:
window.changeLabel = window.changeLabel ||
function() { console.log(`changeLabel here`); }
changeLabel();