ES6学习笔记(一)let和const 块级作用域

本文深入探讨ES6中let和const的使用,包括块级作用域、变量提升、重复声明及全局声明的特点,同时解析let与const的区别,如不可变性、初始化要求及对对象和数组的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

let


let使用的作用域

let是ES6中新加入的变量声明,和var类似,但是let声明的变量只能在其声明的块级作用域中使用

if(true){
    var a=1; 
}
console.log(a);//输出1


if(true){
    let b=1;
}
console.log(b);//报错:Uncaught ReferenceError: b is not defined

在这里a通过var在if块中声明,声明后在if块外面仍能调用,但是let在if块中声明后在块外面调用时却报了变量未声明的错误,这是因为let声明的变量只能在其所在块级作用域中使用。使用for循环时也是一样:

a = [];
for (var i = 0; i < 5; i++) {
    a[i] = () => console.log(i);
}
a[3]();//5



a = [];
for (let i = 0; i < 5; i++) {
    a[i] = () => console.log(i);
}
a[3]();//3

在使用var时,因为i为全局变量,所以在每次循环的时候,i的值都会发生变化,即每次赋给console.log(i)中的i的值是一样的,也即是最后一次循环结束时的值。

而在使用let时,因为其声明的变量会在块级作用域中,所以每次i都是对应我们所要打印出来的值,除此之外,在使用for循环时使用let来声明要用于循环的变量,可以使该变量在循环结束后消除该变量。

这里有个小误区,有人会认为for循环中括号内的let声明在这个for循环中只创建了一个变量,但实际上并非如此,我们用下面这段代码来解释上面的代码。(要注意下面的for循环代码和上面使用let声明的for循环代码一致,除了最终在for循环后var声明的变量不会消除)

a = [];
for (var i = 0; i < 5; i++) {
    let j=i;
    a[j] = () => console.log(j);
}

这样就可以清晰地看出let声明在for循环中的真正工作了,在每次循环都会创建一个块级作用域的变量,即在上面的代码中(for后面的i用let声明的代码),i一共被声明了6次(第6次创建后与5进行比较后跳出循环),每次声明都在不同的块级作用域(不同的循环),所以不会报错。

暂时性死区--消除变量提升

在平时使用var时,我们会遇到一个奇怪的现象,即变量在声明之前就可以使用,即我们平常所说的变量提升

a=1;
var a;//1

在ES6中,let并没有这种奇怪的特性,看起来更为规范,当我们在用let声明前使用了该变量,会发生报错

a=1;
let a;// Uncaught ReferenceError: a is not defined

由于let没有变量提升,所以出现了暂时性死区,即在一个块级作用域中,如果let声明了一个变量,这个变量就会受其“绑定”,不受外部作用域影响,在该块级作用域中如果用let声明了一个变量,那么在这个块级作用域中在这个声明之前就不能使用该变量,即使在外部的作用域中已声明了该变量。

var a=1;
if(true){
    a++;
    let a;
}
//Uncaught ReferenceError: a is not defined

在上面这段代码中,虽然a已经被声明为全局变量,但因为在if后的块级作用域中,用了let声明变量a,但是却又在该声明前使用了a,所以报错了。有趣的是,对未声明的变量使用typeof本来不会报错,会得到undefined,但在暂时性死区中会报错。

{
    console.log(typeof a);// undefined
	console.log(typeof b); Uncaught ReferenceError: b is not defined
    let b;
}

禁止重复声明

使用var或者不加任何修饰符是可以重复声明的,但这样显然有些奇怪,let解决了这一问题。let在同一作用域中不能重复声明,也不能重新声明函数传进来的参数。

if(true){
    let a=1;
    let a=2;
}
//Uncaught SyntaxError: Identifier 'a' has already been declared

function func(a) {
    let a;
}
func(1);
//Uncaught SyntaxError: Identifier 'a' has already been declared

function func(a) {
    {let a;}
}
func(1);
//undefined  (不报错)

