概述
ECMAScript 6(简称ES6)是于2015年6月正式发布的JavaScript语言的标准,正式名ECMAScript 2015(ES2015)。它的目标是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言 [。
另外,一些情况下ES6也泛指ES2015及之后的新增特性,虽然之后的版本应当称为ES7、ES8等
为什么学习ES6
- ES6 的版本变动内容最多,具有里程碑意义
- ES6 加入许多新的语法特性,编程实现更简单、高效
- ES6 是前端发展趋势,就业必备技能
ES6新特性
let 关键字
ES6 新增了 let 命令,用来声明变量。它的用法类似于 var ,但是所声明的变量,只在 let 命令所在的代码块内有效。l使用 let 声明的变量有几个特点:
- 不允许重复声明
- 块级作用域
- 不存在变量提升
- 不影响作用域链
//声明变量
let a;
let b,c,d;
let e = 10;
let f = 521, g = 'asd', h = [];
//1. 变量不能重复声明
let star = '谭梦寻';
// let star = '小名';//报错--Cannot redeclare block-scoped variable
//2. 块级作用域 全局, 函数, eval
// if else while for
{
let girl = '小红';
}
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
//console.log(girl);//报错--ReferenceError: girl is not defined
//3. 不存在变量提升
// console.log(song);//Cannot access 'song' before initialization
// let song = '恋爱告急';
//4. 不影响作用域链
{
let school = '希望小学';
function fn(){
console.log(school);
}
fn();//希望小学
}
暂时性死区:
只要块级作用域内存在 let 命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123
if (true) {
tmp = 'abc' // ReferenceError: Cannot access 'tmp' before initialization
let tmp
}
上面代码中,存在全局变量 tmp ,但是块级作用域内 let 又声明了一个局部变量 tmp ,导致后者绑定这个块级作用域,所以在 let 声明变量前,对 tmp 赋值会报错。
ES6 明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,
就会报错。
总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)
let和var对比学习
var声明的变量全局有效,允许重复声明,存在变量提升(var 命令会发生”变量提升“现象,即变量可以在声明之前使用,值为 undefined )。而let不允许重复声明,存在块级作用域、不存在变量提升、不影响作用域链 。如下:
作用域1:
console.log(b);//输出undefined
var b = 2;//注意 这里不能省,否则报错
var b = 3;
{
var s2 = "1asd";
}
console.log(s2);//输出 1asd
作用域2:
var a = []
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i)
}
}
a[6]() // 10
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i)
}
}
a[6]() // 6
解释:上面代码中,变量 i 是 var 命令声明的,在全局范围内都有效,所以全局只有一个变量 i 。每一次循环,变量 i 的值都会发生改变,而循环内被赋给数组 a的函数内部的 console.log(i) ,里面的 i 指向的就是全局的 i 。也就是说,所有数组 a 的成员里面的 i ,指向的都是同一个 i ,导致运行时输出的是最后一轮的 i 的值,也就是 10。
如果使用 let ,声明的变量仅在块级作用域内有效,最后输出的是 6
变量注意:
ECMAScript的变量是松散型的,即可以用来保存任何类型的数据。如下:
let c = 3;
c = "asd";
console.log(c)//输出asd
const 关键字
const 声明一个只读的常量。一旦声明,常量的值就不能改变。
const 声明有以下特点:
- 声明必须赋初始值
- 标识符一般为大写
- 不允许重复声明
- 值不允许修改
- 块儿级作用域
注意: 对象属性修改和数组元素变化不会发生 const 错误
应用场景:声明对象类型使用 const,非对象类型声明选择 let
例子: 值不允许修改
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
上面代码表明改变常量的值会报错。const 声明的变量不得改变值,这意味着, const 一旦声明变量,就必须立即初始化,不能留到以后赋值 。
声明必须赋初始值
const f;
// SyntaxError: Missing initializer in const declaration
const 的作用域与 let 命令相同:只在声明所在的块级作用域内有效
if (true) {
const MAX = 5;
}
console.log(MAX); // Uncaught ReferenceError: MAX is not defined
const 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
var tmp = 123
if (true) {
tmp = 'abc'
const tmp;//SyntaxError: Missing initializer in const declaration
}
const 声明的常量,也与 let 一样不可重复声明。
var message = "Hello!";
let age = 25;
// 以下两行都会报错
const message = "Goodbye!";
const age = 30;
对象属性修改和数组元素变化不会发生 const 错误
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
const本质
const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针, const 只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。
变量解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。以前为变量赋值,只能直接指定值。
数组的解构
const F4 = ['小沈阳', '刘能', '赵四', '宋小宝']
let [xiao, liu, zhao, song] = F4
console.log(xiao)//小沈阳
console.log(liu)//刘能
console.log(zhao)//赵四
console.log(song)//宋小宝
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。 本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
数组解构默认值
解构赋值允许指定默认值。
let [foo = true] = [];
console.log(foo); // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
注意事项:
只有当一个数组成员严格等于 undefined ,默认值是才会生效的。(ES6 内部使用严格相等运算符( === ),判断一个位置是否有值。)
let [n = 1] = [undefined];
console.log(n); // 1
let [n2 = 1] = [null];
console.log(n2);//null
对象的解构
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
const zhao = {
name: '赵本山',
age: '不详',
xiaopin: function () {
console.log('我可以演小品')
}
}
let {name, age, xiaopin} = zhao;
console.log(name);
console.log(age);
console.log(xiaopin);
xiaopin();
输出结果:
也可以直接通过函数名解构出里面的函数:
const zhao = {
name: '赵本山',
age: '不详',
xiaopin: function () {
console.log('我可以演小品')
}
}
let { xiaopin } = zhao
xiaopin()//我可以演小品
如果变量名与属性名不一致,必须写成下面这样。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
console.log(baz); // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
console.log(f); // 'hello'
console.log(l); // 'world'
console.log(foo);//foo is not defined
解释:对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。上面代码中, foo 是匹配的模式, baz 才是变量。真正被赋值的是变量 baz ,而不是模式 foo 。
对象解构默认值
默认值生效的条件是,对象的属性值严格等于 undefined 。
// 对象解构默认值
var {x = 3} = {};
console.log(x); // 3
var {x, y = 5} = {x: 1};
console.log(x); // 1
console.log(y); // 5
var {x: y = 3} = {};
console.log(y);// 3
var {x: y = 3} = {x: 5};
console.log(y);// 5
var { message: msg = 'Something went wrong' } = {};
console.log(msg); // "Something went wrong
var {x = 3} = {x: undefined};
console.log(x); // 3
var {x = 3} = {x: null};
console.log(x); // null
如果解构失败 ,变量的值等于 undefined 。
let {foo} = {bar: 'baz'};
console.log(foo); // undefined
注意事项:
1、如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。
let {
user: { bar }
} = { baz: 'baz' }
//TypeError: Cannot read properties of undefined (reading 'bar')
解释:等号左边对象的user属性,对应一个子对象。该子对象的 bar 属性,解构时会报错。原因很简单,因为 foo 这时等于 undefined ,再取子属性就会报错,请看下面的代码。
let _tmp = {baz: 'baz'};
_tmp.foo // undefined
_tmp.foo.bar // 报错
2、如果要将一个已经声明的变量用于解构赋值,必须非常小心。
// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error
上面代码的写法会报错,因为 JavaScript 引擎会将 {x} 理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题
// 正确的写法
let x;
({x} = {x: 1});
字符串解构
字符串也可以解构赋值。这是因为字符串被转换成了一个类似数组的对象,且类似数组的对象都有一个 length 属性,因此还可以对这个属性解构赋值。如下:
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length : len} = 'hello';
len // 5
数值和布尔值的解构赋值
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于 undefined 和 null 无法转为对象,所以对它们进行解构赋值,都会报错。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
函数参数的解构赋值
函数的参数也可以使用解构赋值
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
函数参数的解构也可以使用默认值 (原理同上)
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]