ES6笔记之let声明和const
博主为什么要在繁忙的业务中抽时间写这些最基本的笔记,就是想不断积累自己小的细节,让自己的开发更有效率,不能知其然不知所以然。
博主在实际开发中使用了react全家桶(框架)+typescript(预编译JS)+ES6(JS语法)+webpack(打包工具)+styledcomponent(css),并使用node模拟后台数据。
因为自己不想成为写业务 的机器, 而是致力成为 技术 人员。
ES6分类博文笔记摘自 http://es6.ruanyifeng.com/#docs/let
博主在这里只写最核心和自己所需要的。
let 命令
let命令 用来声明变量 ,与var的区别就是let所声明 的变量 作用域只在let 命令所在的代码块{}有效,且需要先声明后使用,如果直接使用会报错
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
a[i]();//这里依次打印0到9
}
a[i]()//这里i从0到9都会打印10
变量i
是var
声明的,在全局范围内都有效,所以全局只有一个变量i
。每一次循环,变量i
的值都会发生改变,
而循环内被赋给数组a
的function
在运行时,会通过闭包读到这同一个变量i
,(其实我这里也不是特别理解,得重新补习一下闭包)导致最后输出的是最后一轮的i
的值,也就是10。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
变量i
是let
声明的,当前的i
只在本轮循环有效,所以每一次循环的i
其实都是一个新的变量,所以最后输出的是6
。你可能会问,如果每一轮循环的变量i
都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i
时,就在上一轮循环的基础上进行计算。
另一个var和let的区别
var
命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined
。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let
命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。(webstorm会自动检测,而不是在编译后检测报错,推荐使用)
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
上面代码中,变量foo
用var
命令声明,会发生变量提升,即脚本开始运行时,变量foo
已经存在了,但是没有值,所以会输出undefined
。变量bar
用let
命令声明,不会发生变量提升。这表示在声明它之前,变量bar
是不存在的,这时如果用到它,就会抛出一个错误。
暂时性死区
这里原博主说了很多,但是简而言之就是如果在一个代码块中在let申明一个变量前使用了当前申请的变量,及时该变量已在外部声明,即会抛出错误。
那有人会问了,既然抛出错误让代码不能执行,我平时用js都不会有这样的问题,那我用let的意义在哪里呢?
为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。
// 不报错
var x = x;
// 报错
let x = x;
// ReferenceError: x is not defined
上面代码报错,也是因为暂时性死区。使用let
声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量x
的声明语句还没有执行完成前,就去取x
的值,导致报错”x 未定义“。
ES6 规定暂时性死区和let
、const
语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
不允许重复声明
let
不允许在相同作用域内,重复声明同一个变量
块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
第一种场景,内层变量可能会覆盖外层变量。
var tmp = new Date();
function f() {
console.log(tmp);//由于下面变量提升,即会输出undefined
if (false) {
var tmp = 'hello world';//没有块级作用域的限制,这里声明的tmp在ES5中会提升至函数作用域。虽然不会执行里面的赋值,但是会变量提升
}
}
f(); // undefined
第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上面代码中,变量i
只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
ES6 的块级作用域
let
实际上为 JavaScript 新增了块级作用域。
ES6 允许块级作用域的任意嵌套。
块级作用域与函数声明(不理解也没关系,本篇主要讲解let)
if (true) {
function f() {}
}
// 情况二
try {
function f() {}
} catch(e) {
// ...
}
上面两种函数声明,根据 ES5 的规定都是非法的。
但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于let
,在块级作用域之外不可引用。
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
上面代码在 ES5 中运行,会得到“I am inside!”,因为在if
内声明的函数f
会被提升到函数头部,实际运行的代码如下。
// ES5 环境
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!'); }
if (false) {
}
f();
}());
而在ES6中,由于f()未声明,所以会报错
原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6在附录B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。
- 允许在块级作用域内声明函数。
- 函数声明类似于
var
,即会提升到全局作用域或函数作用域的头部。 - 同时,函数声明还会提升到所在的块级作用域的头部
注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let
处理。
const
const也可以用来申明变量 但是声明的是常量。一旦声明,常量的值就不能改变。
当我们尝试去改变用const声明的常量时,浏览器就会报错。const有一个很好的应用场景,就是当我们引用第三方库的时声明的变量,用const来声明可以避免未来不小心重命名而导致出现bug: