Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ES6 - let / const #41

Open
jtwang7 opened this issue Oct 7, 2021 · 1 comment
Open

ES6 - let / const #41

jtwang7 opened this issue Oct 7, 2021 · 1 comment

Comments

@jtwang7
Copy link
Owner

jtwang7 commented Oct 7, 2021

参考文章:

let 命令

作用

ES6 新增了 let 命令,用来声明变量。它的用法类似于 var,但是所声明的变量,只在 let 命令所在的代码块内有效。
以 for 循环为例:

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

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

当循环变量 i 通过 var 声明时,其在全局范围内都有效,所以全局只有一个变量 i。每一次循环,变量 i 的值都会发生改变,而循环内被赋给数组 a 的函数内部的 console.log(i),里面的 i 指向的就是全局的 i。也就是说,所有数组 a 的成员里面的 i,指向的都是同一个 i,导致运行时输出的是最后一轮的i的值,也就是 10。
当循环变量 i 通过 let 声明时,当前的 i 只在本轮循环有效,所以每一次循环的 i 其实都是一个新的变量,所以最后输出的是 6。

如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?
这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量 i 时,就在上一轮循环的基础上进行计算。

此外,在 for 循环中,设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

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

上述代码成功运行,说明函数内部的变量 i 与循环变量 i 不在同一个作用域,有各自单独的作用域,因为同一个作用域不可使用 let 重复声明同一个变量。

特点

不存在变量提升

var 命令会发生“变量提升”现象。变量提升实际指的是变量声明提升,var 声明的变量会被提升到全局或函数作用域的顶部,但变量的初始化赋值不会被提升,而是要等待到相应的赋值语句执行,这就导致变量可以在声明之前使用,但此时值为 undefined。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

等价于

var foo;
console.log(foo);
foo = 2;

上面代码中,变量 foo 用 var 命令声明,会发生变量提升,即脚本开始运行时,变量 foo 已经在开头被声明了,但是没有初始化赋值,所以会输出 undefined。

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

变量 bar 用 let 命令声明,不会发生变量提升。这表示在声明它之前,变量 bar 是不存在的,这时如果用到它,就会抛出一个错误。

暂时性死区

let 声明的变量会绑定当前变量所在的块级作用域,不受外部影响。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

上面代码中,块级作用域内let声明了一个局部变量 tmp,导致后者绑定这个块级作用域。因此,块级作用域中 tmp 变量指向 let 声明而非 var 的全局声明,由于 let 不存在变量提升,所以在 let 声明变量前,对 tmp 赋值会报错。

ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经确定,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

隐蔽的暂时性死区

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

函数声明中,函数条件与函数体处于同级作用域,条件块在函数体之前,并且函数形参会在函数调用时自动声明。因此上述代码等同于:

{
  let x = y; // 报错
  let y = 2;
  return [x, y];
}

可以看到,在声明形参的时候,let y = 2 之前的区域属于 y 的暂时性死区,因此 let x = y 使用 y 变量时报错。

不允许重复声明

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

因此在函数声明中,不能在函数体内重复声明形参。(函数体与条件块处于同级作用域)

// 报错:var a 声明了变量并发生变量提升,因此 a 变量实际在 let 声明前已存在
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() // 不报错:函数体内嵌套了块级作用域,不与形参处于同级作用域
@jtwang7
Copy link
Owner Author

jtwang7 commented Oct 7, 2021

参考文章:

const 命令

作用

const 声明一个只读的常量。一旦声明,常量的值就不能改变。因此,const 一旦声明变量,就必须立即初始化,不能留到以后赋值。

const PI = 3.1415;
PI = 3; // TypeError: Assignment to constant variable. - const 声明的常量不可修改

const foo; // SyntaxError: Missing initializer in const declaration - const 声明的同时必须立即初始化

特点

const 特点同 let 一样:

  • 只在所在的代码块内生效
  • 不存在变量提升
  • 存在暂时性死区
  • 不允许重复声明

本质

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

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错 - const 保证了引用类型的指针地址不变
foo = {}; // TypeError: "foo" is read-only

如果真的想将对象冻结,应该使用 Object.freeze 方法,冻结的对象不能添加新的属性。

const foo = Object.freeze({});

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant