【JS】JavaScript语句和声明之let,const

1,093 阅读5分钟

let 语句声明一个块级作用域的本地变量,且可选地将其初始化为一个值。

const 语句声明的常量块级作用域,与使用let语句定义的变量相似。常量的值不能通过重新赋值来改变,且不能重新声明

1.语法

let 变量名1 [= 变量值1] [, 变量名2 [= 变量值2]] [, ..., 变量名N [= 变量值N]];

  • 变量名1,变量名2,...,变量名N:变量名。 必须是合法的标识符。
  • 变量值1,变量值2,...,变量值N:变量的初始值。 可以是任意合法的表达式。

const name1 = value1 [, name2 = value2 [, ... [, nameN = valueN]]];

  • name1,name2,...,nameN:常量名称。 可以是任意合法的标识符。
  • value1,value2,...,valueN:常量值。 可以是任意合法的表达式。

2.描述

let声明的变量、语句或表达式都被限制在块级作用域。与var关键字不同的是,var声明的变量只能是全局或整个函数块的。letvar不同之处在于前者是在编译时才被初始化

const声明创建一个常量,其作用域可以是全局或本地声明的块。 与var变量不同,全局常量不会变为window对象的属性。需要一个常数的初始化器,即必须在声明时指定它的值(这是有道理的,因为以后不能更改)。

const声明创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。比如,在引用内容是对象的情况下,这就意味着可以改变对象的内容(如,其参数)。

const声明创建常量不能和它所在作用域内的其他变量或函数拥有相同的名称

const一样,let也不会在全局声明时(在最顶部的范围)创建window对象的属性。

3.常量的特性

注意: 常量在声明时可以使用大小写,但通常情况下全部用大写字母

  • 不能重新赋值
  • 不能重新声明
const MYFAVNUM = 7;  // 定义常量MYFAVNUM 并赋值7
MYFAVNUM = 20;   // 报错(不能重新赋值)
console.log("my favorite number is: " + MYFAVNUM ); // 输出 7

const MYFAVNUM = 20; //报错(不能重新声明)
var MYFAVNUM = 20;  //  MYFAVNUM 保留给上面的常量,这个操作会失败
let MYFAVNUM = 20; // 也会报错

// 注意:块范围的性质很重要
if (MYFAVNUM === 7) { 
    let MYFAVNUM = 20;// 没问题,并且创建了一个块作用域变量 MYFAVNUM 
    console.log('my favorite number is ' + MYFAVNUM );  // MYFAVNUM 现在为 20
    var MYFAVNUM = 20; //报错(这被提升到全局上下文并引发错误)
}
console.log("my favorite number is " + MYFAVNUM ); // MYFAVNUM 依旧为 7
  • 常量需要一个初始值
const FUN;  // 报错Uncaught SyntaxError: Missing initializer in const declaration(常量要求一个初始值)
  • 常量可以定义成对象
const MY_OBJECT = {"key": "value"};
MY_OBJECT = {"OTHER_KEY": "value"}; // 报错(重写对象和上面一样会失败)
  • 对象属性并不在保护的范围内,下面这个声明会成功执行
MY_OBJECT.key = "otherValue";
  • 也可以用来定义数组
const MY_ARRAY = [];
MY_ARRAY.push('A'); // ["A"] // 可以向数组填充数据
MY_ARRAY = ['B'];  // 但,将一个新数组赋给变量会引发错误!报错。

4.作用域规则

let声明的变量只在其声明的块或子块中可用,这一点,与var相似。二者之间最主要的区别在于var声明变量的作用域是整个封闭函数。举例子如下:

function varTest() {
  var a = 1;
  {
    var a = 2;       // 同样的变量!
    console.log(a);  // 2
  }
  console.log(a);    // 2
}
varTest();
function letTest() {
  let a = 1;
  {
    let a = 2;       // 不同样的变量!
    console.log(a);  // 2
  }
  console.log(a);    // 1
}
letTest();

在代码和方法的最顶端,let不像var一样,let不会在全局对象里新建一个属性。即位于函数或代码顶部的var声明会给全局对象新增属性, 而let不会。比如:

var a = 'global';
let b = 'global';
console.log(window.a);  // "global"
console.log(window.b);  // "undefined"

5. 模仿私有成员

在处理构造函数时,可通过let声明而不是闭包来创建一个或多个私有成员。

var Thing; //定义全局变量Thing
{
  let privateScope = new WeakMap();
  let counter = 0;
  Thing = function() {  //全局变量Thing指向函数对象
    this.someProperty = 'fun';
    privateScope.set(this, {
      hidden: ++counter,
    });
  };

  Thing.prototype.showPublic = function() {
    return this.someProperty;
  };
  Thing.prototype.showPrivate = function() {
    return privateScope.get(this).hidden;
  };
}
console.log(typeof privateScope); // "undefined" ,privateScope不存在全局作用域

var thing = new Thing();
console.log(thing);  // Thing {someProperty: "fun"}
thing.showPublic();  // "fun"
thing.showPrivate(); // 1

可用var创建和闭包具有相同隐私模式的局部变量,但它们需要函数作用域(通常是模块模式中的IIFE),而不仅仅是上面示例中的块作用域。

6.重复声明

  • 在同一个函数作用域块作用域中重复声明同一个变量会引起SyntaxError(语法错误)。
if (a) {
  let b;
  let b; //报错:Uncaught SyntaxError: Identifier 'b' has already been declared.
}

下面这种varlet合并的声明方式也会报SyntaxError错误, 因为var会将变量提升至块顶部, 这就导致隐式地重复声明变量。

let a = 1;
{
  var a = 2; // 报错 Uncaught SyntaxError: Identifier 'a' has already been declared
}
  • switch语句中只有一个块,可能因此而遇到错误。
let a = 1;
switch(a) {
  case 0:
    let b;
    break;
  case 1:
    let b;  //报错:Uncaught SyntaxError: Identifier 'b' has already been declared
    break;
}

但,需要特别指出的是,一个嵌套在case 子句中的块会创建一个新的块作用域的词法环境,就不会产生上诉重复声明的错误。如下:

let a = 1;
switch(a) {
  case 0: {  //新的块级作用域
    let b;
    break;
  }  
  case 1: { //新的块级作用域
    let b;
    break;
  }
}

如果你觉得这篇文章对你有帮助,请点赞支持一下哦!