ES6 let 和 const 命令

本文详细介绍了ES6中的let和const命令,它们引入了块级作用域,解决了var命令的一些问题。let命令用于声明变量,只在声明所在的代码块内有效,不存在变量提升,存在暂时性死区。const命令用于声明常量,其值不可改变,但也存在暂时性死区。块级作用域使得函数声明的行为变得复杂,浏览器环境下的处理方式可能导致意外的结果。此外,文章还讨论了块级作用域的其他特性及其在实际编程中的应用。
摘要由CSDN通过智能技术生成

let 命令

基本用法

用于声明变量,用法类似于var,但所声明的变量,只在let命令所在的代码块内有效。

{
  let a = 1;
  var b = 2;
}
​
a // ReferenceError: a is not defined.
b // 1

上面代码在代码块之中,分别用letvar声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。let常搭配for循环使用。

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
 };
}
​
a[6](); // 10
​
var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
      console.log(i);
    };
}
​
a[6](); // 6

由上述代码可知,var声明的i变量在全局范围内有效,所以全局范围内只有一个变量i。每次循环都会使i的值+1,因此循环内数组a的函数内的console.log(i)中的i指向全局的同一个i。而let声明的i变量只在本轮循环有效,每次循环的i都是一个新的变量(在JavaScript引擎内部会记住上一轮循环的值,初始化本轮变量i时,就在上一轮循环的基础上进行计算)。

除此之外,for循环设置循环变量的部分使一个父作用域,而循环体内部使一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = "abc";
  console.log(i);
}
​
// abc
// abc
// abc

在同一个作用域中不可使用let重复声明同一个变量

// 报错
function foo() {
  let a = 10;
  var a = 2;
}
​
// 报错
function bar() {
  let a = 1;
  let a = 2;
}

不可以在函数内部重新声明参数

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

不存在变量提升

// var
console.log(a); // undefined
var a = 2;
​
// let
console.log(b); // 报错ReferenceError
let b = 2;

暂时性死区(Temporal Dead Zone, TDZ)

在代码块内,使用let命令声明变量之前,该变量都是不可用的。

var tmp = 1;
​
if (true) {
  tmp = "a"; // ReferenceError
  let tmp;
}

虽然存在全局变量tmp,但在块级作用域内let又声明了一个局部变量tmp,导致后者绑定了该块级作用域,所以在let声明变量前,对tmp赋值会报错。

暂时性死区意味着typeof不再是一个绝对安全的操作。

typeof a; // ReferenceError
let x;

如果一个变量没有声明,使用typeof反而不会报错

typeof undeclared_variable // "undefined"

举例:一些比较隐蔽的死区

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

在上述代码中,由于bar(x = y, y = 2)首先读取参数x,并令参数x默认值等于另一个参数y,但此时y还没有声明,属于死区;与之相对的,若y的默认值是x,则不会报错,因为此时x已经声明。

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

由于暂时性死区以下代码也会报错。

var x = x; // undefined
​
let x = x; // ReferenceError: x is not defined

const 命令

基本用法

const声明一个只读的常量,该常量的值在声明后不可改变。

const声明后必须立即初始化

const作用域与let相同,只在声明所在的块级作用域内有效

const同样存在暂时性死区,只能在声明的位置后调用

const只能在声明后调用

const PI = 3.141592;
PI // 3.141592
PI = 3; // TypeError: Assignment to constant variable
const a; // SyntaxError: Missing initializer in const declaration
​
if (true) {
  const b = 1;
}
b // Uncaught ReferenceError: b is not defined
​
if (true) {
  console.log(c); // ReferenceError
  const c = 1;
}
​
var x = 1;
let y = 2;
const x = 2;
const y = 3; // SyntaxError: Identifier 'a' has already been declared

const 命令的本质

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

const foo = {};
​
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
​
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

下面是另一个例子。

const a = [];
a.push('Hello'); // 可执行
a.length = 0;    // 可执行
a = ['Dave'];    // 报错

上面代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。

如果真的想将对象冻结,应该使用Object.freeze()方法。

const foo = Object.freeze({});
​
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

上面代码中,常量foo指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。

除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。

var constantize = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
  });
};

ES6 的块级作用域

function foo() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}
function foo() {
  let n = 5;
  if (true) {
    var n = 10;
  }
  console.log(n); // SyntaxError: Identifier 'n' has already been declared
}
function foo() {
  let n = 5;
  if (true) {
    n = 10;
  }
  console.log(n); // 10
}
function foo() {
  var n = 5;
  if (true) {
    var n = 10;
  }
  console.log(n); // 10
}

上述代码表示外层代码块不受内层代码块的影响。

ES6 允许块级作用域的任意嵌套,并且内层作用域可以定义外层作用域的同名变量

{{{{{
  { let a = 1 }
  console.log(a); // 报错
}}}}}
{{{{{
  let a = 1;
  { let a = 1 }
}}}}}

块级作用域使得匿名立即执行函数表达式(匿名IIFE)不再必要

// IIFE 写法
(function () {
  var tmp = ...;
  ...
})
​
// 块级作用域写法
{
  let tmp = ...;
  ...
}

块级作用域与函数声明

ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。

ES6 规定,块级作用域之中可以声明函数,函数声明语句的行为类似于let,在块级作用域之外不可引用。

function f() {
  console.log('outside');
}
​
(function () {
  if (false) {
    function f() { console.log('inside'); }
  }
  
  f();
}());
上述代码在 ES5 中运行,会得到"inside",实际的运行代码如下

function f() { console.log('outside'); }
​
(function() {
  function f() { console.log('inside'); }
  if (false) {}
  f();
}())

但是在 ES6 中,由于块级作用域,理论上在if(false) {}中声明的f()不应影响在块级作用域外的f(),即理论上打印的结果应为"outside·"。

但在实际的 ES6 浏览器中运行该代码会报错

// 浏览器的 ES6 环境
function f() {
  console.log('outside');
}
​
(function () {
  if (false) {
    function f() { console.log('inside'); }
  }
  
  f();
}());
// Uncaught TypeError: f is not a function

上述代码在 ES6 浏览器中,都会报错,原因如下:

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

  • 允许在块级作用域内声明函数。

  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。

  • 同时,函数声明还会提升到所在的块级作用域的头部。

注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。

因此,上述代码实际运行如下:

// 浏览器的 ES6 环境
function f() {
  console.log('outside');
}
​
(function () {
  var f = undefined; // 将函数声明提升到函数作用域头部
  if (false) {
    function f() { console.log('inside'); }
  }
  
  f();
}());
// Uncaught TypeError: f is not a function

因此,环境导致的结果差距,应避免在块级作用域内声明函数。若需要,也应写成函数表达式形式。

// 块级作用域内的函数声明语句,建议不要使用
{
  let a = 'inside';
  function f() {
    return a;
  };
}
​
// 块级作用域内优先使用函数表达式
{
  let a = 'inside';
  let f = function() {
    return a;
  };
}

ES6 的块级作用域必须有大括号,如果无大括号,则 JS 引擎认为不存在块级作用域

if (true) let x = 1; // 没有大括号,报错
​
if (true) {
  let x = 1; // 不报错
}

参考:阮一峰的ES6教程

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值