let

let用于定义一个变量,这和ES5中的var是一样的作用。但是var定义的变量会存在变量污染的问题。

  1. var a = 1;
  2. var a = 2;
  3. console.log(a); // 2

虽然可以通过立即执行函数来解决全局变量污染的问题,但是在立即执行函数内的问题无法得倒解决。

  1. (function(){
  2. var a = 1;
  3. var a = 2;
  4. console.log(a); // 2
  5. })()

ES6新增了let来定义变量,let有以下几个特点:

1、同一个变量不容许重复声明

这个特点也就直观的解决了var变量污染的问题

  1. let a = 1;
  2. let a = 2; // Uncaught SyntaxError: Identifier 'a' has already been declared
  3. var b = 1;
  4. let b = 2; // Uncaught SyntaxError: Identifier 'a' has already been declared
  1. // 在函数内部声明也是一样的
  2. function test() {
  3. let a = 1;
  4. let a = 2; // Uncaught SyntaxError: Identifier 'a' has already been declared
  5. }
  1. // let 声明不能和形参同名
  2. function test(a) {
  3. let a = 2;
  4. console.log(a);
  5. }
  6. test(1)
  1. // 变量也不能和函数同名
  2. {
  3. let a = 1;
  4. function a() {}
  5. console.log(a); // Identifier 'a' has already been declared
  6. }

2、块级作用域

ES5var是不受块作用域{}限制的

  1. if(true){
  2. var a = 1;
  3. }
  4. console.log(a); // 1

ES6let声明的变量在块内{}是独立的

  1. if(true){
  2. let a = 1;
  3. }
  4. console.log(a); // a is not defined
  1. function test(a) {
  2. {
  3. let a = 2;
  4. console.log(a); // 2
  5. }
  6. console.log(a); // 1
  7. }
  8. test(1);

3、暂时性死区

let不会进行预编译的变量提升,在let之前访问变量会存在暂时性死区,使用var定义的变量会进行变量提升

  1. var a = a;
  2. console.log(a); // undefind
  3. let b = b;
  4. console.log(b); // Cannot access 'b' before initialization
  5. // 预编译阶段:
  6. var a;
  7. a = a;
  8. // let 不会进行变量提升,所以报错:初始化前无法访问“b”
  1. // 全局作用域内,无法在 let 之前访问变量
  2. console.log(a); // Cannot access 'a' before initialization
  3. let a = 10;
  1. // 函数的作用域内也无法在 let 之前访问变量
  2. function test() {
  3. console.log(a); // Cannot access 'a' before initialization
  4. let a = 10;
  5. }
  6. test();
  1. console.log(typeof a); // Cannot access 'a' before initialization
  2. let a = 1;

4、let只能在当前的块级作用域内生效

这个特点可以和特点2合并成一个

  1. {
  2. // let 声明的变量只作用于当前 {} 内
  3. let a = 2;
  4. }
  5. console.log(a); // a is not defined
  1. function test(){
  2. let a = 2;
  3. }
  4. console.log(a); // a is not defined
  1. if(true){
  2. var a = 2;
  3. let b = 3;
  4. }
  5. console.log(a); // 2
  6. console.log(b); // b is not defined
  1. // for 循环中 () 然后属于 {} 块作用域的范畴
  2. for (let i = 0; i < 3; i++) {}
  3. console.log(i); // i is not defined
  1. // 针对 for 循环还有一个需要注意的地方
  2. for (let i = 0; i < 10; i++) {
  3. let i = "a";
  4. console.log(i); // 10 个 a
  5. }
  6. for (let i = 0; i < 10; i++) {
  7. var i = "a";
  8. console.log(i); // 报错
  9. }
  10. // 解析
  11. // 我们可以把()和 {} 理解为两个块作用域
  12. // 第一个循环相当于一个父子块作用域
  13. {
  14. let i;
  15. {
  16. let i;
  17. }
  18. }
  19. // 第二个循环的时候,因为 var 会变量提升,所以会出现这样
  20. var i;
  21. {
  22. let i;
  23. {
  24. }
  25. }

