ES6标准下在if中进行函数声明

news2024/11/30 1:34:01

ES5中规定,函数只能在顶层作用域或函数作用域之中声明,不能在块级作用域声明。

// 情况一
if (true) {
  function f() {}
}

// 情况二
try {
  function f() {}
} catch(e) {
  // ...
}

上面两种函数声明,根据 ES5 的规定都是非法的。但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。

ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();
}());

上面代码在 ES5 中运行,会得到“I am inside!”,因为在if内声明的函数f会被提升到函数头部,实际运行的代码如下。

// ES5 环境
function f() { console.log('I am outside!'); }

(function () {
  function f() { console.log('I am inside!'); }
  if (false) {
  }
  f();
}());

ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于let,对作用域之外没有影响。

但是,如果你真的在 ES6 浏览器中运行一下上面的代码,是会报错的,这是为什么呢?

// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function

原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在附录B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。

  1. 允许在块级作用域内声明函数。
  2. 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  3. 同时,函数声明还会提升到所在的块级作用域的头部。

根据这三条规则,浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。上面的例子实际运行的代码如下。

// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
  var f = undefined;
  if (false) {
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function

再看一个例子

console.log("第一次输出a: ", a) //输出 本地a
if (true) {
    // 这里js隐式的把function a的定义放到这里来了,此刻这里有一个 块a
    a = 1 // 将 块a的值 由函数修改为1
    console.log("第二次输出a: ",a) // 此时输出的是 块a的值
    function a() {} // 执行到function a的声明处,此时会将if块中的a同步到外部去,也就是把块a赋值给了本地a, 本地a此刻从undefined被修改为了1
    a = 2 // 此处修改的是 块a, 块a的值由1修改为2
    console.log("第三次输出a: ", a) // 输出块a的值
}
console.log("第四次输出a: ",a) // if执行完毕,没有块了,此时输出的是本地a的值

在if语句处打一个断点。当程序执行到if语句时(linenumber 2),本地a的定义是undefined,且不存在块,说明2成立,函数声明a被提升到全局作用域。

当语句执行进入到if时(linenumber4),此时就会产生块,debug停留在a=1;这句时(此时赋值还未进行),块a已经变为了function,3成立,if块中的函数声明被提升到了所在块的顶部。但此时本地a还是undefined:

当debug停留在a=2;这句时(linenumber 7,赋值还未进行),此时块中执行了function a的函数声明,该声明被同步提升到了块外,影响了本地a,本地a此时从undefined变为了1,这也是2的证实,执行到具体的“声明”语句时,全局就有了值,这与ES6中使用var声明变量时的效果是一样的。

当debug执行到if块的最后一句console.log时(linenumber8),打印的是块中a的内容(就近原则,或者说块a遮蔽了本地a),所以输出的结果是2。

 当debug执行到最后一句console.log时(linenumber 10),此时if语句已经执行完毕,块没有了,所以打印的只能是本地a的内容,本地a的内容此时的值是1

===========================华丽的分割线====================================

块内的函数声明会有一次全局性提升(例如例子中的function a),在作用域顶部声明了一个与函数同名的变量(也就是vscode调试中的本地变量a),初始值是undefined。进入块之后,函数声明又会被提升到块的顶部(也就是块变量a,初始值是function)。此时本地一个变量a(值是undefined),块内一个变量(值是function)。然后块内代码继续执行,每次执行到块内的函数声明那句话时,就会进行一次块内变量为本地同名变量的赋值,就好像块内与本地同名变量的同步。

//本地变量a,初始值是undefined
console.log(`1 ${window.a},${a}`);//undefined,undefined
{  //块变量a,初始值是function a{}
   console.log(`2 ${window.a},${a}`);//undefined,function a(){}
   function a(){} // 同步
   console.log(`3 ${window.a},${a}`);//function a(){},function a(){}
   a = 42;//为块变量a赋值
   console.log(`4 ${window.a},${a}`);//function a(){},42
   function a(){}//同步
   console.log(`5 ${window.a},${a}`);//42,42
}//块变量a消失,此时仅有本地变量a
console.log(`6 ${window.a},${a}`);//42,42

注意,这个示例代码在本地VSCode上会有严重的错误提示(function a不能重复定义!!!),但是在Chrome中可以正常运行,运行的结果就如提示中所示。

考虑到环境导致的行为差异太大,在MDN上有关于块中定义函数时的建议:

应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

函数声明语句并非真正的语句,JS 规范只是允许它们作为顶级语句。它们可以出现在全局代码中,或者内嵌在其他函数中。相比之下,函数表达式是更大意义上表达式的一部分。函数表达式不会出现函数提升,放在哪里就在哪里执行,不会像函数声明那样随意放置具有误导性。

上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。

本文大量参考了阮一峰大神的文章,获益匪浅:ES6 的块级作用域

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/768809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

win10 host 配置不生效 浏览器访问无效

遇到了一个比较坑的问题,host配置不生效。电脑是win10,排查了一个小时,刚开始我先排查编码是否有问题,然后又排查是不是权限的问题,经过我的修改编码和权限还是有问题,也查看了一些博客也没找到跟我出现一样…

从零搭建vue+electron桌面应用

从零搭建vueelectron桌面应用 一、准备工作1.全局下载electron2.全局下载vue脚手架3.创建vue项目(这里用的是vue2版本)4.安装打包插件5.安装electron-builder,安装后可以直接生成主进程的配置文件6.在vue.config.js中添加以下配置 二、运行项…

SpringCloud(五)Gateway 路由网关

一、路由网关 官网地址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/ 我们需要连接互联网,那么就需要将手机或是电脑连接到家里的路由器才可以,而路由器则连接光猫,光猫再通过光纤连接到互联网&a…

Linux 的远程唤醒

Linux (Ubuntu、Debian、Centos …) 的远程唤醒 环境说明: 两台局域网内的 linux 主机,本环境的系统为 loongnix 目的主机为:IP 192.168.12.11 MAC 86:d8:60:47:28:22远程主机为:IP 192.168.12.15 一、唤醒准备工作 (目的机上操…

Sentinel限流--流控模式与限流效果

文章目录 1、簇点链路2、流控入门案例3、流控模式:关联模式4、流控模式:链路模式5、流控效果:warm up6、限流效果:排队等待7、热点参数限流 1、簇点链路 簇点链路就是项目内的调用链路(controller -> servcie ->…

python-在transformers的问答模型中使用中文

先安装transformers在huggingface下载模型 模型bert-multi-cased-finetuned-xquadv1可以从huggingface中下载,具体操作方法可以参照文章https://blog.csdn.net/zhaomengsen/article/details/130616837下载 git clone就可以了然后使用pipline加载模型 from transfor…

【卫朋】华为 IFS 集成财经服务流程(限制版)

目录 简介 集成财经服务 专栏列表 CSDN学院 简介 今天主要来谈谈华为流程体系中的财经流程。 大家可以看下面这张图。 深蓝色标注的就是 IFS 流程的在企业整体流程架构中的位置。 财经部门其实也是直接或间接跟客户打交道的。 这就意味着,财经也是需要做端到…

Unity源码分享-大量鱼类模型Underwater Animals Pack

Unity源码分享-大量鱼类模型Underwater Animals Pack 下载地址:https://download.csdn.net/download/Highning0007/88061483

FPGA XDMA 中断模式实现 PCIE3.0 HDMI视频采集卡 提供2套工程源码和QT上位机源码

目录 1、前言2、我已有的PCIE方案3、PCIE理论4、总体设计思路和方案视频采集和缓存XDMA简介XDMA中断模式QT上位机及其源码 5、vivado工程详解6、上板调试验证7、福利:工程代码的获取 1、前言 PCIE(PCI Express)采用了目前业内流行的点对点串…

中国芯片发出怒吼,要求回购700台光刻机,ASML承受不起

多家媒体报道指国内知名存储芯片企业长江存储的董事长指出已买回的光刻机因维护和零件问题可能无法使用,因此提出基于公平原则,ASML理应回购这些光刻机,凸显出中国芯片企业的愤怒。 由于美国的阻挠,ASML不仅不会继续对中国出售先进…

工厂方法模式(java)

目录 结构 案例 类图 代码实现 抽象咖啡工厂 美式咖啡工厂 拿铁咖啡工厂 咖啡类(抽象产品类) 美式咖啡 拿铁咖啡 咖啡店类 测试类 优缺点 优点 缺点 结构 工厂方法模式的主要角色: 抽象工厂(Abstract Factory&…

MySQL常用语句

目录 连接MySQL 数据库操作 表的操作 数据操作 进阶查询 源码等资料获取方法 连接MySQL -- 语法:mysql -u用户名 -p密码 注:--空格 起到注释的作用 mysql -uroot -p123456 数据库操作 -- 显示当前时间、用户名、数据库版本(可以单独…

NodeJS实现支付宝沙箱支付②③

文章目录 前言版权声明Alipay SDK 沙箱环境简介Node环境要求沙箱环境配置下载所需模块准备前端静态页面以及Node服务器文件夹规范AlipaySdk 配置准备AlipaySdk 代码演示 Alipay实例化 ~ alipay.sdk 文件 AlipayForm ~ alipayForm文件 AlipayFormStatus ~ alipayForm文件 …

MQTT协议在物联网环境中的应用及代码实现解析(四)

四、使用单片机环境编程接收MQTT服务器上特定主题的信息 以下是使用STM32F103单片机链接W5500芯片链接到网络上,利用MQTT协议接收MQTT服务器“mqtt.laobai.net”上的“laobai_topic001”主题上的订阅信息,并发送给串口的C语言代码示例,包括完…

C语言——指针详解(进阶)

轻松学会C语言指针 一、字符指针二、数组指针2.1 数组指针的定义2.2 &数组名VS数组名2.3 数组指针的使用 三、指针数组四、数组参数和指针参数4.1 一维数组传参4.2 二维数组传参4.3 一级指针传参4.4 二级指针传参 五、函数指针六、函数指针数组七、指向函数指针数组的指针八…

计算机网络微课堂学习笔记(详细图解讲解)-长期更新

前言: 计算机网络在信息时代的作用 计算机网络已由一种通信基础设施发展成为一种重要的信息服务基础设施,计算机网络已经像水、电、煤气这些基础设施一样,成为我们生活中不可或缺的一部分 一、因特网概述 (1)网络、…

ChatGPT火热之下的冷思考

作为一款基于人工智能的自然语言处理(NLP)​​聊天机器人​​程序,ChatGPT通过大量来自互联网的文本进行训练,并使用深度学习和机器学习算法来理解用户的问题并提供准确的回答。并且,ChatGPT还内置了情感分析、关键字提取和实体识别等功能&am…

ngsoc使用指南

和威胁告警差不多。 ngsoc是以资产为核心,以安全事件为管理的关键流程,建立一套威胁检测,相应,预测,和持续监控分析,一体化的监控与相应平台。 和天眼的区别:会把天眼的告警,其他安…

【041】从零开始:逐步学习使用C++ STL中的stack容器

从零开始:逐步学习使用C STL中的stack容器 引言一、stack容器概述二、stack容器常用API2.1、构造函数2.2、赋值操作2.3、数据存取操作2.4、大小操作 三、使用stack容器实现一个高效的算法总结 引言 💡 作者简介:一个热爱分享高性能服务器后台…

安达发|如何选择更适合我们的APS高级排程软件

如何选择aps高级排程公司更适合我们?在选购aps高级排程的时候,一些朋友由于不清楚其中的选购技巧,许多时候会掉入些许选择误区,导致我们买不了合适我们选择的aps高级排程。因此选择适合我们的aps高级排程就变得十分重要,唯有明白…