重走es6之let和const

let声明定义变量

let跟var的作用差不多,用来定义变量,最明显的区别是:let声明的范围是块作用域,而var声明的范围是函数作用域
块作用域是函数作用域的子集,因此适用于var的作用域限制同样也适用于let。特点:let不允许同一个块作用域中出现冗余声明。

let对比var有如下特点:

  1. 变量声明不会被提升,即在变量声明之前无法使用该变量,否则产生“暂时性死区”
  2. 具有局部作用域,即let声明的变量只能在对应代码块中使用
  3. 不允许重复声明
console.log(b)    //undefined
//console.log(c)   报错:c is not defined
//使用let声明的变量不具有变量提升的作用,它的作用域为局部作用域
{
    var b=20
    let c=30
    console.log(c)
}
console.log(b)
//console.log(c)   //报错:c is not defined

从上述实例中,我们可以知道,let声明的变量不像var声明的变量一样,使用var声明的变量会自动提升到函数作用域顶部。

暂时性死区

let和var的另外一个重要区别,就是let声明的变量不会在作用域中被提升
实例:

console.log(name)   //undefined
var name="zhangsan"

console.log(age)   //报错:age没有定义
let age=18

在解析代码时,javascript引擎也会注意出现在块后面let声明,只不过在此之前不能以任何方式引用未声明的变量。而在let声明之前的执行瞬间就称之为“暂时性死区”,有了“暂时性死区”的概念后,我们的typeof不再是一个百分之百安全的操作。

示例:

typeof x; // ReferenceError
let x;

上述代码中,变量x使用let命令声明,所以在声明之前,都属于x的“死区”,只要在此之前使用到该变量,就会报错。但是如果一个变量在使用之前根本没有被声明,使用typeof反而不会报错。
示例:

typeof x // "undefined"

但是有些"暂时性死区"是比较隐蔽的,不太容易被发现。

function bar(x = y, y = 2) {
  return [x, y];
}
bar(); // 报错

上面代码中,调用bar函数之所以报错(某些实现可能不报错),是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于“死区”。如果y的默认值是x,就不会报错,因为此时x已经声明了。如果y的默认值是x,就不会报错,因为此时x已经声明了。

function bar(x = 2, y = x) {
  return [x, y];
}
bar(); // [2, 2]

另外,下面的代码也会报错,与var的行为不同。

// 不报错
var x = x;

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

上面代码报错,也是因为暂时性死区。使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“。
总结:ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

for循环中的let声明

在let出现之前,for循环定义的迭代变量会渗透到循环体外部
示例:

var arr=[]
for(var i = 0 ; i<3;i++){
    arr[i]=function (){
        console.log(i)
    }
}
arr[0]()  //arr[0]表示的时函数,运行需要给()  ====>3
arr[1]()  //=========>3
arr[2]()  //=======>3

全都是3 ,因为变量i是var命令声明的,在全局范围内都有效,而全局只有一个变量i。每一次循环,变量i的值都会发生改变,
而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 3。

改成使用let之后,这个问题就消失了,因为迭代变量的作用域仅限于for循环内部
示例:

let arr2=[]
for(let i = 0 ; i<3;i++){
    arr2[i]=function (){
        console.log(i)
    }
}
arr2[0]()  //arr[0]表示的时函数,运行需要给()  ====>0
arr2[1]()  //=========>1
arr2[2]()  //=======>2

而在使用let声明迭代变量时,JavaScript引擎在后台会为每一个迭代循环声明一个新的迭代变量。每个function引用的都是不同的变量实例,所以console.log(i)输出的就是0,1,2,也就是我所期望的值,也就是循环执行过程中每个迭代变量的值。

let声明不允许重复声明

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

//let声明的变量不允许重复声明
// 报错    Identifier 'a' has already been declared
function func() {
    let a = 10;
    var a = 1;
  }
  
  // 报错
  function func() {
    let a = 10;
    let a = 1;
  }

因此,不能在函数内部重新声明参数。

function func(arg) {
  let arg;
}
func() // 报错

function func(arg) {
  {
    let arg;
  }
}
func() // 不报错

块级作用域

为什么需要块级作用域?
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景
第一种场景,内层变量可能会覆盖外层变量

var tmp = new Date();

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}
f(); // undefined

原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

第二种场景,用来计数的循环变量泄露为全局变量

var s = 'hello';

for (var i = 0; i < s.length; i++) {
  console.log(s[i]);
}

console.log(i); // 5

上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

在es6中新增了块级作用域,块级作用域的特性:

  1. 允许块级作用域的任意嵌套。
  2. 内层作用域可以定义外层作用域的同名变量
  3. 块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了

注意点:

  1. JavaScript引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同的标识符不会报错,而是因为同一块中没有没有重复声明。
  2. 对于声明的冗余报错不会因为混用了let和var而受到影响。这两个关键词声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在。

块级作用域与函数声明

如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在浏览器的实现可以不遵守上面的规定,有自己的行为方式。

  • 允许在块级作用域内声明函数。
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
  • 同时,函数声明还会提升到所在的块级作用域的头部

还有一个需要注意的地方。ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。

// 第一种写法,报错
if (true) let x = 1;

// 第二种写法,不报错
if (true) {
  let x = 1;
}

上面代码中,第一种写法没有大括号,所以不存在块级作用域,而let只能出现在当前作用域的顶层,所以报错。第二种写法有大括号,所以块级作用域成立

const常量声明

const声明一个只读的常量。一旦声明,常量的值就不能改变。
const具有与let相同的特性,此外会有一些其他特性:

  1. 变量声明不会被提升,即在变量声明之前无法使用该变量
  2. 具有局部作用域,即let声明的变量只能在对应的代码块中使用
  3. 不允许重复声明
  4. const声明的变量在声明的时候就需要赋值,并且只能赋值一次,不能修改
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.

const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值,否则报错
const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

es6中声明变量的6种方式

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加letconst命令,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值