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

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在全局声明时所在的作用域

ES6学习笔记(一)let和const 块级作用域_第1张图片

可以看到,当我们在全局使用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的语法

 

你可能感兴趣的:(ES6学习笔记,JavaScript)