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环境中遵循以下三条规则:
- 允许在块级作用域内声明函数
- 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
- 同时,函数声明还会提升到所在的块级作用域的头部
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






















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)