在块语句中的函数声明

本文深入探讨JavaScript中的块级作用域和函数声明,解释在ES6之前的函数声明行为,以及ES6引入块级作用域后带来的变化。文章通过示例和历史背景分析,展示了不同引擎对函数声明的处理方式,以及ES6后的扩展规范如何影响函数声明的提升和作用域。文章最后总结了在不同环境下函数声明的行为及其应用,并提供了相关的结论和建议。
摘要由CSDN通过智能技术生成

在块语句中的函数声明

下面这个示例,在JS中会输出什么呢?

{
   
  a = 1
  function a() {
   }
  a = 2
  console.log(a) // 输出1
}
console.log(a) // 输出2

只要跑一下程序,就会知道答案是“先2后1”:

2  // 输出1
1  // 输出2

然而“输出2”为什么会是1呢?

这个问题得从远古时期的JavaScript说起,那个时代还没有ES6所谓的“环境(Environment)”,与之相似的概念称为“作用域”。也因为这个缘故,在通常的JavaScript中,把这个在ES6之后才发展起来的东西称为“块级作用域”。

然而仅仅是“块级作用域”,是不足以解释上面这个例子的独特之处的。

在没有块级作用域之前

在ES5及其之前的时代,JavaScript只有“函数”和“全局”两个级别的作用域。这带来了许多的问题,其中之一是“eval执行在哪里”的问题,另一个就是今天要讲到的“在语句中的函数声明的作用域在哪里”的问题。例如:

function foo(tag) {
   
    if (tag) {
   
        function bar() {
   
            console.log("Hello")
        }
    }
    else {
   
        function bar() {
   
            console.log("World")
        }
    }
    bar();
}

foo(true);  // 输出1
foo(false); // 输出2

在这个例子中,从正常人的思维来说,我们会认为输出下面的内容:

Hello  // 输出1
World  // 输出2

但是在没有块级作用域之前,不同的JS引擎对上述代码的理解并不相同。例如在微软的JScript中,bar就被作为函数foo()的内嵌函数来理解,因此代码中事实上是连续两次声明了函数bar(),所以只有最后一个声明有效。因此无论foo(true)还是foo(false)都将输出World,上面的结果也就是两个World

然而Mozilla Firefox提出不同的看法,他们认为应该在结果上与“正常人的思维”一致,因此在ES5之前的时代他们就提出并实现了“(类似)块级作用域”的概念,这里被处理成了“条件声明”,也就是说bar()这个函数在foo()中将是未被声明的,只有执行到了if的某个分支之后,它才会“正式地”声明出来。——在它被“正式地”声明出来之前,bar这个函数名并不存在,无需理会。

这其实带来了更多的问题,例如如果函数bar()尚不存在,那么在if条件语句之前就会访问到全局,而执行了语句之后,就会访问到foo()函数内的声明。——不要忘了,如果动态地在函数内使用eval语句,还会动态地创建出“foo”这样的名字来,所以……没有人能真正搞懂函数foo()中有没有bar()这个名字,又或者调用函数foo()的时候会不会访问到全局中的bar——函数或者变量名等等。

所以,在“块级作用域”出来之前,上面的示例的含义就成了悬案。Mozilla Firefox也只是在1.6+的JavaScript扩展语法中支持它们(Spider Monkey JavaScript 1.5支持到ECMAScript 3)。

有了ES6的块级作用域,问题却更大了

好了,ES6提出了块级作用域,也就是说有了“语句”这个级别的作用域。但是有了这个东西,对于上述示例来说,问题却更加复杂。

NOTE1:在《JavaScript语言精髓与编程实践》的第一、二版中都讨论过,JavaScript 1.3~1.5的语言设计中没有补全“语句”和“表达式”这两个级别的作用域。而从ES6开始,才有了“语句”级别,亦即是所谓的“块级作用域”。

NOTE2:并不是只有块语句才有“块级作用域”,也并不是所有的语句都有块级作用域。可以参见《JavaScript语言精髓与编程实践(第3版)》“4.4 语句与代码分块”以及表4-6。

这又得从另一个历史问题来讲起了。首先,与“块级作用域”一起出现的、由块级作用域限制的是let/const语句声明的变量名,而在传统上,函数名是与“var变量名”类似的名字。——因此,函数名事实上也是没有块级作用域的,它“理论上”应该和“var变量名”一样放到函数或全局级别的作用域。这常常被称为“提升(Hoisting)”,例如:

function foo() {
   
  console.log(x); // undefined
  {
     // 块语句
     var x = 100;
     let y = 200; // `y`在块语句的作用域中
     function bar() {
   
         ...
     }
  }
  console.log(x); // 100, `x`在函数的作用域中
}

既然其中的“var变量名”被提升到了“foo()函数的作用域”,那么“与它性质相似的”bar()函数也应当被提升到foo()函数中啊。

——这看起来很合理啊。然而大家还记得吗,Mozilla firefox在js1.6+的时代已经实现过了所谓的“条件声明”,那么这就意味着Spidermonkey系的js引擎把这样的bar()函数声明留在了“语句一级”,而反过来,这一次反倒是JScript占了优势,他们的设计与新规范站到了一起。

然而两种主要的引擎都有极大量的用户,尤其那已经是Firefox与Chrome开始联手颠覆IE的市场的时代了,所以两种声音都有足够的话语权,最终在这个问题上

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 在C语言,复合语句是指一组语句被放在一个大括号"{}"内的语句。复合语句可以在函数使用,也可以在其他语句的内部使用,例如循环语句。在一个复合语句内,可以定义变量并执行多条语句,它们将作为整体被该语句解释。 ### 回答2: C语言的函数复合语句是指在一个函数体内使用一对大括号{}括起来的多条语句的集合。它是一种用来实现更复杂的功能以及提高代码的可读性的方法。 函数复合语句可以包含多条语句,这些语句按照顺序依次执行,并且可以定义局部变量。在函数复合语句内定义的局部变量只在该复合语句内部有效,出了复合语句就会被销毁,这可以避免变量的冲突和重复使用,提高程序的安全性和可靠性。 函数复合语句也可以内嵌在其他语句,例如可以将一个复合语句嵌套在if语句或循环语句。这样的嵌套可以提供更复杂的条件执行和循环控制。 使用函数复合语句可以将一段逻辑上相关的代码组织起来,并且可以使用大括号大致表示代码的边界。这样做可以方便代码的维护和调试,也使得代码更加易于理解。 总之,函数复合语句是C语言用来组织和实现复杂功能的一种语法特性。它可以定义局部变量,按照顺序执行多条语句,并且可以嵌套在其他语句。使用函数复合语句可以提高代码的可读性和可维护性,使程序更加安全和可靠。 ### 回答3: C语言的函数复合语句是指在函数体内使用{}括号括起来的一个或多个语句的组合。 函数复合语句的主要作用是将多个语句组合成一个逻辑整体,从而实现特定的功能。在C语言,函数体语句是按照顺序依次执行的,复合语句可以将多个语句按照自定义的顺序组合在一起,以实现更复杂的操作。 函数复合语句可以包含各种类型的语句,比如变量声明、赋值语句、条件语句、循环语句等。在复合语句,可以定义局部变量或临时变量,这些变量的作用域限制在函数内部,不会对其他函数产生影响。 复合语句语句可以根据需要嵌套使用,也可以使用控制流语句控制程序的执行流程。例如,可以使用if语句进行条件判断,根据条件的不同执行不同的语句;还可以使用for或while语句进行循环操作,多次执行同一组语句。 通过使用函数复合语句,可以简化代码结构,提高代码的可读性和维护性。同时,复合语句还可以将代码作为一个整体进行调试和测试,便于排查错误。 总之,函数复合语句是C语言用{}括起来的一个或多个语句的组合,可以实现多个语句按照自定义顺序组合在一起,以实现特定的功能。它是C语言的基本语法结构之一,常用于定义函数体或其他需要组合多条语句的场合。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值