第二十节: ES6变量声明
1. 前言: 理解ES6 与 ECMAScript 2015 的关系
2011 年,ECMAScript 5.1 版发布后,就开始制定 6.0 版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本。
ES6 的第一个版本,就这样在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes
方法和指数运算符),基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准。
因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,
ECMAScript的历代版本:
1997 ECMAScript 1.0
1998 ECMAScript 2.0
1999 ECMAScript 3.0
3.0 版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了 JavaScript 语言的基本语法,以后的版本完全继承。
2000 ECMAScript 4.0
这个版本最后没有通过但是它的大部分内容被 ES6 继承了。因此,ES6 制定的起点其实是 2000 年。
2007 年 10 月
ECMAScript 4.0 版草案发布,本来预计次年 8 月发布正式版本,以 ,Yahoo、Microsoft、Google 为首的大公司,反对 JavaScript 的大幅升级,主张小幅改动,以 JavaScript 创造者 Brendan Eich 为首的 Mozilla 公司,则坚持当前的草案。
2008 年 7 月
由于争议太多中止 ECMAScript 4.0 的开发,小幅的改进后,发布发布为 ECMAScript 3.1,激进的部分放到以后版本,会后不久,ECMAScript 3.1 就改名为 ECMAScript 5。
2009 年 12 月,
ECMAScript 5.0 版正式发布。ES5 与 ES3 基本保持兼容,有争议的部分放在了next版本中
2011 年 6 月,ECMAScript 5.1 版发布
2013 年 3 月,ECMAScript 6 草案冻结,不再添加新功能
2013 年 12 月,ECMAScript 6 草案发布。然后是 12 个月的讨论 期,听取各方反馈。
2015 年 6 月,ECMAScript 6 正式通过,2000至今15年
ECMAScript 6于2015年6月正式发布,成为企业级开发语言,又称ES2015。
ES6细枝末节很多(因为大家知道,JS是一个千疮百孔的语言,所以ES6在定稿的时候就特别细致,细枝末节极多)
一. let 命令
1. ES5 的变量定义
定义变量(声明变量)
var a = 12;
1.1 var 的变量的特性
-
会发生变量提升
var a = 12; function fn(){ alert(a); // undefined var a = 5; } fn()
-
ES5 作用域只用全局和函数内部作用域
for(var i = 0;i < 10; i++){ // TODO } // 突然有一天我想弹出i,这个i是几 alert(i)
变量提升,这个我们之前讲这就是JS语言的特性,后来就有人提议,说这种特性不好,我不知道我程序发生了什么事情,同时for循环的循环变量i会污染全局变量,比较讨厌
2. ES6 变量定义
为了解决ES5 变量声明的问题,所以在ES6 新增了两个定义变量的关键词
let, 就相当于之前的var
const 常量,定义好了以后就不能改变了
我们先来看看let;
2.1 基本用法
跟使用es5的var一样
let a = 12;
console.log(a);
3. 关于ES6 变量
3.1 变量提升的问题
之前讲var的时候有变量提升,那么let, const 是否具有变量提升呢
let a = 12;
function fn(){
alert(a); // 这里当然能用了,这不就是作用域的查找吗
}
fn();
// 那么如果我在函数内定义一个let a; 如下
let a = 12;
function fn(){
alert(a); // 报错,这个时候报引用错误
let a = 5;
}
fn();
// Uncaught ReferenceError: a is not defined
// 报错信息是 a 没有并定义
所以let,const不会进行变量提升,必须先定义在使用
官方的称发是在变量没有let前的所有区域叫TDZ,暂时性死区
所以必须先定义在使用.用来规范大家编程行为
let会引发暂时性死区(面试常考):
在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
var m = 10;
function fun(){
m = 20; //报错。函数在预解析阶段会预读所有的语句,发现了let语句,所以就将这个函数变为了一个m的暂时性死区,此时m不允许在let前被赋值。
let m;
console.log(m);
}
fun();
3.2. 块级作用域
我们都知道 在语句中 {}叫做块,
我们都知道ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
比如:
我们刚讲过的for循环的循环变量污染全局
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
还有就是内层变量可能会覆盖外层变量。
var tmp = 123;
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
这也是为什么我们需要块级作用域的原因
但ES6 let const 所定义的变量具有块作用域
if(true){
var a = 12;
}
alert(a); // 能用 之前讲的作用域只有全局和函数作用域,什么if for 在全局就当全局用
// 如果此时换成let
if(true){
let a = 12;
// 接下来只能在这里使用
}
alert(a);
到了es6 里面
{
// 块里面就具有了作用域,叫块级作用域
}
3.3 同一个变量多次定义的问题
之前咱们说过变量至少要var一次吧;但是我们多次var一个变量也没有问题
// ES5
var a = 1;
var a = 5;
alert(a); // 弹出5
// ES6 的年代
let a = 1;
let a = 5;
alert(a); // 报错
//Uncaught SyntaxError: Identifier 'a' has already been declared
3.4 关于for循环的循环变量问题
我们之前讲for循环的时候,是不是讲循环变量是当前作用域的
for(var i =0 ;i < 10; i++){
}
alert(i); // 这个时候i指定是一个10
那么用let定义的for循环呢,会有哪些不同呢
// 如果我们使用let呢
for(let i =0 ;i < 10; i++){
console.log(i); // 在循环里面是可以正常使用的
}
alert(i); // 报错: 报一个引用错误
//Uncaught ReferenceError: i is not defined
// i未定义
// 如果我们在循环体内在let i呢
for(let i =0 ;i < 10; i++){
let i = "abc"; // 感觉应该会报错,重复定义嘛,其实不是
// 可以理解()中的i是父级作用域,循环体内i是子级作用域,互不干扰
console.log(i); // 在循环里面是可以正常使用的
}
alert(i); // 报错: 报一个引用错误
//Uncaught ReferenceError: i is not defined
// i未定义
3.5 ES6 允许块级作用域的任意嵌套。
也就是扩展父子级的块作用域
{
let a = 12;
console.log(a); //12
}
console.log(a); // Uncaught ReferenceError: d is not defined
// 如果我开心我是不是可以在块作用域中在加一个块作用域啊
{
let a = 12;
{
let b = 10;
console.log(b); // 10
}
console.log(a); //12
}
console.log(a); // 报错
// 这不叫重复定义,重复定义是不能在同一个作用域中重复定义
// 如果你愿意包含多少层作用域都可以.但是没什么意义,无论你包多少层,外边又访问不到,只有里面能访问到
let 特点:
没有预解析,不存在变量提升
官方的称发是在变量没有let前的所有区域叫TDZ,暂时性死区
所以必须先定义在使用.用来规范大家编程行为
不能重复定义变量
for循环里比较特殊,可以将()中的循环变量理解为父级作用域中的变量,循环体重的变量理解为子作用域中的变量,所以他们let同一个变量不会报错
还记得我们讲for循环里面函数的问题吗
var arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function(){
console.log(i); // 这里我们是不是希望,数组第几项函数执行,打印几啊
}
}
arr[6](); //10 结果你发现所有的打印都是10
// 有些小伙伴说我代码没有问题,是的,代码真没有什么问题,这是这么语言特性造成的
// 这是不是就是我们之前讲的函数闭包造成的问题啊
// 这个时候我们换成let去定义
var arr = [];
for(let i = 0; i < 10; i++){
arr[i] = function(){
console.log(i); // 你发现这个时候就符合我们的预期了
}
}
arr[6](); // 6
示例:
<input type="button" value="aaa"/>
<input type="button" value="bbb"/>
<input type="button" value="ccc"/>
<script>
var aInput = document.querySelectorAll("input");
for(var i = 0 ;i < aInput.lenght; i++){
aInput[i].onclick = function(){
console.log(i)
}
}
</script>
4. const 声明常量
4.1 const 基本使用
const声明一个只读的常量。
const: 特性和let一样
只是const定义的变量不能修改,因为人家叫常量,所以不能修改.
let a = 12;
a = 20;
console.log(a); // 20
有些人可能会说.老师修改怎么了
但是实际开发中有些我是不希望它修改的,比如一些配置文件
const root = 12;
console.log(root); // 12 你发现能正常使用
// 一旦更改
root = 20; // 直接报出 错误, TypeError 类型错误
//Uncaught TypeError: Assignment to constant variable.
// 你把一个常量转换到变量的错误
4.2 声明变量赋初值问题
const还有一个特点平时我们喜欢先定义变量,在赋初值
var a;
a = 10;
console.log(a); // 10 没什么问题
let a;
a = 20
console.log(a); // 20 let 也没什么问题
// 但是const有问题
const a;
a = 10;
console.log(a); // 报错 语法错误
//Uncaught SyntaxError: Missing initializer in const declaration
//缺少初始值在常量定义时
也就是说cons定义时必须赋值,不能后赋值,后赋值也是修改,不能修改
如果只是定义不赋值也会报错.
const a;
console.log(a); // 报错 语法错误
//Uncaught SyntaxError: Missing initializer in const declaration
//缺少初始值在常量定义时
4.2 const 无提升
const a = 12;
function fn(){
alert(a); // 报错,这个时候报引用错误
const a = 5;
}
fn();
// Uncaught ReferenceError: a is not defined
// a未定义
有人说不能修改真的假的,看个例子
const arr = ["apple","banana"];
//arr = [];
//console.log(arr); // 报错
// Uncaught TypeError: Assignment to constant variable.
// 哎 我们学过push修改数组
arr.push("wuwei");
console.log(arr); //["apple", "banana", "wuwei"]
// 发现可以为什么呢
因为数组本来就是引用类型,他们是引用关系,
如果真想定义一个动都不能然它动的,那么对象身上有个方法Object.freeze(),冻结的意思
const arr = Object.freeze(["apple","banana"]);
arr.push("orange");
console.log(arr); // 这个时候报类型错误
// Uncaught TypeError: Cannot add property 2, object is not extensible
// 不能添加属性,对象是不可扩展的
这不是const的问题,这是对象的特性问题
总结: const的特点
- const`一旦声明变量,就必须立即初始化,不能留到以后赋值。
- 一旦声明,常量的值就不能改变。
5.变量的其他问题
5.1自执行函数
IIFE
(function(){
// TODO
})();
// 因为之前只有函数作用域
// 现在有块作用域了
// 可以使用
{
// TODO
}
5.2. 关于顶层对象属性与全局变量
顶层对象,在浏览器环境指的是window
对象,在 Node 指的是global
对象。ES5 之中,顶层对象的属性与全局变量是等价的。
window.a = 1;
a // 1
a = 2;
window.a // 2
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。
为了解决这个问题,es6引入的let
、const
和class
声明的全局变量不再属于顶层对象的属性。
而同时为了向下兼容,var和function声明的变量依然属于全局对象的属性
var a = 1;
window.a // 1
let b = 1;
window.b // undefined
hhhhhhhhhhh