《ECMAScript 6 入门》精读: 关于let和const的重要整理
今天是周天,本来周五晚上就想写这一篇总结,但是每周分享的题总结还没结束,只能结束之后新写这一个文章。
其实,昨天在逛github的时候给我推荐了几个不错的仓库博客,类似于《一周攻克一个前端难点》,我也会有时间去学习,并且把学习到的干货分享到博客上来。
关于这边《ECMAScript 6 入门》,我没有买这个书而是看阮一峰老师的电子开源书籍,周五看了第一章关于let和const,然后加深了我对于作用域,死区的理解,然后在书中也有非常多的demo例子,这些例子在笔试中还是高频率的出现的,一些前端开发者在面试(入行不深的程序员)选择google出来的答案,背下来....
但是这非常不实用,因为你不了解这种题如何做,在工作中如何避免bug。
在新专栏的开始之前,我想多说几句,和大家分享一下我的学习方法,我也是入行不深,仅仅只有半年多而已,但是写代码即将2年多,
我学习前端的方法也非常简单,项目实战(有利于能接触到更多的场景)-》刷题(渣渣程序员只能从语言角度上刷题,涉及不到算法数据结构的题)-》笔记和博客(每天总结笔记,每一段时间总结博客)要养成良好的习惯,不管多忙,在地铁和公交车上一定要把笔记写完,随手复习,工作中可以翻阅;
正题开始............
es6是前端开发者必须掌握的一个知识点,不管是培训结构还是某些大学的前端课程,传授的es6都相对非常浅,没有去认真的一篇一篇去消化巩固,作者也是这样的,所以现在开始我们将一起一步一步精读阮一峰老师的这本非常棒的书籍,把es6完全消化,能够让你在工作中面试中杀出一条血路。
首先我们来快速了解一下let;
Let
我们尝试写这样一段代码:
{
let a = 1;
var b = 10;
}
console.log(a); // Uncaught ReferenceError: a is not defined
console.log(b); // 10
这个例子说明了let只会在声明它的代码块中生效,而var则相反
我们一起再看一个例子:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[1](); // 10
这个循环中的i是一个全局变量,每一次循环全局的变量i都会发生改变,所以尽管它循环100次,数组中console.log中的i永远指向全局变量即100,最后一次循环的赋值;
但是如果把上面的var换成let就不一样,let只在自己的块中存在,所以每次循环都会重新let一次i变量,所以每次输出的console.log中的i指的是当前循环到的i
但是你肯定会说:
既然是重新let一次i,那么每次它应该都是0呀,为什么还能记忆上次的i值呢?这不科学...
那是因为js引擎会记住上一次i的值,为下次循环做运算,这一部分是js帮助开发者做好的。
js的for循环还有一个特别之处:设置循环变量的是一个父作用域,而循环体是一个子作用域
for (let i = 0; i < 10; i++) {
let i = '123';
console.log(i) // 123
}
正确运行,并且成功赋值打印新的值;
Let - 关于变量提升
js中的var是存在变量提升的,什么是变量提升呢?
console.log(i); // undefined
var i = "I love you"
在变量未被附值的时候,js解析代码会变成下面这个样子:
var i;
console.log(i);
i = "I love you"
但是let并非这样,它没有所谓的变量提升,只要在声明前使用,则会报错;
console.log(a); // Uncaught ReferenceError: a is not defined
let a = "yes";
所以var会让变量提前声明,而let不会提前声明,如果声明前用到了它,就会报错Uncaught ReferenceError;
Let - 暂时性死区
文章的开头我们降到了死区这个概念,之前呀,我也不太懂这个死区,以为是一个很难的概念,没想到和let有关系,非常简单;
var a = "123";
{
a = "125";
let a = "456";
}
ES6明确规定,let和const声明了一个变量,那么会和当前作用域块进行绑定,如果在声明前使用到了变量,那么就会报错;
这在代码中我们称之为 "暂时性死区"
那么如果我们不向变量赋值会不会报错呢? 事实证明typeof也不是一个正确的操作
typeof x; // Uncaught ReferenceError
let x;
如果我们检测一个不存在的变量
typeof notfound; // undefined
所以说这也意味着我们编写代码的时候要在声明之后使用它;
在阮一峰老师的文章中也提到了不常见的死区的案例:
function test(x = y, y = 2){
}
test();
x的默认值是还未声明的y,所以会报错;
var x = x; // 不报错
let x = x; // 报错
其实上面这个代码,之前就解释过了,var出来的变量即已对变量进行初始化,所以等号右边的x就是undefined
而let出来的变量没有变量提升,然后又在变量x声明之前使用到了x,这显然是错误的,因为之前使用的就是死区;
因此死区的触发有很多,检测,赋值,使用, 等等等;
Let - 不允许同时声明在同作用域中
{
var a = 1;
let a = 1;
}
或者
{
let a = 1;
let a = 1;
}
又或者
funtion test(arg){
let arg = "123";
}
这些都会报错,但是在不同作用域中是可以的
funtion test1(arg){
{
let arg = "12";
}
}
it is ok!!
关于块级作用域
面试中经常会被问到作用域, 全局作用域,函数作用域,在es6中新的概念就是块级作用域,之前面试过其他的前端开发者谈到块级作用域解决了什么问题,和let,const的关系,他们也是没有一个很准确的答案;
这边用阮一峰老师的2个demo例子:
var temp = new Date();
function test(){
console.log(temp);
if(true){
var temp = "hello"
}
}
test(); // undefined
上面这个例子之所以输入未定义,是var变量会造成变量提升,即var temp; 内层的temp替换掉了new Date的那个变量,提升到了函数作用域的顶级;
还有一种场景就是,我们在用循环的时候,使用var,导致循环结束var的变量依然存在,造成变量泄露;
事实上,let和const就是块级作用域,因为我们知道在前面的内容讲到let只能在当前作用域下使用,和其他的作用域不相干,这就是形成了块级作用域;
let a = 1;
if(true){
let a = "1111"
}
console.log(a); // 1
ES6允许我们块级作用域嵌套
{{{{{
console.log(1)
}}}}}
即内层的可以读取到外层同名变量,外层不能读取内层的同名变量;
块级作用域和函数声明
ES5规定,块级作用域里面不能写函数声明语句,但是浏览器为了兼容以前的旧代码,没有遵守这个规定;
但是ES6明确规定,块级作用域下可以写函数声明,但是函数声明类似于let,在作用域之外不能使用;
看一下下面这个demo:
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
如果是在ES5中写这个函数,那么最终会输出 "I am inside!" ,因为会变量提升提前到函数顶部,所以会输出inside
但是如果在ES6中声明,理论来讲会输出outside,因为es6规定了函数声明相当于let,只在自己作用域生效,不会影响其他作用域,也没有变量提升,但是如果在ES6中就会输出undefined;
为什么呢?说好的规定呢?说好的let呢?
原来浏览器又有自己实现的一套方法,输出undefined的原因还是被提升了
var f = undefined;
所以我们在作用域中写函数,不要用函数声明,要使用函数表达式,这样最稳妥...
const
ES6新增的常量,我喜欢叫它常量,因为它不可改变;
const a = 1;
a = 12; // TypeError: Assignment to constant variable.
且不能这样写
const a;
不能像var和let一样先定义再赋值;
const和let一样,只能在声明的块级作用域有效
const和let一样,也有死区
const和let一样,也不能重复声明
const的本质其实并不是const的变量值不能被改变,而是指针(地址)是固定的;
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
---------------《ECMAScript 6 入门》
所以我们可以改变const的数据结构
const props = {};
props.name = "1";
props.age = "12";
props = "12"; // 报错
结束咯,喜欢这篇总结,就收藏起来吧~~
原文阮一峰老师地址: http://es6.ruanyifeng.com/#docs/let