Safari iOS ReferenceError: Can't find variable: xxx

掘金没有故记录之。

问题

以下代码有什么问题(注意在没有打包工具的环境下)?

js 复制代码
{
  const bar1 = 1

  function foo1() {
    console.log('bar1', bar1)
  }

  foo1();
}

答案:点击展开 Safari 或 iOS 移动端将报错『ReferenceError: Can't find variable: bar1』。其他浏览器正常。

为什么报错?

如果是下面这样倒还能理解,但是我们已经将 bar1 的定义放到了函数调用甚至申明之前为什么还是报错?

js 复制代码
{
  function foo1() {
    console.log('bar1', bar1);
  }

  foo1();

  const bar1 = 1;
}

正式解答: Safari 特有的兼容性问题,非严格模式下,各个浏览器实现各自不同,因为标准并没有规定(different browsers implemented it differently in non-strict mode)。

Safari 实现:

大括号生成块级作用域,bar1 变成局部变量,而在非严格模式下函数 foo1 是全局函数,故执行时找不到局部变量 bar1。相当于将函数 foo1 挪到了顶部。等价于:

js 复制代码
function foo2() {
  console.log('bar2', bar2);
}

{
  const bar2 = 1;

  foo2();
}

自然报错。

Chrome 实现

等价于:

js 复制代码
var foo1; // 意味着 globalThis.foo1 = undefined;

{
  const bar1 = 1

  foo1 = function () {
    console.log('bar1', bar1)
  }
  
  // 意味着 globalThis.foo1 = foo1

  foo1();
}

你可以尝试在 Safari、Chrome、Node.js 运行下面代码,验证我们的猜想是否正确:

js 复制代码
console.log('above scope foo1:', foo1, typeof foo1, 'foo1' in globalThis);

{
  const bar1 = 1

  function foo1() {
    console.log('bar1', bar1)
  }

  foo1();
}

console.log('below scope foo1:', foo1, typeof foo1, 'foo1' in globalThis);

Safari

javascript 复制代码
above scope foo1: 
function foo1() { console.log('bar1', bar1); }
"function"
true

[Error] ReferenceError: Can't find variable: bar1

Chrome

javascript 复制代码
above scope foo1: undefined undefined true
bar1 1
below scope foo1: ƒ foo1() {
    console.log('bar1', bar1);
  } function true
bar1 1

Node.js

❯ node reference-error-ios.js

javascript 复制代码
above scope foo1: undefined undefined false
bar1 1
below scope foo1: [Function: foo1] function false
bar1 1

但是有个怪异的地方,Node.js 和 Chrome 应该要一样,但是实际行为有少许不同:

'foo1' in globalThis Chrome 是 true,Node.js 是 false(各位有知道答案的吗?)改成 foo1 in globalThis 倒是变成 true 了,但是不对呀 in 的操作符应该是字符串。

验证字符串才是正确的 Chrome 验证:

如何解决

既然是作用域导致的报错,可以从作用域解决。

方法 1:去除大括号。让报错的变量也变成全局作用域

javascript 复制代码
const bar1 = 1

function foo1() {
console.log('bar1', bar1)
}

foo1();

但显然我们本意是利用 {} 大括号的块级作用域让变量不泄露到全局,故该方案并不是最佳的。

方法 2:将 const bar1 = 1 挪到括号外面的顶部。

和方法 1 同理,但好在保留了其他变量不被泄露,也不是最佳解决方案。

方法 3:使用 var

和方法 2 同理。

方法 4:IIFE

利用 IIFE 构造一个函数作用域,能保证变量不泄露。

js 复制代码
(function () {
  const bar1 = 1;

  function foo1() {
    console.log('bar1', bar1);
  }

  foo1();
})();

在 ES6 之前我们的最佳实践确实如此,但写起来太费劲了。

方法 5:use strict

开启严格模式

js 复制代码
'use strict';

{
  const bar1 = 1;

  function foo1() {
    console.log('bar1', bar1);
  }

  foo1();
}

还有一个问题,开启严格模式,是否可以把最外层的 {} 去掉?

答案:不能。如果去除,如果有同名的 bar1 则报错 cannot re-declare bar1

总结

严格模式 + {} 块级作用域才是最佳实践。

最后一个问题,为什么日常工作中,我们即使不写 use strict 也不会遇到这种问题?因为我们有打包工具(webpack)会自动在我们的代码顶部增加 use strict 指令,以及将编译后的代码用 IIFE 打包成一个个 module(即模块化)。

相关推荐
wvy1 小时前
Xcode 26还没有适配SceneDelegate的app建议尽早适配
ios
游戏开发爱好者82 小时前
苹果 App 上架流程,结合 Xcode、CI 等常见工具
macos·ios·ci/cd·小程序·uni-app·iphone·xcode
前端老白2 小时前
webview在微信小程序中,安卓加载失败,IOS正常加载
android·ios·微信小程序·webview
2501_915106322 小时前
用 HBuilder 上架 iOS 应用时如何管理Bundle ID、证书与描述文件
android·ios·小程序·https·uni-app·iphone·webview
2501_915909062 小时前
资源文件混淆在 iOS 应用安全中的实际价值
android·安全·ios·小程序·uni-app·iphone·webview
2501_915918412 小时前
iOS App 性能测试中常被忽略的运行期问题
android·ios·小程序·https·uni-app·iphone·webview
Name_NaN_None3 小时前
iPhone怎么投屏到电脑上?
ios·电脑·iphone
sweet丶15 小时前
UIWindowScene 使用指南:掌握 iOS 多窗口架构
ios
崽崽长肉肉20 小时前
swift中的知识总结(一)
ios·swift
2501_915106321 天前
HTTP 协议详解,HTTP 协议在真实运行环境中的表现差异
网络·网络协议·http·ios·小程序·uni-app·iphone