阅读 199

清扫变量声明提升

前言

js 中存在变量提升,前端er基本都知道,但是这个变量提升的是什么,什么阶段提升的,var,let和function等关键字声明的变量在if条件语句中是否有提升,提升是否能穿透条件语句的执行体?不知道有多少人对这些有深入理解。

先上代码:

var b = true;
if(b) {
    function a() {
        console.log('a');
    }
} else {
    function a() {
        console.log('b');
    }
}
a();
复制代码

当你读完这段代码后,如果心中没有准确且确定的知道它是怎么运行的,那么你本文可能对你有所帮助。

js 的预处理

JS 执行前,会对代码进行预处理,预处理过程会提前处理var、function声明、class、const、let等关键字声明的变量。

声明提升规则

在变量的声明提升中存在一些平常我们可能不太注意的细节,这里将声明提升的具体行为分成三类进行总结,分别是:

  • var 声明
  • function 声明
  • class、let 和 const声明

var 声明

var 声明的变量提升时,只管在当前作用域内声明这个变量并初始化其值为undefined。来看一个实际的例子:

function s() {
    console.log(a);
    var a = 'ss';
}

s();
复制代码

这段代码打印的结果是undefined,由于 var a在预处理时被提升至函数s的作用域最开始,并初始化为 undefined,所以打印结果是undefined。

再来看另一段

function test() {
    console.log(a);
    if(false) {
        var a = 'ss';
    }
}
test();
复制代码

这段代码的打印的结果也是 undefined,虽然判断条件为 false 的语句永远不会被执行,但是 var 的声明提升是不管这些的,它的提升可以穿透条件语句直达当前作用域的顶部,至于执不执行声明的提升是不管的。

总之 var 的提升就一句,提升到当前作用域的顶部,并初始化其值为undefined。

function 声明

相较于 var 的提升简单明了,function 的提升则要复杂一些,考虑下面这段代码:

function test() {
    console.log(a);
    function a() {
        
    }
}
test();
复制代码

这段代码比较简单,会打印函数出一个函数a,而不是undefined,也就是说函数声明不仅仅是变量声明的提升,同时给变量 赋值了。那么是不是无论何时,函数声明的提升都是及提升变量声明还赋值呢?考虑下面这段代码:

function test() {
    console.log('函数a', a);
    if(false) {
        function a() {
            
        }
    }
}

test();
复制代码

这段代码的打印结果是undefined,如果没有函数声明,直接打印a,这里会抛出 not defined 的错误。

这说明在 if 语句中 function 的声明名仍然会提升,只是被赋值为 undefined 了,其具体的赋值发生在了代码执行阶段。

class、let 和 const声明

class、let 和 const的声明提升具有相同特征,所以这里我只说以下 let。

先考虑下面这段的代码:

function test() {
    console.log(a);
    let a = 'aLet';
}
test();
复制代码

嗯,这段代码会抛错,就像这样

现在去网上搜一下关于 let 的变量提升的内容,仍然会看到很多说 let 不存在变量声明提升的说法,给出的理由是,既然提升了,为什么在声明之前使用该变量会报错呢?下面就说一说为什么认为 let 是存在提升的。 考虑下面代码:

function test() {
    console.log(c)
    var c = 'tVar';
    let c = 'cLet';
};
test();
复制代码

这段代码会抛处错误,告诉你已经存在同名变量了,不能再定义c,按道理来说正常结果应该会打印 undefined 才对,毕竟var c 声明的 c 会提升并被赋值为 undefined 。如果去掉let c的定义,则会按我们预期的打印 undefined ,也就是说出现在后面的 let 声明影响了前面语句的结果。

这说明 let 声明的变量在预处理阶段仍然会被处理,只不过这种处理只是单纯的在声明作用域中提升了变量的声明,而没有给变量初始化,而在变量被初始化之前是无法使用的,也就出现在 let 声明之前使用变量会抛出错误。

关于 let 的声明提升还有一个叫暂时死区的名词,其实这个词非常好理解,let 声明的变量在被赋值之前是无法使用的,那么在变量被赋值之前到变量所在作用域范围开头的那段位置称之为这个变量的死区,但是这个变量的作用域不会被影响,当它被赋值后,即使是在它被定义的位置之前调用这变量也依然是可以的,这种不可用是暂时的,所以叫做暂时死区。这里讨论暂时死区没有太大意义,知道是怎么回事就行。

声明提升的优先级

声明提升的优先级发生在同一作用域中声明同名变量的情况下,所以let,const,class 声明的变量就不需要讨论这个问题了。只需考虑 var 和 function 声明的变量。

对于同名的 var 声明,Javascript 采用的是忽略原则,后声明的会被忽略,变量声明和赋值操作可以写在一起,但是只有声明会被提升,提升后变量的值默认为undefined,结果是在赋值操作执行前变量的值必为undefined

对于同名的 function 声明,Javascrip t采用的是覆盖原则,先声明的会被覆盖,因为函数在声明时会指定函数的内容,所以同一作用域下一系列同名函数声明的最终结果是调用时函数的内容和最后一次函数声明相同,考虑下面这段代码:

function test() {
    a();
    function a() {
        console.log('a1')
    }
    function a() {
        console.log('a2')
    }
 }
 test();
复制代码

打印的结果是a2,说明后面定义的函数覆盖了前面的函数。

对于同名的函数声明和变量声明,采用的是忽略原则,由于在提升时函数声明会提升到变量声明之前,变量声明一定会被忽略,所以结果是函数声明有效。考虑下面代码:

function test1() {
    console.log(typeof a);
    var a = 'aVar';
    function a() {
        
    }
}
test1();

//先声明函数后声明变量,证明上边的例子不是function覆盖了变量
function test2() {
    console.log(typeof a);
    function a() {
        
    }
    var a = 'aVar';
}
test2()
复制代码

test1和test2运行的结果均是function,这表明函数的变量声明是优先的。

综合上面的描述,函数声明提升的优先级是高于var 声明变量提升的。

结论

尽管本文是在讨论变量提升,但是我本人并不提倡在变量声明之前使用变量声明之前使用变量这一违反人类直觉的做法,在实际编码中我们还是应该先声明变量后使用它。掌握变量提升有助于我们更好的理解代码,避免在遇到提升的用法时无法理解。

关注下面的标签,发现更多相似文章
评论