您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

ES6+ const

我们学习了使用 let 取代 var 声明变量,但是很多情况下,我们希望我们声明的变量不能被。在 ES5 中不能直接声明常量,如果想声明不可的变量需要借助 defineProperty 。ES6 为了弥补这方面的缺失,新增了 const 语句用于声明常量。本节我们还将学习到 letconstvar 的区别。

const 的使用类似于 let,不同的是 const 在声明时必须初始化值,而且这个值不能被改变。

@H__47@const PI = ;  // 定义圆周率常量 PI

上面的声明了常量 PI,如果声明时没有初始化值时,则会抛出异常。

@H__47@const PI;
// Uncaught Error: Missing initializer in const declaration

const 语句声明的是常量,并且这个常量是不能被更改的:

@H__47@const PI = ;  // 定义圆周率常量 PI
PI =              // Uncaught TypeError: Assignment to constant variable.

这里声明了圆周率常量,我们知道圆周率是固定的不会被改变的,如果对 PI 重新赋值,则会抛出不能给常量分配变量的。

但如果使用 const 声明的变量是对象类型的话,我们可以改变对象里的值,这是因为 const 存的变量只是地址的引用,所以只要不改变引用的值,就不会报错。如下面的例子:

@H__47@const obj = {};
obj.a =           // 12
@H__47@const arr = [];
arr.push();       // 12
arr = {};           // Uncaught TypeError: Assignment to constant variable.

使用 const 声明了对象和数组,然后对象的和数组的值都不会报错,这是因为我们没有改变 obj 和 arr 的引用。如果对 arr 进行重赋值,则会报不能给常量分配变量的。

由于 constlet 都是声明变量使用的,所以他们的使用基本相同,下面总结一下它们共有的,详情参考的 let 的使用:

在 ES6 之前是不能的,如果想的话,需要借助 ES5 中的 defineProperty ,这里我们写个示例:

@H__47@function setConst(key, value, obj) {
  Object.defineProperty(window, key, {
    @H__47@get: @H__47@function(){
      @H__47@return value;
    },
    @H__47@set: @H__47@function(){
      console.error('Uncaught TypeError: Assignment to constant variable');
    },
  });
}
setConst('PI', );
console.log(PI)     // 3.1415
PI = ;             // Uncaught TypeError: Assignment to constant variable.

上面的是的,使用了 ES5 的 Object.defineProperty ,这个允许在对象上定义新的,具体使用详情可以参考 ES5 的相关文档说明。这里我们会在 window 对象上,也可以自己定义对象进行,可以实现局部作用域的。通过向 setConst 中传入指定的变量和值来声明常量,这样我们就在 ES5 中实现了常量的概念。由此可见,ES6 的 const 带来的好处。

在工作中经常会遇到 varletconst 以下几个问题:

这些问题在上面的讲解中都有提到过,这里我们总结一下:

变量还没有被声明,但是我们却可以使用这个未被声明的变量,这种情况就叫做提升,并且提升的是声明。

console.log(a); // undefined
@H__47@var a = 

这个其实可以写出下面这样的方式:

@H__47@var a;
console.log(a);  // undefined
a = 

其实变量提升就是,把变量名统一地提升到作用域的顶部进行率先定义,这也就是变量提升。不仅变量可以被提升,也可以被提升,并且的提升要优于变量的提升,提升会把整个挪到作用域顶部。

暂时性死区主要是针对 let 和 const 而言的,因为它们不存在变量提升,所以在它们声明变量之前是不能使用的,这个时候如果使用了就会报错,这时候就形成了暂时性的死区,也就是不能被引用。

{
  console.log(name);  // ReferenceError: name is not defined. 
  @H__47@let num = ;
}

定义前被引用则会抛出异常。

上面两个了,再看它们的区别其实就是显而易见的,主要从以下几个方面来分析它们之区别:

下面的实例主要考察作用域的问题,下面的的结果是什么?

@H__47@for (@H__47@var i = ; i < ; i++) {
  setTimeout(() => {
    console.log(i);
  }, *i);
}
// 3
// 3
// 3

分析: 这里由于 setTimeout 是异步回调,所以 for 循环运行完后才会执行 setTimeout 内的栈。使用 var 声明的变量是全局作用域的,循环完后 i 的值是 3,所以会间隔 1s 打印 3。想要 i 的值不被覆盖,这时可以借助 let 的块级作用域的特性,来这个问题:

@H__47@for (@H__47@let i = ; i < ; i++) {
  setTimeout(() => {
    console.log(i);
  },  * i);
}
// 0
// 1
// 2

分析: 这里循环的变量 i 是 let 声明的,当前的 i 只在本轮循环有效,所以每一次循环的 i 其实都是新的变量。你可能会问,如果每一轮循环的变量 i 都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为在 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量 i 时,就在上一轮循环的基础上进行计算。

另外,for 循环还有特别之处,就是设置循环变量的那部分是父作用域,而循环体内部是单独的子作用域。这样每次定义的 i 都是局部作用域下的变量,所以在异步之后,i 的值是不会变的,所以依次打印 0 到 3 的结果。

本节我们学习了使用 const 来声明常量,这里需要注意以下几点:


联系我
置顶