函数的形参是 var声名 还是 let声名 ?
最近看 阮一峰老师 的 ES6入门教程 中对 暂时性死区有如下理解:
// 有些“死区”比较隐蔽,不太容易发现: function bar(x = y, y = 2) { return [x, y]; } bar(); // Cannot access 'y' before initialization /* 上面代码中,调用`bar`函数之所以报错(某些实现可能不报错), 是因为参数`x`默认值等于另一个参数`y`, 而此时`y`还没有声明,属于“死区”。如果`y`的默认值是`x`,就不会报错,因为此时`x`已经声明了。*/ 复制代码
如果说函数形参声名存在暂时性死区,
那么此处 y 应该为 let 声名,
然而我们可以在函数内部使用 var 进行声名 :
function bar(x) { var x = 3; console.log(x); } bar(2); // 3 复制代码
按正常情况来说, 如果用 let 声名过的变量,是无法再用 var 声名的:
let x = 2; var x = 3; console.log(x); // 'x' has already been declared 复制代码
有的同学说 function() 括号中的部分属于一个 块级作用域
如果这样的话应该 类似 for()循环的 let 声名
然而 :
1. for 循环中
for (let i = 0; i < 3; i++) { let i = 'abc'; // 此处并不会报错 console.log(i); } // 'abc' // 'abc' // 'abc' 复制代码
2. 函数中
function bar(x) { let x = 3; console.log(x); } bar(2); // Identifier 'x' has already been declared 复制代码
可见这样理解并不合理
--------------------------------------------------------------------------
在大佬指出 :
js中,执行一个函数时,形参和局部变量是存在一个活动对象里的,或者叫变量对象,
如果在局部变量中定义了一个和形参相同名字的变量,编译器会忽略这个定义,
而所有对该变量的操作都会反映到形参的那个变量中去
function fn(id) { console.log(id); // 2 var id = 9; return arguments[0] } console.log(fn(2)); // 9 复制代码
我理解为:
在函数内部不定义与形参相同名字相同的变量时,形参采取 let 声名,
然而如果在函数内部定义与形参相同名字的变量,let 声名会被忽略,进而将所有操作指向函数内部定义的同名变量,这个过程也存在实参赋值形参的过程,即 id = 2; 可以理解成如下
id = 2; var id = 9; return id 复制代码
此处 var 关键字进行变量提升也并不会影响结果,因为在全局作用域下该部分输出也是没问题的,
这也是 let 声名 id 会报错的原因了,因为在 let 声名之前我们已经执行了 id = 2;
---------------------------------------------------------------------------------------
感谢大佬的指正,这里用大佬原话解释:
形参既不是var也不是let声明,它本身就是函数参数声明,是一种独立的类型
另外函数的括号是函数作用域,也不是块级作用域,和块级作用域是有区别的。
---------------------------------------------------------------------------------------
总结:
综合阮大的观点,个人理解为:
第一段代码确实存在暂时性死区,但并不是 let 引起的,而是函数形参默认声名引起的
函数内部重新定义形参会导致对形参的所有操作转移到同名参数上(因为 arguement 会发生变化)
而实参赋值形参的操作仍然会在函数执行后立即执行,这就对 为什么可以使用 var 重新声名 而不能使用 let 做出了合理的解释
作者:早川秋
链接:https://juejin.cn/post/7026337395964379172