《ES6标准入门》阅读笔记——let和const

写在前面

本系列为在我读完阮一峰老师的《ES6标准入门》第二版之后,所做的阅读笔记的整理。

许多初学ES6, 同时和我一样初次阅读阮老师的这本书的时候, 读第一遍会越发的困惑, 因为阮老师上面说的很多的定义,方法,之前都是没有见过的, 读到后面才发现, 哦~原来是这样.

有一句话叫做”大神的世界我们不懂”, 所以我在初读第一遍《ES6标准入门》这本书的时候,也是踩了不少坑,读书的时候查阅了不少的资料.

所以,在这一系列的笔记教程中,我会从一个初学者的角度,向您讲述ES6的相关知识,在后面介绍的知识我会尽量不提前用,即使提前使用,也会同时做好标注,避免了阅读时各种查阅资料的烦恼.

系列博客将采用一个一个的样例,来说明书中的精华部分(当然,这只是我认为的),同时引导新手,快速入门ES6,并逐步将其投入到生产实践中。

同时,在阅读前也提醒您, 为了系统连贯性的学习ES6的基础知识,建议您从我的博客第一章开始阅读,当然,如果您对对应的知识已经有所了解,那么可以跳转到任意章节阅读,每一篇博客名中均有介绍该博客中涉及到的ES6的内容.

阮一峰老师的这本书是开源的,在其官方博客就可以下载到,但是我强烈建议大家去购买一本书, 一是方便自己查阅ES6中新增的众多API, 二也是表达一下对大神的敬仰.

函数的作用域

要知道,在ES6以前,JS一直都是只有函数作用域,而没有块级作用域的。在这样的情况下,我们大多会使用IIFE(自执行函数表达式)来模拟块级作用域,达到避免全局作用域污染等目的。

那么ES6有新定义了两种变量的定义方式,let和const,两种变量的定义方式均会产生块级作用域,也就是我们使用大括号包起来的区域,都是块级作用域。

let和const两者之间的区别就是,const定义的变量,之后是无法更改的。

1
2
3
4
5
6
{
let a = 10;
var b = 1;
}
console.log(b)
console.log(a) // 报错

我们在大括号外面去打印a会报错,说明let其实是有块级作用域的,当我们使用循环语句的时候,let来声明变量是一个不错的选择.

1
2
3
4
5
6
7
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function() {
console.log(i)
}
}
a[6]() // 6

当我们使用let来进行声明的时候,才能得到想要的值 也就是6

原因是因为,变量i是let声明的,当前的i只在本轮循环有效,所以相当于每一次循环的i都是一个新的变量

let 不和var一样,存在变量的提升,所以我们在没有声明之前调用let声明的变量,会报错

1
2
console.log(b)   // 报错
let b = 2;

还有一点要注意的是,我们在第一行使用var声明了b,在这里再声明,就会报错,因为let和const是不允许变量被重复声明的.

暂时性死区

只要块级作用域内存在let命令,那么它所声明的变量就绑定了这个区域,不会受外部的影响
es6明确规定,只要区块中存在let和const命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域.只要在声明之前就使用这些变量,就会报错

其实上面那个例子,已经很好地说明了暂时性死区这个概念了.

1
2
3
4
5
var c = 3;
if(true) {
tmp = 'abc' //报错,tmp没有定义
let tmp
}

不仅如此,坑爹的是有些死区,你不仔细看真的很难发现

另外补充一点,在函数中进行形式参数的预定义,也就是设置函数的参数默认值,也是ES6中新定义的函数写法,关于此的内容,会在之后在函数篇中详细介绍.

1
2
3
4
function bar(x = y, y = 2) {
return [x, y]
}
bar() //报错, y is not defined

当然,我们把上面的代码稍微改一下就不会报错了

1
2
3
4
function bac(y = 2, x = y) {
return [x, y]
}
console.log(bac()) // [2,2]

结合上面的介绍,还有一点,当我们在块级作用域的内部再定义块级作用域的时候,就不会报错了.

1
2
3
4
5
6
7
8
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n) // 5
}
f1()

讲到这里,我再分享一个坑,关于函数的声明提升问题,当然, 下面这一题我没有直接在题上写答案,大家可以把解析先遮住,思考一下这题的结果是什么.既然是坑, 我觉得新手还是老老实实往上踩几脚比较好.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let f = function() {
console.log('outside')
}

function aaa() {
if (false) {
function f() {
console.log('inside')
}

}
f()
}
aaa()

万万没想到啊没想到, aaa函数中f()函数的执行居然会报错,而且打印f的值居然是undefined!

这其实是一个函数的作用域提升问题,预编译阶段内部函数f会跨过if判断而提升至函数aaa的作用域顶端

那么可能大家会问,我在aaa函数内部打印f函数,是undefined啊.

因为函数的声明赋值时在if判断语句之内的,所以在此之前仅有一个var f,也就是定义了一个f,但并未对其赋值,所以f打印出来就是undefined.

const和let的具体区别

我们来看下面这几行代码

1
2
3
4
5
6
7
8
9
10
11
let f;
console.log(f); // undefined
{
let a = 'secret'
console.log(f) // undefined
f = function() {
return a;
}
console.log(f) // f() { return a }
}
console.log(f()); //secret

其实上面的代码很好地说明了let的作用域的问题,而且需要注意的是,let的变量值,是可以随时更改的.

而const,就没有这么自由了

1
2
3
const PI = 3.14;
PI = 3; // 报错
const foo; // 定义变量时位对其赋值,报错

需要注意的是const定义复合类型值的时候,因为复合类型的变量不直接指向数据,而是指向数据所存储的相应内存空间

所以这个时候当我们对相应的内存空间中的值进行更改,只要内存空间不变,则都可以对值进行更改

1
2
3
const foo = {};
foo.prop = 1;
console.log(foo.prop) // 1

但是呢,,如果我们更改相应的foo指向的内存空间,则就会报错

1
foo = {}  // 报错(接上面的代码实例)

如果在声明引用类型变量后,我们不想让里面的值改变怎么办呢?

这个时候可以使用Object.freeze()方法,冻结这个对象,包括其内存空间的值

1
2
3
const foo1 = Object.freeze({});
foo1.prop = 1;
console.log(foo1.prop) // undefined

这个时候为该对象定义属性和方法,都无效了,但是不会抛出异常

最后需要注意的一点是,即使我们在全局使用let或是const定义变量,变量都不会挂载到全局的window对象上.

1
2
3
4
5
6
7
var a = 1
let b = 2
const c = 3

console.log(window.a) // 1
console.log(window.b) // undefined
console.log(window.c) // undefined

(完)

文章目录
  1. 1. 写在前面
  2. 2. 函数的作用域
  3. 3. 暂时性死区
  4. 4. const和let的具体区别
|