关于 JavaScript 变量提升相关的问题?

起因是我看到了这个思考题

function a(){
    console.log(b);
    var b  = 10;
    function b(){};
    console.log(b);
}
a();

我分析了一下我的逻辑,我觉得应该是这样
image.png

我一开始以为一切都以函数声明为最优先,但是一旦加入参数以后,我发现 《函数声明》的优先级并不是最高的。从而引发了我的问题:

这是一个参数如果有默认值的情况,我发现函数内部的同名函数 a 优先级被放低了,优先采用了参数的默认值 99
image.png


就当我想得出结论的时候,又突然发现了这样的情况。当参数没有默认值时,同名函数 b 的优先级又被提升了。

image.png

请问这到底是什么情况呢?参数在函数内部到底是 let 声明的还是 var 声明的呢?

阅读 2.1k
3 个回答
要明确答案,最好是去看 ECMAScript Specification,我没去看,只是从人个理解的角度去分析描述,仅供参考
  • 变更提升是 JavaScript 一直存在的特性。
  • 参数默认值是 ES6 加入 JS 的特性。

没有使用参数默认值之前,需要兼容早期版本的 JS,所以行为看起来比较容易理解,函数直接提升到顶,后面的 var b = 10 由于 b 已经存在,实际是 b = 10,是覆盖值,所以第二句输出是 10

使用默认参数之后,参数 a = 99 的作用域可能是按 ES6 引入的块级作用域来处理的。

试验:
引用

function test() {
    console.log(".......");
    return 99;
}
引用
function f1(a = test()) { }
f1();

引用
这个代码会报错:Cannot access 'a' before initialization。在 ES6 之前,非 strict 模式使用未定义的变更应该不报错,strict 模式报错应该是 ReferenceError: a is not defined

如果是块级作用域,一个变量一但定义了就不能再定义,比如

function ff() {
    let a = 99;
    function a() { } // Identifier 'a' has already been declared
}

这么说来,参数中的 a = 99 还不是按单纯的局部变量来处理的。我只记得这里的处理有点复杂,但具体怎么处理的不记得了(要看 Spec)

总之,写代码的时候应该尽量避免这种写法。就算你写的时候搞明白了,机器也能够准确的执行,但是不能保存别人看这个代码的时候能理解准确,也不能保证过一段时间之后你自己还能理解准确……


补充一下

  • 如果想进行技术研究,阅读 ECMAScript-262
  • 如果想写这样的代码……劝你趁早打住
  • 如果读到这样的代码,跟踪调试来查看实际的情况;或者简化逻辑之后运行来查看实际的情况。搞明白之后如果有权限的话,右手把代码重构掉。

这个应该和函数参数默认值有关

function f1(a=99){
    console.log(a)
    function a() {}
    console.log(a)
    var a = 1
    console.log(a)
    
}

等同于

function f1(a){
    a = 99
    console.log(a)
    function a() {}
    console.log(a)
    var a = 1
    console.log(a)
}

这样就说的通了

你最顶上的逻辑也不完全正确
你要明确一点,函数的变量提升和var 变量声明提升是不一样的,参考文档

JavaScript 在执行任何代码段之前,将函数声明放入内存中的优点之一是,你可以在声明一个函数之前使用该函数(函数表达式 function expressions 不会被提升)

所以你在执行代码块代码之前

console.log(b);
var b  = 10;
function b(){};
console.log(b);

function b(){} 已经被放入了内存中

所以上面代码可以分成两块

// 块1
function b(){}; // 先放到内存中

// 块2
console.log(b);
var b  = 10; // 此时上下文已经有了变量 b,所以无需再声明,直接覆盖
console.log(b);

社区里面有挺多关于变量/函数提升的文章了,很多都是讲述的很详细的,你可以参考一下的,比如说:
深入解析变量声明提升和函数声明提升
JS变量提升与函数提升的机制

不过我个人理解是习惯把变量的声明提到最前,然后是函数的声明,然后是初始化赋值。

函数/变量的声明都是在顶部的。具体呢先是变量的声明,接着是函数的声明。然后按照业务逻辑顺序依次去给变量赋值。

这样就会比较好理解了。

上面的这篇文章里面提到的不会覆盖的额外值概念,就很容易让人理解错误。但是实际呈现的结果其实应该是符合他所说的。


所以其实并不是 “被优先了”。 而是是否有变量赋值的操作把对应的变量名/函数名重新赋值了。

关于函数参数的默认值,其实你就把他当成在函数顶部增加了一个变量的赋值操作。
比如说你的两个例子,转换成执行阶段其实就是类似这样的伪代码:

// Example 1
function a(){
  var b
  function b(){};
  console.log(b); // f b(){}
  b = 10;
  console.log(b); // 10
}
a();
// Example 2
function f1(a = 99) {
  var a
  function a(){}
  a = 99
  console.log(a) // 99
  console.log(a) // 99
  a = 1
  console.log(a) // 1
}
推荐问题
宣传栏