关于javascript:面试题解毒Let-Var-Const-有什么区别

4次阅读

共计 3249 个字符,预计需要花费 9 分钟才能阅读完成。

序言

这是一道非常简单的面试题,然而想要答复好它却不容易。

从实用性的角度登程,var, let, const 都是 js 中用来申明变量的关键字。其中 varlet 用来定义变量, const用来申明常量。其中 letconst是在 ES6 中新增的。

问题到这里还远远没有完结,真正的考验才刚刚开始

变量晋升

这是大家看到 var 和 let 最容易想到的点。那么什么是变量晋升呢?

先放一个 MDN 官网定义在这里

变量晋升(Hoisting)被认为是 Javascript 中执行上下文(特地是创立和执行阶段)工作形式的一种意识。

实际上来讲,变量晋升就是指在变量申明之前拜访该变量。

那么为什么会产生变量晋升?因为 JavaScript 尽管是动静语言,但的确是领有动态语义的。而在 JavaScript 的晚期,这个动态语义其实并没有解决得太好,就导致了变量晋升的存在。

光是文字有点绕,咱们先来看一段代码:

console.log(a) // undefined
var a = 1;
console.log(a) // 1

为什么在咱们定义 a 之前,a 就被应用了,代码却没有报错,而且后果输入了 undefined?

你能够说 var 的默认值是 undefined,但这不够谨严,咱们须要从头说起。

在传统的编译语言中,程序中的一段源代码在执行之前会经验三个步骤,统称为“编译”。

  • 分词 / 词法剖析(Tokenizing/Lexing)
  • 解析 / 语法分析(Parsing)
  • 代码生成

比起那些编译过程只有三个步骤的语言的编译器,JavaScript 引擎要简单得多。例如,在语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包含对冗余元素进行优化等。

摘录来自:“你不晓得的 JavaScript”

那么咱们在下面这段代码失去执行前,在代码的预编译阶段,var a 就曾经由动态剖析失去了。

那么随之而来就产生了第二个问题,为什么这里的 a 不是 1 而是 undefined 呢?

因为变量晋升的实质的起因是 申明是在语法分析阶段就解决的, 如下所示:

console.log(a) // undefined
var a = 1;
console.log(a) // 1

// 变量晋升后

var a;
console.log(a) // undefined
a = 1;
console.log(a) // 1

PS: 这里存在一个 LHS 和 RHS 的概念,也是一道面试题。

在预编译完之后,javascript 的编译器会为引擎生成运行时所需的代码,这些代码被用来解决 a = 1 这个赋值操作。引擎运行时会首先询问作用域,在以后的作用域汇合中是否存在一个叫作 a 的变量。如果是,引擎就会应用这个变量;如果不是,引擎会沿作用域链持续查找该变量;如果查到根作用域也没有查到,会主动申明 a。

那么这样的话,为什么 let 会抛出异样呢?

这个问题上面会有答案

let 和 var 还有什么区别?

反复申明

除了变量晋升,咱们还须要探讨 var 和 let 反复申明的问题。

先来看这样一段代码:

var a = 1;
console.log(a); // 1
var a = 2;
console.log(a); // 2

// ------------

let a = 1;
console.log(a);
let a = 2;
console.log(a); // Uncaught SyntaxError: Identifier 'a' has already been declared

const a = 1;
console.log(a);
const a = 2;
console.log(a); // Uncaught SyntaxError: Identifier 'a' has already been declared

为什么 var 在这里能够申明两次?其实如果搞明确刚刚讲的编译过程,就很好解释了。这些都是在语法分析阶段解决的后果。

预编译时候的 LHS(Left Hand Side)并不关怀以后 a 的值是什么,只是想要为 =1 和 = 2 这两个赋值操作找到一个指标。就如同《让子弹飞》一样

当然了,let 这种更合乎个别编程习惯的做法更值得提倡。


暂时性死区

在应用 let、const 命令申明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。应用 var 申明的变量不存在暂时性死区。

用代码解释就是:

console.log(a) // ReferenceError: a is not defined
let a

ES6 规范中对 let/const 申明中的解释是:

The variables are created when their containing Lexical Environment is instantiated but may not be accessed inany way until the variable’s LexicalBinding is evaluated.

这其实对应了 var 的变量晋升。当程序的管制流程在新的作用域(module function 或 block 作用域)进行实例化时,在此作用域中用 let/const 申明的变量会先在作用域中被创立进去,但因而时还未进行词法绑定,所以是不能被拜访的,如果拜访就会抛出谬误。因而,在这运行流程进入作用域创立变量,到变量能够被拜访之间的这一段时间,就称之为临时死区。

换句话说,var 和 let,const 这些标识符都是在用户代码执行之前就曾经由动态剖析失去,并且创立在环境中,它们都是在读取一个“曾经存在”的标识符名。而 let 之所以会抛出异样,不是因为它不存在,而是因为这个标识符被回绝拜访了。


全局属性:

咱们都晓得,浏览器的全局对象是 window,Node 的全局对象是 global。

var 在全局环境申明变量,会在全局对象里新建一个属性,而 let 在全局环境申明变量,则不会在全局对象里新建一个属性。

var foo = 'global'
let bar = 'global'

console.log(this.foo) // global
console.log(this.bar) // undefined

那么咱们随之而来就会产生一个疑难,let 的全局变量到底去了哪里?
咱们来运行这样一段代码:

var foo = 'global'
let bar = 'global'

function test() {}

console.dir(test)

这段代码是我在网上看到的,刚看到我就产生了一个疑难,为什么要定义一个没用的函数?
起初发现,定义这段函数只是为了引出全局变量

咱们能够看到,bar 所处的地位是与 window 同级,也就是 [[Scopes]][0]: Script 这个变量对象的属性中,而 [[Scopes]][1]: Global 就是咱们常说的全局对象。


块级作用域

块作用域由 {}包含,let 和 const 具备块级作用域,var 不存在块级作用域。块级作用域解决了 ES5 中的两个问题:
• 内层变量可能笼罩外层变量
• 用来计数的循环变量泄露为全局变量

用 var 申明的变量的作用域是它以后的执行上下文,即如果是在任何函数里面,则是全局执行上下文,如果在函数外面,则是以后函数执行上下文。换句话说,var 申明的变量的作用域只能是全局或者整个函数块的。

而 let 申明的变量的作用域则是它以后所处代码块,即它的作用域既能够是全局或者整个函数块,也能够是 if、while、switch 等用 {} 限定的代码块。

另外,var 和 let 的作用域规定都是一样的,其申明的变量只在其申明的块或子块中可用。

示例代码:

function varTest() {
  var a = 1;

  {
    var a = 2; // 函数块中,同一个变量
    console.log(a); // 2
  }

  console.log(a); // 2
}

function letTest() {
  let a = 1;

  {
    let a = 2; // 代码块中,新的变量
    console.log(a); // 2
  }

  console.log(a); // 1
}

varTest();
letTest();

从上述示例中能够看出,let 申明的变量的作用域能够比 var 申明的变量的作用域有更小的限定范畴,更具灵便。

总结

Javascript 存在很多乏味的语法问题,有时候简略的答复并不能很好的解释起因。咱们要知其然更知其所以然

正文完
 0