全局声明时不出现在全局对象上

我们知道,当我们在全局写入一个

var a = 1

的时候,这个a会被作为globalThis的属性,在浏览器中也就是a的对象

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

但是如果是使用let的话,是不会有这种情况的

let a = 1
console.log(window.a)
// undefined

我们可以通过打断点来查看let和const在全局声明时所在的作用域

可以看到,当我们在全局使用let和const声明变量的时候,变量是在与Global同级的Script中

const


const声明一个只读的常量,声明之后不能对其进行修改,出于这个特性,const在声明时就要赋值,如果在之后赋值会报错,其作用域与let一样,都是块级作用域,不存在变量提升,也会有暂时性死区。同样的,在上面也有截图看到,const在全局声明时并不会添加到全局对象上

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

const a;
a=1;//Uncaught SyntaxError: Missing initializer in const declaration

事实上const声明的变量也并非值不能改变,在阮一峰的ECMAScript 6入门中有说到

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

即是说,假如我们使用const声明一个变量指向一个对象,只是使得该变量变为一个固定指向指定的对象的“常量”,但对象里面的内容是可以修改的,即对象的属性和方法。

const b={
    a:2
}

console.log(b.a); // 2

b.a=3; // 不会报错!

console.log(b.a) // 3

同样的,数组的const声明也是一样的,看看下面这段代码

const arr=[1,2,3];
arr[3]=4;
console.log(arr);

//[1, 2, 3, 4]

可以看到,我们很明显修改了这个数组,但是这里const不变的是对这个数组的引用,所以并不会报错。

块作用域函数


在ES6开始,块内声明的函数,其作用域在这个快内,在块外调用块内声明的函数会报错(如果块外没有声明同名的函数的话)。看如下代码

{
    fn(); // fn
    function fn(){
        console.log('fn');
    }
}
fn(); // 报错

上面的代码因为是在块中声明的,所以在块外调用时会报错,我们将其放到babel下编译,就得到下面的代码

{
    var _fn=function _fn(){
        console.log('fn');
    }
    _fn();
}
fn();

这段代码就可以明显看到块中定义的函数只在块中有效了。出于ES6的这种特性,依赖于旧的非块级作用域的代码可能会出现问题。

if(flag){
    function fn(){
        console.log(1);
    }
}else{
    function fn(){
        console.log(2);
    }
}

fn();

这里的fn()调用最后会打印出什么,视当前所处的环境,若是在ES6之前的环境,不管flag是什么,最后都是打印出2。因为在ES6之前没有块级作用域的概念,两个fn方法会被提升到块的外部,而后声明的fn方法代替了前声明的fn方法,所以都是打印出2,而在ES6后的环境,最后的fn()会报错(如果在外部没有声明fn()方法的话),因为两个fn()方法都是在块内声明的,没法在块的外部调用。


参考文档:阮一峰的《ECMAScript 6入门》

        Kyle Simpson的《你不知道的JavaScript 下卷》


ES6学习笔记目录(持续更新中)


ES6学习笔记(一)let和const

ES6学习笔记(二)参数的默认值和rest参数

ES6学习笔记(三)箭头函数简化数组函数的使用

ES6学习笔记(四)解构赋值

ES6学习笔记(五)Set结构和Map结构

ES6学习笔记(六)Iterator接口

ES6学习笔记(七)数组的扩展

ES6学习笔记(八)字符串的扩展

ES6学习笔记(九)数值的扩展

ES6学习笔记(十)对象的扩展

ES6学习笔记(十一)Symbol

ES6学习笔记(十二)Proxy代理

ES6学习笔记(十三)Reflect对象

ES6学习笔记(十四)Promise对象

ES6学习笔记(十五)Generator函数

ES6学习笔记(十六)asnyc函数

ES6学习笔记(十七)类class

ES6学习笔记(十八)尾调用优化

ES6学习笔记(十九)正则的扩展

ES6学习笔记(二十)ES Module的语法

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值