let和const

名字 特性说明
var 变量; 能重复声明; 函数级; 顶层对象的属性; 不限制修改
let 变量; 不能重复声明; 块级; 不属于顶层对象的属性; 可修改
const 常量; 不能重复声明; 块级; 不属于顶层对象的属性; 声明和赋值必须是同时进行; "不"可修改[ 实际上并非完全不可修改,如果引用类型时 修改里面的值和长度是可以的。如果真的想把类型不允许修改,应该使用Object.freeze方法]

一、let命令

基本用法
*只在`let`命令所在的代码块内有效
{
  let a = 10; var b = 1;
}
a // ReferenceError: a is not defined.
b // 1

-------------------
for (let i = 0; i < 10; i++) {}
console.log(i); //ReferenceError: a is not defined.

for (var i = 0; i < 10; i++) {}
console.log(i); //10
-------------------

*`for循环时的let还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。`
for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i); 
}
输出了 3 次`abc`。这表明函数内部的变量`i`与循环变量`i`不在同一个作用域,有各自单独的作用域。
-------------------
变量提升 let在同个作用域,在执行声明之前是暂时性死区(TDZ)
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

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

-------------------

var tmp = 123;
if (true) {
  tmp = 'abc'; // 变量提升 并开启TDZ开始  ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ结束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}

*注意 在所以死区时 typeof x; // ReferenceError*
意味着`typeof`不再是一个百分之百安全的操作;

typeof x; //声明前检验 ReferenceError
let x;

typeof peo; //"undefined"无声明的
不允许重复声明
// 报错
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() // 不报错
二、Es6块级作用域
function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}
-------------------
*ES6 允许块级作用域的任意嵌套。内层可读外层的变量
{{{{
  let insane = 'Hello World';
  let name = 'tom';
  {
    console.log(name); //'tom'
    let insane = 'Hello'
  }
  console.log(insane); //Hello World
}}}};

-------------------
块级作用域与函数声明

环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

// 块级作用域内部的函数声明语句,建议不要使用
{
  let a = 'secret';
  function f() {
    return a;
  }
}

// 块级作用域内部,优先使用函数表达式
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}

注意:ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。

// 第一种写法,报错
if (true) let x = 1;

// 第二种写法,不报错
if (true) {
  let x = 1;
}

三、const命令

在块级作用域 和变量提升暂时性死区方面,效果和用法let保持一致。

基本用法

普通认为:const声明一个只读的常量。一旦声明,常量的值就不能改变。
本 质 上: const实际上保证的,并不是变量的值不得改动,而是变量指向的引用的数据不得改动,如下:

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

简单类型时:

//简单类型 修改值报错
---Es5常量 不允许修改---
    Object.defineProperty(window,"my_PI",{
        value:3.1415926,  
        writable:false  
    })
    console.log(my_PI);//3.1415926
    my_PI = 3;//能执行 但不起效
    my_PI//3.1415926
    
---Es6常量 不允许修改---
const PI = 3.1415;
PI = 3;// TypeError: Assignment to constant variable.

*声明和赋值必须同时进行
const foo; //声明没值 报错
// SyntaxError: Missing initializer in const declaration

-------------------

复合类型时:

-------对象的例子--------
*`引用源没有变化 可以随便更改里面的值`
const foo = {}; 
foo.prop = 123; // 为 foo 添加一个属性,可以成功
foo.prop // 123 
foo.prop = 456 // 再次修改同样成功;
delete foo.prop //删除里面的值 同样成功

*`修改到引用源都不成功 将 foo 指向另一个对象,就会报错`
foo = {}; // TypeError: "foo" is read-only
foo = {prop: 111}; // TypeError: "foo" is read-only
delete foo; //false; 修改到引用源了

-------数组的例子--------
*`引用源没有变化 可以随便更改里面的值`
const arr = [];
arr.push('Hello');//可执行 arr》["Hello"]
arr[2] = 'wellcome';//可执行 arr》["Hello",empty,"wellcome"]
arr.splice(1,1,"tom") //可执行 arr》["Hello","tom","wellcome"]
...
arr.length = 0; //可执行 arr》[]

*`修改到引用源都不成功 将 arr 指向另一个数组,就会报错`
arr = [];//TypeError
arr = ["see"];//TypeError

如果真的想将对象冻结(复杂类型不允许修改),应该使用Object.freeze方法。~~~~

const foo = Object.freeze({});

// 常规模式时,执行但没效果;
// 严格模式时,该行会报错
foo.prop = 123;
console.log(foo);//{}
--------------------
数组同上
const arr = Object.freeze([]);
arr[0] = "hi";
console.log(arr);//[]
--------------------

*除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数
var constantize = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
  });
};

四、顶层对象的属性

顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。

window.a = 1;
a // 1

a = 2;
window.a // 2

ES6 为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;
let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined

const c=12;
window.c //undefined

五、globalThis 对象

  • 全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
  • 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。即指向window或undefined。
  • 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么evalnew Function这些方法都可能无法使用。

资料:
ECMAScript 6 入门

菜鸟:https://www.runoob.com/w3cnot...
ES6基础之——new Set:https://www.cnblogs.com/ajaem...


Jerry
481 声望203 粉丝

学习的付出 从不欺人。记忆总是苦,写总结最牢固