🛳 下面是一个经典的**for**循环执行方法的题目:

  1. var arr = [];
  2. for (var i = 0; i < 5; i++) {
  3. arr.push(function () {
  4. console.log(i);
  5. });
  6. }
  7. for (var i = 0; i < 5; i++) {
  8. arr[i]();
  9. }
  10. // 解析
  11. // 答案: 0 1 2 3 4
  12. // 这道题猛然一看就认为是 5 个 5
  13. // 因为 var 会变量提升,for 循环可以拆分出来
  14. var i = 0;
  15. for (;i < 5;) {
  16. // ../
  17. i++
  18. }
  19. // 实际上第二次循环的时候,又 var i 相当于把全局的 i 覆盖了
  20. var i = 5;
  21. for (var i = 0; i < 5; i++) {
  22. // i = 0
  23. // i = 1
  24. }
  25. // 所以覆盖的时候去执行方法,方法去寻找全局的 i,此时就打印出来 0 1 2 3 4
  1. // 正常的题目是这样滴:
  2. var arr = [];
  3. for (var i = 0; i < 5; i++) {
  4. arr.push(function () {
  5. console.log(i);
  6. });
  7. }
  8. for (var j = 0; j < 5; j++) {
  9. arr[j](); // 这个时候打印的肯定是 5 个 5
  10. }
  11. // ==========
  12. // 当我们把 var 更改为 let 的时候就可以正常打印出 0 1 2 3 4 了
  13. var arr = [];
  14. for (let i = 0; i < 5; i++) {
  15. arr.push(function () {
  16. console.log(i);
  17. });
  18. }
  19. for (var j = 0; j < 5; j++) {
  20. arr[j]();
  21. }
  22. // ==========
  23. // 解析
  24. // 因为 let 有块作用域的功能,所以第一次循环的时候相当于生成 5 个独立的块
  25. {
  26. let i = 0;
  27. function () {
  28. console.log(i);
  29. }
  30. }
  31. // 第二次循环的时候,函数会往上找块作用于内的 i ,所以也就能正常的打印了

const

constlet基本上类似,只不过const主要用来定义常量(不可更改的数据)。
const的特点:

1、定义常量必须赋值

  1. const a;
  2. console.log(a) // Missing initializer in const declaration

2、定义常量后不可更改

  1. const a = 10;
  2. a = 11; // TypeError: Assignment to constant variable.

3、不容许重复声明

  1. const a = 10;
  2. const a = 12; // Identifier 'a' has already been declared
  3. var a = 10;
  4. const a = 12; // Identifier 'a' has already been declared

4、暂时性死区

let定义变量是一样的

  1. console.log(a); // Cannot access 'a' before initialization
  2. const a = 10;

5、块作用域

  1. {
  2. const a = 10;
  3. }
  4. console.log(a); // a is not defined

6、定义引用对象无效

const对于「原始数据类型」后更改值是不被容许的,但是定义「引用数据类型」后更改它的属性仍然是可以的,也就是说**const**只能保证引用值的指针不被改变,并不能保证引用值的属性不被改变

  1. const person1 = {};
  2. person1.name = "张三";
  3. console.log(person1); // {name: "张三"}
  4. const person2 = {};
  5. person2 = {}; // Assignment to constant variable.
  1. // 我们可以利用 Object.freeze() 方法来冻结对象,达到不能操作属性的目的
  2. const obj = {};
  3. Object.freeze(obj);
  4. obj.name = "张三"
  5. console.log(obj); // {}

顶层对象

在使用var定义数据的时候,相当于在window对象上新增了一个属性,这样某些情况下就会造成混乱。

  1. var a = 1;
  2. console.log(window.a); // 1

letconst为了解决这个问题,定义的数据不会存在顶层对象window上。

  1. let a = 1;
  2. const b = 2;
  3. console.log(window.a); // undefined
  4. console.log(window.b); // undefined