JavaScript学习系列5 ---ES6中的var, let 和const

我们都知道JavaScript中的var,在本系列的 

JavaScript学习系列2一JavaScript中的变量作用域 中,我们详细阐述了var声明的变量的作用域

文章中提到,JavaScript中没有块级作用域(由{}限定的作用域), 也就是说JS中变量的作用域只有两种,一种是全局作用域,另一种就是函数作用域

但是,其实在ES6中,新增了let命令,改变了这种状况,它为JavaScript增加了块级作用域. ES6中还有一个const命令。 我们现在来看看它们

一个简单的例子

{
   let a = 1;
   var b = 1;
}

a  //  ReferenceError: a is not defined
b // 1  

上面这个例子中,在代码块中,用let声明了一个变量a, 用var声明了一个变量b. 然后我们尝试在代码块之外去访问,发现可以访问到b,但是却没法访问到用let声明的变量a. 

上面这个例子很好的说明,ES6中的let设立了一个JavaScript中的块级作用域,上面的let声明的变量a,只在{}里面的块级作用域中有效,出了这个{}块级作用域,它就不存在了,所以才会报错 ReferenceError

在JavaScript中,有一个地方非常适合使用let,哪里呢. 就是for循环中的计数器 (记住这一点 JavaScript中for循环的计算器,特别适合使用使用let声明), 我们来看一个例子

for(let i = 0; i < 100; i++)
{
     //....
}

因为for循环中的计数器只需要在for循环中使用,所以它的作用域就是for循环这一个语句块,所以特别适合使用let来声明。 除了作用域不同之外,var和let还有哪些区别呢,我们来看看

1. 不存在var 那样的 声明提升 (hoisting)


JavaScript学习系列2一JavaScript中的变量作用域中,我们提到了JavaScript中用var来声明变量时,存在的声明提升(hoisting), 如下

alert(name);    // 输出 undefined
var name = "Luke";

//实际执行过程  (变量提升)

var name;
alert(name); 
name = "Luke";

那么ES6中的块级作用域声明let是否也存在变量提升呢, 答案是否定的,我们来看看

let  const 不存在变量提升

// var 声明的变量

console.log(test);  //  输出 undefined
var test = 2  

// let 声明的变量

console.log(test)  // 报错 ReferenceError
let test = 2;

 我们可以看到,用let来声明变量时,没有发生变量提升. 当执行console.log(test) 时,在它后面的声明语句 let test = 2; 是用let来声明的,不会发生声明提升。所以,这个时候test还是没有声明的变量,不存在,所以报错ReferenceError

 let, const中的暂时性死区

 什么是暂时性死区呢,我们还是先通过一个例子来直观的理解

var test = 'a';

if(true)
{
    test = 'b';  //报错 ReferenceError
    let test;
   
}

是不是觉得很难理解,感觉不应该报错啊,因为虽然在if语句块里面, 用let对test的声明是在赋值 test = 'b'之后,但是在if语句块外面,我们用var对test进行了声明,它应该也是个全局变量。好像这样理解是对的哈。但事实呢...

ES6中有规定,  如果在语句块 (区块) 中存在let 和 const命令, 这个语句块(区块) 对用let或者const声明的变量,  就形成了一个封闭作用域. 如果你在声明之前就开始使用这个变量,那么从第一次使用它开始,一直到它的声明出现的地方,这个范围我们称之为 "暂时性死区" (temporal dead zone 简称 TDZ) . 在这个 暂时性死区 TDZ 中,使用这些变量 (因为是在声明之前使用),就一定会报错。

上面的代码中,if语句块内部就是一个暂时性死区. 在这个暂时性死区中,用let声明的变量test就会绑定 (binding) 在这个区域,不再受到外部影响. 也就是说如果你在 暂时性死区 外面也声明了一个同名的全局变量,也完全对暂时性死区 里面的变量没有任何影响。 

所以,在if语句块这个暂时性死区中,你在let声明test之前,就对test赋值,会报错,因为这个时候test压根就还不存在  我们再看一个例子

if(true)
{

     // TDZ 开始
      test = 'a';
      console.log("The result is " + test);
     
      let test;  //TDZ结束
      console.log(test); // 输出 undefined

      test = 1;
      console.log(test); //输出 1
}

 上面这个例子,对 暂时性死区 (TDZ) 的区分写的很清楚,在if语句块中,从test变量第一次出现开始,一直到test的声明出现,这整个范围都是 TDZ 暂时性死区

出现了let之后,以前我们知道的一个常识  “JavaScript中的typeof运算符是百分之百安全的” 就不再是正确的了. 我们来看一个let出现之前(ES6之前)的一个例子

typeof a //  输出 "undefined"

在上面的代码中,因为a没有声明,所以默认就是一个全局变量,而类型未知,所以输出它的类型就是 undefined

出现了let之后,我们再来看一个例子

