let和const命令

let命令

基本用法

  • let声明的变量只在其所在的代码块(块级作用域)内有效
{
  var a = 10;
  let b = 20;
}
console.log(a);//10
console.log(b);//ReferenceError: b is not defined

//for循环的计数器就很适合使用let命令
for (let i = 0; i < 6; i++) {}
console.log(i);//ReferenceError: i is not defined
复制代码
  • for循环中的var:变量i是var声明的,此代码中的i是全局变量,当for循环执行时,会将新的值赋值给全局变量i,当for循环结束后,将10赋值给了全局变量i,此时全局变量i变为了10,所以arr数组中无论调用哪一个函数,输出的i都是10
var arr = [];
for (var i = 0; i < 10; i++) {
  arr[i] = function() {
    console.log(i);
  };
}
arr[5]();//10
复制代码
  • for循环中的let:变量i是let声明的,当前的i只在本轮循环中的块级作用域中有效,所以每一次循环的其实都是一个新的变量i;
  • 既然每一轮循环的变量i都是重新声明的,那怎么知道上一轮循环的值从而计算出本轮循环的值?:这是因为JavaScript引擎内部会记住上一轮循环中的值,初始化本轮循环中的变量i时,会在上一轮循环的基础上进行计算
var arr = [];
for (let i = 0; i < 10; i++) {
  arr[i] = function() {
    console.log(i);
  };
}
arr[5]();//5
arr[6]();//6
复制代码
  • for循环中使用let声明变量还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域
for (let i = 0; i < 2; i++) {//for循环中的循环变量也就是括号中的是父作用域
  let i = 'hello';//而循环体中的也就是大括号中的就是子作用域
  console.log(i);//输出两次hello
}
复制代码

不存在变量提升

  • 使用var声明变量时,会发生变量的声明提升,即变量可以在声明之前使用,值为undefined
console.log(foo);//undefined
var foo = 1;
复制代码
  • 使用let命令声明变量时,没有变量的声明提升,let声明的变量一定要在声明后使用,否则会拨错;
console.log(bar);//Cannot access 'bar' before initialization:在初始化变量bar之前不能使用b
let bar = 2;
复制代码

暂时性死区

  • ES6明确规定,如果区块中存在let和const命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域,只要在声明之前使用这些变量,就会报错
  • 暂时性死区的本质就是,只要进入当前作用域,所有使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
  • 只要块级作用域内存在let命令,他所声明的变量就“绑定”这个区域,不再受外部影响
//首先声明了全局变量temp,但是块级作用域内let又声明了一个局部变量temp,
//导致后者绑定这个块级作用域,所以在let声明的局部变量前,对temp赋值会报错
var temp = 123;
if (true) {
  temp = 'abc';//Cannot access 'temp' before initialization
  let temp;
}
复制代码
  • 暂时性死区简称TDZ
if (true) {
  //TDZ开始
  temp = 'acb';//ReferenceError
  console.log(temp);//ReferenceError
  let temp;//TDZ结束
  console.log(temp);//undefined
  temp = 123;
  console.log(temp);//123
}
复制代码
  • 有些“死区”比较隐蔽
function foo(n) {
  let n = 123;//Identifier 'n' has already been declared
  console.log(n);
}
等同于==>
function foo(n) {
  var n = 666;
  let n = 123;//使用var声明就不会有暂时性死区
  console.log(n);
}
foo(666);
复制代码
//在变量y还没有声明前就使用它赋值给x变量
function bar(x = y, y = 2) {//Cannot access 'y' before initialization
  return [x, y];
}
bar();
复制代码
var x = x;//不报错
console.log(x);//undefined
let y = y;//报错
复制代码

typeof

  • “暂时性死区”也意味着typeof不再是一个百分之百安全的操作
  • x变量使用let声明,在声明之前都属于x的“死区”,只要使用到该变量就会报错
typeof x;//Cannot access 'x' before initialization
let x;
复制代码
  • 一个根本没有被声明的变量,使用typeof反而不会报错
typeof isNotDefined;//undefined
复制代码

不允许重复声明

  • let不允许在相同作用域内重复声明同一个变量
function foo() {
  var a = 1;
  let a = 10;//Identifier 'a' has already been declared:变量a已经被声明了
}
function bar() {
  let b = 1;
  let b = 10;//Identifier 'b' has already been declared
}
复制代码
  • 不能在函数内部重新声明参数
function foo(arg) {
  let arg;//Identifier 'arg' has already been declared
}
函数内部等同于==>
var arg;
let arg;
复制代码
  • 不过,可以在不同块级作用域中重新声明参数
function bar(arg) {
  {
    let arg;//不是同一个块级作用域
  }
}
复制代码

块级作用域

ES6的块级作用域

  • 外层代码块不受内层代码块的影响
