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

时间:2018-07-11 07:47:43

标签: javascript

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

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

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

2 个答案:

答案 0 :(得分: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(){}
})()

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

在JavaScript var中,不是具有块作用域,它具有 functional 作用域,因此您不能假设每次看到{ }时,它为其中声明的var创建一个作用域。基本上,功能块的工作方式与条件和迭代所使用的其他块不同。

最近,在ES2015中,引入了带有关键字letconst的块范围变量。尽管如此,作用域在JS中并不简单明了,所以您必须了解不同的关键字如何创建变量以及变量如何在不同的块结构内限定作用,以及严格模式如何影响该行为。

事实证明,情况B是偶然的,也是varfunction () {}声明在非严格模式下工作的一种偶然现象。

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

// 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
})();

因此,您发现了一个奇怪的特殊情况,即在非严格模式下,函数声明的提升和作用域表现很差。它不会在严格模式下执行此操作,请参阅下面的下一部分。

函数参数的行为更像是用let定义的变量,而不是var,这就是为什么情况A的行为不像情况B的原因。块级范围函数声明不能​​改变的原因不多一个正式的参数,因为它无论如何也不应这样做。情况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

答案 1 :(得分:1)

更好的问题是,为什么可以更改var声明的名称?答案是因为与旧的非标准浏览器行为具有向后兼容性。

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

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

或者,如果您同时使用“ var”但也使用“ 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