ES6_Z02_let和const

302 阅读6分钟

let 和 const 命令

for 循环中的特别之处,设置循环变量的那部分是一个父作用域,循环体内部是一个单独的子作用域

for (let i = 0; i < 3; i++) {
  let i = "aaa";
  console.log(i); //打印三次都是aaa说明和上面不在同一个作用域
}

//原始es5
const arr = [];
for (var i = 0; i < 10; i++) {
  arr[i] = function() {
    console.log(i);
  };
}
arr[5](); //10
arr[9](); //10

//改写
const arr2 = [];
for (let i = 0; i < 10; i++) {
  arr2[i] = function() {
    console.log(i);
  };
}
arr2[5](); //5
arr2[3](); //3

let


1. 不存在变量提升

2. 暂时性死区 TZD

只要块级作用域内存在 let 命令,他声明的变量就 binding 了这个区域,不受外部影响 ,在 let 声明变量之前都是死区,意味着 typeof 不再是一个百分之百的操作

var temp = 2;
if (true) {
  temp = "222"; //报错
  console.log(typeof temp); //ReferenceError
  let temp; //此处以上的代码声明temp,都属于temp变量的暂时性死区
}

有些暂时性死区比较隐蔽

function bar(x = y, y = 2) {
  //此时参数x的默认值等于y,但y还没有声明,属于死区
  return [x, y];
}
console.log(bar(1, 2)); //[1,2]
bar(); //ReferenceError: y is not defined

//如果 y的默认值是x就不会报错
function bar2(x = 2, y = x) {
  console.log(x, y);
}
bar2(); // 2,2

3. 不允许在相同作用域内重复声明同一个变量

function func(args) {
  let args; //不熬在函数内部重新声明参数
}

块级作用域

1. 为什么需要块级作用域?

  1. 内层变量可以覆盖外层变量
  2. 用来计数的循环变量泄露成全局变量 使得广泛应用的立即执行匿名函数(IIFE)不在必要了
(function() {
  var temp = "aaa";
})();

//块级作用域
{
  let temp = "aaa";
}

2. 块级作用域和函数

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

//情况 1
if (true) {
  function fn() {}
}
//情况 2
try {
  function fn() {}
} catch (e) {}

以上两个函数声明在 ES5 中是非法的,在 es6 中应该避免在块级作用域中声明函数,如果需要可以应该写成函数表达式的形式

es6 允许在块级作用域中声明函数,在块级作用域中,函数声明语句的行为类似于 let,在块级作用域之外不可引用

//以下代码会报错

function f() {
  console.log("外面函数f");
}

(function() {
  if (false) {
    function f() {
      console.log("里面函数f");
    }
  }
  f();
})();

//实际运行的情况如下

function f() {
  console.log("外面函数f");
}

(function() {
  var f = undefined;
  if (false) {
    function f() {
      console.log("里面函数f");
    }
  }
  f();
})();

考虑到环境导致的行为差异太大,应该避免在块级作用域中声明函数,确实需要的话,应该写成函数表达式的形式,而不是函数声明语句

{
  let a=123;
  function f(){return a}
}

//确实需要的话
{
  let a=123;
  let f=function(return a)
}


3. do 表达式

本质上块级作用与是一个语句,将多个操作封装在一起,没有返回值,可以使用 do

//以下块级作用域中不返回值,此作用域之外无法得到t的值,除非t是全局变量
{
  let t = f();
  t * t * t + 1;
}

新的提案,在块级作用域前加上 do,使他变为 do 表达式

let x = do {
  let t = f();
  t * t * t + 1;
}; //x 会得到整个块级作用域的返回值

const 命令

const 声明一个只读的常量,一旦声明,常量的值就不能改变

1. 注意只声明不赋值会报错

const MAX; //报错

2. const 和 let 的作用域一样,只在声明所在的块级作用域内有效

if (true) {
  const MAX = 555;
}
console.log(MAX); //报错

3. const 命令也不存在变量提升,同样存在暂时性死区

if (true) {
  console.log(MAX); //报错
  const MAX = 5555;
}

4. 和 let 一样不可以重复声明

var a = 1;
let b = 2;
//下面两行报错
const a = 3;
const b = 4;

const的本质实际是保证变量指向的那个内存的地址不得改动,对于引用数据类型来讲,变量指向内存的那个指针,const只能保证这个指针是固定的,至于他指向的数据结构是不可变的,这完全不能控制,所以将一个对象声明为一个常量要格外小心

const foo = {};
foo.name = "foo";
foo = {}; //指向新的对象会报错

const arr = [];
arr.push("a");
arr = [1, 2, 3]; //赋值为新的数组报错

如果想冻结对象,应该使用 Object.freeze({})

const obj = Object.freeze({
  name: "cc",
  age: 12
});

console.log(obj); //{ name: 'cc', age: 12 }
obj.name = 123; //严格模式下会报错
console.log(obj); //{ name: 'cc', age: 12 }
//常量指向一个冻结的对象,所以给他添加新的属性是不起作用的,严格模式还会报错

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

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

5. ES6 中声明变量的 6 中方法

ES5 声明变量只有 varfunction ES6 声明 const let import class

顶层对象的属性

顶层对象在浏览器环境中是指 window,在 node 中是指 global 对象

//ES5中,顶层对象的属性和全局变量是等价的
window.a=1 =====  var a=1
//ES6开始全局变量将逐步与顶层对象的属性隔离
let b=1  !====  window.b //undefined

global 对象

ES5 的顶层对象本身也是一个问题,因为他在各种实现中是不统一的

  1. 浏览器中顶层对象就是 windoqm,但是 Node 和 Web Worker 中没有 window
  2. 浏览器和 Web Workder 中,self 也指向顶层对象,但是 Node 没有 self
  3. 在 Node 中,顶层对象是 global,但是其他环境都不支持

同一端代码为了能够在各种环境中都能获取顶层对象,目前一般是采用 this 变量,但是也有局限性

  1. 全局环境中,this 返回顶层对象,但是 ES6 模块和 node 模块中,this 返回的是当前模块 2.对于函数 this,如果函数不是以对象的方式运行,而是单纯作为函数运行,this 会指向顶层对象,但是严格模式下,this 指向 undefined
  2. 不管是严格模式还是普通模式,new Function('return this')()总是返回全局对象

所以很难有一种方法可以适合上面所有场景,取得顶层对象,以下是两种勉强可以使用的方法

//方法一
var a =
  typeof window !== "undefined"
    ? window
    : typeof process === "object" &&
      typeof require === "function" &&
      typeof global === "object"
    ? global
    : this;

// console.log(a);
//方法二

var getGlobal = function() {
  if (typeof self !== "undefined") {
    return self;
  }
  if (typeof window !== "undefined") {
    return window;
  }
  if (typeof global !== "undefined") {
    return global;
  }
  throw new Error("unable to locate global object");
};
console.log(getGlobal());