function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n);//5
}
f1();
复制代码
  • ES6允许块级作用域的任意嵌套
{{{{{let message = 'hello'}}}}}
复制代码
  • 外层作用域无法读取内层作用域的变量
{{{{
  {let message = 'hello'}
  console.log(message);//message is not defined
}}}}
复制代码
  • 内层作用域可以定义外层作用域的同名变量
{{{{
  let message = 'world';
  {let message = 'hello'}
}}}}
复制代码
  • 块级作用域的出现,实际上使得获得广泛应用的立即执行函数(IIFE)不再必要了
//IIFE写法
(function() {
  var temp = ···;
  ···
}());

//块级作用域写法
{
  let temp = ···;
  ···
}
复制代码

块级作用域与函数声明

  • ES5中规定,函数只能在顶层作用域和函数作用域之中声明,不能再块级作用域声明
  • ES6中规定,允许在块级作用域之中声明函数
  • 在浏览器的ES6环境中遵循以下三条规则:
    1. 允许在块级作用域内声明函数
    2. 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
    3. 同时,函数声明还会提升到所在的块级作用域的头部
console.log(f);//undefined,提升到了全局作用域头部,所以变量f变为了全局变量
{
  console.log(f);//undefined,全局变量f的值
  if (true) {
    console.log(f);//[Function: f],提升到了块级作用域头部,将全局变量f赋值函数体
    function f() {
      console.log('hello');
    }
  }
  console.log(f);//[Function: f],全局变量f的值
  f();//hello
}

等同于==>

var f;//全局变量f,提升到全局作用域头部
console.log(f);//undefined,全局变量f的值
{
  console.log(f);//undefined,全局变量f的值
  if (true) {
    function f() {//提升到块级作用域头部,赋值给全局变量f
      console.log('hello');
    }
    console.log(f);//[Function: f],全局变量f的值
  }
  console.log(f);//[Function: f],全局变量f的值
  f();//hello
}
复制代码
  • ES6的块级作用域允许函数声明,在严格模式下没有使用大括号会报错
'use strict';
if (true)
  function f() {console.log(666);}
f();//报错
复制代码
  • 考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式的形式,而不是函数声明语句
//函数声明语句(尽量避免)
{
  let a = 1;
  function f() {
    return a;
  }
}
//函数表达式
{
  let a = 1;
  let f = function() {
    return a;
  };
}
复制代码

const命令

基本用法

  • const声明一个只读的常量;一旦声明,常量的值就不能改变
const PI = 3.14;
PI = 3.1415;//Assignment to constant variable:对常量进行赋值
复制代码
  • const声明的常量不能改变值,这意味着,const一旦声明常量,就必须立即初始化,不能留到以后赋值
const MAX;//Missing initializer in const declaration:常量声明必须初始化
复制代码
  • 只在声明所在的块级作用域内有效
if (true) {
  const PI = 3.14;
}
console.log(PI);//PI is not defined
复制代码

不存在变量提升

console.log(MAX);
const MAX = 999;//Cannot access 'MAX' before initialization
复制代码

暂时性死

var MIN = 1;
if (true) {
  MIN = -1;//Cannot access 'MIN' before initialization
  const MIN = 0;
}
复制代码

不可重复声明

var message = 'hello';
let age = 21;
const message = 'bye';//Identifier 'message' has already been declared
const age = 12;//Identifier 'age' has already been declared
复制代码

const本质

  • const实际上保证的并不是变量的值不得变动,而是变量指向的那个内存地址不得改动
  • 对于简单的数据(数值,字符串,布尔值)而言,值就保存在变量指向的内存地址中,因此等同于常量
  • 对于复合类型的数据(主要是对象和数组)而言,变量指向的内存地址保存的只是一个指针,指针指向保存的值,const只能保证这个指针是固定的,不能保证指针指向的数据结构不变
const foo = {};
foo.age = 21;
console.log(foo.age);//21
foo = {};//Assignment to constant variable:对常量进行赋值
复制代码
const arr = [];
arr.push('hello');
console.log(arr[0]);//hello
console.log(arr.length);//1
arr = ['world'];//Assignment to constant variable
复制代码

冻结对象

  • 冻结对象,应该使用Object.freeze()方法
const foo = Object.freeze({});
//常规模式时,下面一行不起作用
//严格模式时,改行会报错
foo.age = 21;
复制代码
  • 彻底冻结对象
function freezeAllObject(obj) {
  Object.freeze(obj);//冻结第一层对象
  Object.keys(obj).forEach((key, index) => {//遍历所有属性
    if (typeof obj[key] === 'object') {//属性对应的值是对象类型
      freezeAllObject(obj[key]);//递归调用,传入的数据是对象类型的值
    }
  });
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享