typeof x;  // 报错 ReferenceError
let x;

 按照let的暂时性死区 TDZ的说法,在let x之前一直到这一行,都是暂时性死区. 在这个 暂时性死区 TDZ 中,使用这些变量 (因为是在声明之前使用),就一定会报错。所以这个时候使用 typeof x 就一定会报错 ReferenceError, 因为它这个时候压根就还不存在

针对let的暂时性死区TDZ, 我们再来看一个有意思的例子

// 不报错
var a = a;

//报错   ReferenceError: a is not defined
let a = a;    

 上面,在用let声明变量时,就已经开始使用变量,把a赋值给a,也就是说声明和使用是几乎同步进行的。这种情况,报错 ReferenceError:a is not defined . 这说明,在let形式的暂时性死区TDZ中,只要你是在变量还没有声明完成之前使用,就会报错

所以,上面这行语句 let a = a, 在let a 对变量a进行声明语句还没有执行完成之前,就去获取a的值,就报错了。报的错误就是 a 这个时候还没有定义,所以不能使用 (a is not defined).

let,const不允许在同一个作用域内重复声明一个变量

我们来看一个例子,

上面的例子,我用var重复声明了变量a, 完全没有任何问题,成功获取到了最后一次声明时赋的值1. 我们把同样的操作放在let上,来看看会出现什么情况

从上面例子中可以看到,不能使用let重复声明同一个变量, 会报错. 我们再看看

 好了,再看一个例子

由上面的两个例子可以看出,用var和let同时声明一个相同的变量,同样报错,因为同样也是,重复声明一个变量. 

因为不能重复声明一个变量,所以看看下面的例子:

 

可以看到, 出错了. 因为函数myFunc的传入参数名为arg,它的作用域是整个函数内部.  而在函数内部又用let声明了同名的变量arg.所以就报错.

 

这个例子中,可以看到完全没有任何问题。因为let的作用域,函数myFunc的传入参数arg,它的作用域是整个函数内部。而我们在函数内部的{}块声明的变量let arg, 作用域只在这个{}内部,所以没有问题

我们看看下面这个例子就知道了:

也就是说 使用let来定义变量时,内层作用域可以定义和外层作用域同名的变量。但是在同一个作用域内,不能定义同名的变量

{{{

   let test = ‘This is test’;
   
   {let test = 'This is test'}

}}};

上面这个例子,是完全没有问题的

看完了let, 我们再来看看const

和在C#中一样,const用来声明一个常量, 既然是常量,那么它一旦声明,常量的值就不能改变.   我们来看一个例子

上面,我们声明了一个常量a=123; 声明后,它的值是不可以改变的。 上面的代码试图修改它的值为355,所以报错

用const声明的变量不能改变值,也就是说, const声明的变量一旦声明,就必须立即初始化,不能说留到以后去赋值.

const test;
Uncaught SyntaxError: Missing initializer in const declaration

上面的例子中可以看到,声明一个常量 const test 但是没有给它赋初始值. 结果就是报错 Missing initializer in const declaration

上面在讲let时,我们说到, let只在其声明的块级作用域中有效, const也是一样,只在它声明的块级作用域中有效果,只要出了这个块级作用域,它就无效了

if(true)
{
    const PI = 3.1415;
}

PI //报错 Uncaught ReferenceError: PI is not defined     =>  Const定义的常量PI 只存在于if条件语句块中

和 let 声明的变量一样,  const声明的变量同样不存在变量提升,也存在暂时性死区TDZ. 只可以先完成声明工作(const是声明和初始化工作),后面才可以开始使用

if(true)
{
    console.log(a);   // 报错 ReferenceError   => 此时a还没有声明没有初始化,直接使用时报错 , 这里到下面的声明初始化为止,也是常量a的暂时性死区TDZ  
     const a = 1;
}

和let一样,const也不允许在同一个作用域内重复声明一个变量, 我们来看几个例子

 我们看到,上面三个例子全部都是错误的,都是在同一作用域内,重复声明了一个变量a

 顶层对象的属性

 JavaScript中的顶层对象, 在浏览器环境中指的是window对象。 在Node中指的是global对象. 在原来的ES5中,顶层对象的属性与全局变量是等价的...这句话怎么理解呢,我们来看一个例子就理解了

window.a = 1; 
a // a此时为1

a = 2;
window.a //此时 window.a 为2

从上面的代码中可以看到, 顶层对象的属性赋值 和 全局变量的赋值,是同一件事.  很多人都认为,顶层对象的属性与全局变量挂钩,是JavaScript语言最大的设计败笔之一.

但是,在ES6中,let命令,const命令声明的全局变量, 都不再属于顶层对象的属性. 如下面的例子

var a = 0;
window.a // 此时,window.a 也是0

let b = 1;
window.b // 此时,window.b是undefined

上面的例子就可以看出, 使用var声明并且初始化了全局变量a, 它同时也是顶层对象window的属性。

继而,我们又用let声明并初始化了全局变量let, 但它就不是顶层对象window的属性,所以此时window对象并没有b属性,所以返回undefined

 

 

 

原文地址:https://www.cnblogs.com/wphl-27/p/9491281.html