当前位置: 首页 > Web前端 > HTML

【面试题解毒】Let、Var、Const有什么区别

时间:2023-03-28 19:32:25 HTML

前言这是一道很简单的面试题,但要答好却并不容易。从实用的角度来说,var、let、const都是js中用来声明变量的关键字。其中var和let用于定义变量,const用于声明常量。其中let和const是ES6新加入的。问题远没有到这里,真正的考验才刚刚开始。变量提升是大家看到var和let最容易想到的一点。那么什么是变量提升呢?先在这里放一个MDN官方定义。变量提升(Hoisting)被认为是对执行上下文(尤其是创建和执行阶段)在Javascript中工作方式的理解。实际上,变量提升是指在变量声明之前访问它。那么为什么要变量提升呢?因为虽然JavaScript是一门动态语言,但它确实具有静态语义。在早期的JavaScript中,并没有很好地处理这种静态语义,从而导致了变量提升的存在。只是文字有点绕,先看一段代码:console.log(a)//undefinedvara=1;console.log(a)//1为什么我们在定义a之前使用了a,但是代码没有报错,结果输出undefined?你可以说var的默认值是undefined,但这不够严谨,我们需要从头开始。在传统的编译型语言中,程序中的一段源代码在执行之前要经过三个步骤,统称为“编译”。分词/词法分析(Tokenizing/Lexing)解析/语法分析(Parsing)代码生成JavaScript引擎对于只需要三步编译的语言来说比编译器复杂得多。例如,在语法分析和代码生成阶段有特定的步骤来优化运行时性能,包括优化冗余元素。摘自:《你所不知道的JavaScript》那么在上面的代码执行之前,在代码的预编译阶段,已经通过静态分析得到了vara。那么第二个问题来了,为什么这里的a不是1而是undefined呢?因为变量提升的本质是在语法分析阶段处理声明,如下所示:console.log(a)//undefinedvara=1;console.log(a)//1//变量提升后vara;console.log(a)//undefineda=1;console.log(a)//1PS:这里有个LHS和RHS的概念,也是一道面试题。预编译后,javascript编译器会生成引擎运行时需要的代码,这些代码用于处理a=1的赋值操作。引擎运行时,会先询问scope是否有一个变量叫a在当前作用域集中。如果是,引擎将使用这个变量;如果没有,引擎将继续沿着作用域链搜索变量;如果它找到了根作用域而没有找到,它会自动声明一个。那么在这种情况下,为什么let会抛出异常呢?下面会有这个问题的答案let和var有什么区别?重复声明除了变量提升之外,我们还需要讨论一下var和let的重复声明问题。我们先看这段代码:vara=1;console.log(a);//1vara=2;console.log(a);//2//----------leta=1;console.log(a);leta=2;console.log(a);//未捕获的语法错误:标识符'a'已被声明consta=1;console.log(a);consta=2;console.log(a);//UncaughtSyntaxError:Identifier'a'hasalreadybeendeclared为什么在这里可以声明两次var?其实如果理解了刚才说的编译过程,就很好解释了。这些是解析阶段处理的结果。预编译时的LHS(LeftHandSide)并不关心a的当前值是多少,只想为=1和=2这两个赋值操作找到一个目标。就像《让子弹飞》一样,当然更符合一般编程习惯的let更值得提倡。临时死区在使用let,const命令声明变量之前,该变量不可用。从句法上讲,这称为临时死区。用var声明的变量没有时间死区。代码解释是:console.log(a)//ReferenceError:aisnotdefinedletaES6标准中对let/const声明的解释是:变量是在其包含的LexicalEnvironment被实例化时创建的,但可能无法在任何方式,直到评估变量的LexicalBinding。这其实对应var的变量提升。当程序的控制流在一个新的作用域(模块函数或块作用域)中被实例化时,在这个作用域中用let/const声明的变量会先在该作用域中被创建,但是它们还没有被词法绑定,所以不能访问,访问会报错。因此,从运行进程进入作用域创建变量到可以访问变量之间的这段时间称为临时死区。也就是说,var、let、const等标识符在用户代码执行之前已经通过静态分析得到,并在环境中创建,它们都是在读取一个“已有”的标识符名称。let之所以抛出异常,不是因为它不存在,而是因为标识符被拒绝访问。全局属性:我们都知道浏览器的全局对象是window,Node的全局对象是global。如果var在全局环境中声明一个变量,它会在全局对象中创建一个新的属性,而let在全局环境中声明一个变量,它不会在全局对象中创建一个新的属性。varfoo='global'letbar='global'console.log(this.foo)//globalconsole.log(this.bar)//undefined那么我们就会有一个疑问,let的全局变量是什么?去?让我们运行一段这样的代码:varfoo='global'letbar='global'functiontest(){}console.dir(test)这段代码是我在网上看到的,刚刚看到一个疑问,为什么要定义一个无用的函数?后来发现,定义这个函数只是为了导致全局变量。我们可以看到bar的位置是和window在同一层的,也就是Script的变量对象属性中的[[Scopes]][0]:,而[[Scopes]][1]:Global是我们常说的全局对象。块级作用域块作用域被{}包围,let和const有块级作用域,var没有块级作用域。块级作用域解决了ES5中的两个问题:?内部变量可能会覆盖外部变量?用于计数的循环变量被泄漏为全局变量用var声明的变量的作用域是它当前的执行上下文,即如果如果它在外部任何函数,都是全局执行上下文,如果在函数内部,就是当前函数执行上下文。也就是说,var声明的变量作用域只能是全局或者整个功能块。let声明的变量的作用域是它当前所在的代码块,即它的作用域可以是全局也可以是整个函数块,也可以是if、while、switch等{}限定的代码块.另外,var和let的作用域规则是一样的,它们声明的变量只在声明它们的块或子块中可用。示例代码:functionvarTest(){vara=1;{变量a=2;//在函数块中,同一个变量console.log(a);//2}console.log(a);//2}functionletTest(){让a=1;{让a=2;//在代码块中,新变量console.log(a);//2}console.log(a);//1}varTest();letTest();从上面的例子可以看出,let声明的变量作用域可以比var声明的变量作用域更小,更加灵活。总结Javascript有很多有趣的语法问题,有时简单的答案并不能完全解释原因。我们需要知道为什么