8
;(function(a){
    if(true){
        function a(){}
    }
    console.log(a) // 1
})(1)

;(function(){
    var a = 0
    if(true){
        function a(){}
    }
    console.log(a) // function a(){}
})()

为什么块级作用域中的函数不能更改形式参数?

4

2 回答 2

3

我不明白你为什么要这样做,但为了更好地理解 JavaScript 的一个极端情况,让我们来探索一下。有时这有助于我们更好地理解语言的基础知识。

为了便于讨论,让我们考虑您的两个示例,分别为案例 A 和案例 B:

// Case A - argument a is not overwritten
;(function(a){
    if(true){
        function a(){}
    }
    console.log(a) // 1
})(1)

// Case B - var a is overwritten
;(function(){
    var a = 0
    if(true){
        function a(){}
    }
    console.log(a) // function a(){}
})()

为什么块级范围内的函数声明不能​​更改形式参数

在 JavaScriptvar没有块作用域,它有函数作用域,所以你不能假设每次你看到{ }它都会为其中声明的变量创建一个作用域。基本上,功能块的工作方式与条件和迭代使用的其他块不同。

最近,在ES2015let中,使用关键字and引入了块作用域变量const。然而,在 JS 中作用域并不简单,因此您必须了解不同的关键字如何创建变量以及它们如何在不同的块结构中作用域,以及严格模式如何影响该行为。

事实证明,案例 B 是一种侥幸和意外,var声明function () {}在非严格模式下的工作方式。

首先,在所有 JavaScript(包括严格模式)中,使用函数声明定义的函数,例如function foo() {...}被提升到当前块级范围的顶部!这意味着,在范围内,您永远不能用var函数声明覆盖 a。

// Case B modified
;(function(){
    console.log(a) // function a(){}
    var a = 0;  // overwrites value of 'a'
    function a(){}; // will be hoisted to top of block-level scope
    console.log(a) // 0
})()

其次,在条件if块中,函数声明被提升到定义它们的任何块的顶部,而不是周围的功能块。

第三,在草率模式(非严格)下,JavaScript 对于使用if块定义的函数声明将允许该值覆盖在var该块之前声明的变量的值。

// showing behavior of points #2 and #3:
;(function(){
    console.log(a); // undefined
    var a = 0;
    console.log(a); // 0
    if(true) {
       console.log(a); // function a(){...} - a() was hoisted to top of if block
       function a() {};
    })();
    console.log(a); // function a(){} - function declaration allowed to overwrite var declared above in surrounding function scope
})();

因此,您发现了一个奇怪的极端情况,其中函数声明提升和作用域在非严格模式下表现不佳。它不会在严格模式下执行此操作,请参阅下面的下一节。

函数参数的行为更像是用 s 定义的变量letvar所以这就是案例 A 的行为不像案例 B 的原因。与其说块级范围函数声明不能​​更改形式参数,不如说它不应该这样做无论如何,即使对于 vars。案例 A 是它应该如何表现。

请注意,如果您使用let而不是var事物的行为更加一致,即使在草率模式下:

// Case B using 'let' instead
;(function(){
    let a = 0;
    console.log(a); // 0
    if(true) {
        console.log(a); // function a(){}
        function a() {};
    }
    console.log(a); 0
})();

此外,let通常表现得更好,例如,即使在草率模式下,let也不允许尝试重新定义已经声明的变量:

// just try this!
let a = 0;
function a() {} // this will throw a syntax error

节点和浏览器的区别?不,这是关于严格模式的。

一些评论者在这个问题上指出了浏览器中 Node.js 和 JavaScript 之间的区别。声称是:

// in a browser
console.log(a) // Case A: 1
console.log(a) // Case B: function a(){}
// in node
console.log(a) // Case A: 1
console.log(a) // Case B: 0

但实际上,我只是在浏览器中使用 Codepen 和在本地使用 Node(8.11.3 和 10.5.0)进行了测试,都返回了以下结果:

 // in Node and browser
console.log(a) // Case A: 1
console.log(a) // Case B: function a(){}

但是,当您设置use strict指令时,您会得到以下结果,但在 Node 和浏览器中都是相同的:

 // with 'use strict, in Node and browser
console.log(a) // Case A: 1
console.log(a) // Case B: 0

关于条件函数声明的建议

基本上,我不会这样做,除非我的函数总是返回一个函数。换句话说,在大多数情况下,我不会编写函数或方法来有时返回原始值而有时返回函数。

但是让我们假设你想这样做。然后,理所当然地,我总是:

在这种情况下,我将不使用函数声明,而是将函数表达式分配给我的变量:

;(function(){
    let a = 0;
    console.log(a); // 0
    if(true) {
        console.log(a); // 0
        a = function () {}; // assign 'a' the value of the function
    }
    console.log(a); // function () { ... }
})();

为了我自己,我创建了一个 Codepen 来帮助解决所有这些问题:https ://codepen.io/mrchadmoore/pen/jpEKaR?editors=0012

于 2018-07-11T18:42:18.893 回答
1

更好的问题是为什么你可以改变一个 var 声明的名字?答案是因为向后兼容旧的非标准浏览器行为。

如果你用“let”而不是“var”声明变量,你得到0。

;(function(){
    let a = 0
    if(true){
        function a(){}
    }
    console.log(a) // 0
})()

或者如果你使用“var”但也使用“use strict”,你会得到 0。

;(function(){
    "use strict";
    var a = 0
    if(true){
        function a(){}
    }
    console.log(a) // 0
})()

因此,在非严格环境中使用 var 声明的名称是特例。

如果您有兴趣,这是规范的相关部分。
https://www.ecma-international.org/ecma-262/8.0/index.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics

于 2018-07-11T18:05:47.343 回答