一、ES6简介
ES6(ECMAScript 6.0)是一个历史名词,也是一种泛指,指代ECMAScript5.1版本之后JavaScript的下一代标准。
二、变量声明let和const
ES6之前,通常用var关键字来声明变量。无论在何处声明,都会被视为在所在函数作用域最顶部(变量提升)。
let和const使用的好处:
- 可以解决ES5使用var初始化变量时会出现的变量提升问题。
- 可以解决使用闭包时出错的问题。
- ES5只有全局作用域和函数作用域,却没有块级作用域。
- 可以解决使用计数的for循环变量时会导致泄露为全局变量的问题。
let命令表示被声明的变量值在作用域内生效。
{
let a = 1;
var b = 2;
}
console.log(a); //出错
console.log(b); //2
从上述可以看出,let声明的代码只能在其所在的代码块内有效,出了代码块,就会报错。
对于let来说,不存在变量提升。在一般代码逻辑中,变量应该是在定义后才可以使用,但var的变量提升却可以先使用再定义,而let更改了这种语法行为。要使用一个变量必须先声明,不然就会报错。
代码示例如下所示:
console.log(a); //undefined
var a = 1;
console.log(b); //出错
let b = 2;
在let中不允许重复声明同一个变量。
在代码块内,使用let声明变量之前,该变量都是不可用的(不可访问、不可赋值等)。在语法上,这被称为“暂时性死区”。暂时性死区(TDZ)就是只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取。只有等到声明变量的哪一行代码出现,才可以获取和使用该变量。
例如:
if (true) {
temp = "hello"; //出错
console.log(temp); //出错
let temp; //暂时性死区(TDZ)结束
console.log(temp);
temp = 1;
temp = "hello";
console.log(temp);
}
在ES5中,变量提升可能还会导致内层变量覆盖外层变量。
例如:
var i = 1;
function func() {
console.log(i);
if (true) {
var i = 1;
}
}
func() //undefined
let还引入了块级作用域的概念,传统ES5中不存在块级作用域。假如没有块级作用域,可能会出现这种问题:
var arr = [1, 2, 3, 4];
for(var i = 0; i < arr.length; i++){
console.log(i);
}
console.log(i); //4
在上述代码中,希望达到的结果是,在for循环之后变量i被垃圾回收机制回收。但用来计数的循环变量在循环结束后并没有被回收,内存泄漏为了全局变量。在这种情况下可以用块级作用域let。进行改写后的结果为:
var arr = [1, 2, 3, 4];
for(let i = 0; i < arr.length; i++){
console.log(i);
}
console.log(i); //ReferenceError: i is not defined
块级作用域的出现允许作用域的任意嵌套,同时内存作用域可以使用跟外层同名的变量名。
块级作用域可以使立即执行函数表达式(IIFE)不再成为必要项。
例如:
(function(){
var a = 1;
console.log(a);
}())
{
let a = 1;
console.log(a);
}
const用于声明只读的常量,一旦声明就不能改变。和let一样,const只在块级作用域内有效,不存在变量提升,存在暂时性死区和不可重复声明。
三、解构赋值
解构赋值:按照一定模式从数组或对象中提取值,对变量进行赋值。
提示:解构赋值的对象是数组或对象,作用是赋值。
const test = {
a: '1',
b: '2',
c: 3
};
let {a, b, c} = test;
console.log(a, b, c); //1 2 3
let {d, e, f} = test;
console.log(d, e, f); //undefined undefined undefined
注意:在进行对象的赋值中,其相关的变量名应该与该对象的属性名相同,其相关的顺序不重要。
传统的将对象中的属性值赋给变量的操作为:
const test = {
a: '1',
b: '2',
c: 3
};
let d = test.a;
let b = test.b;
let c = test.c;
console.log(d); //1
console.log(b); //2
console.log(c); //3
对象解构中指定默认值的操作如下:
var {a = 1} = {};
console.log(a); //1
var {a, b = 2} = {a: 1};
console.log(a); //1
console.log(b); //2
当解构不成功时,其相关变量的值为undefined;
代码示例如下所示:
let {a} = {b: 1};
console.log(a); //undefined
解构赋值在数组中也能进行相关的使用。
数组解构赋值示例:
let [a, b, c] = [1, 2, 3];
console.log(a); //1
console.log(b); //2
console.log(c); //3
let [x, , y] = [1, 2, 3];
console.log(x); //1
console.log(y); //3
let [e, f, ...g] = ["he"];
console.log(e); //he
console.log(f); //undefined
console.log(g); //[]
从以上的代码中可以看出其从数组中取到值后,按照对应位置赋值该对应变量。如果解构失败就会赋值为undfined。如果等号右边是不可遍历的解构,也会报错。
在数组解构中也允许给其赋初始值。
代码示例如下:
let [a = [1, 2]] = [];
console.log(a); //[1,2]
let [x, y = "hi"] = ["a", "b"];
console.log(x); //a
console.log(y); //b
四、拓展运算符(spread)…
拓展运算符是3个点…,可以将它比作rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
1、合并数组
在ES5中要合并两个数组,写法为:
var a = [1, 2];
var b = [3, 4];
var c = a.concat(b);
console.log(c); //[ 1, 2, 3, 4 ]
在ES6中拓展运算符提供了合并数组的新写法:
var a = [1, 2];
var b = [3, 4];
var c = [...a, ...b];
console.log(c); //[ 1, 2, 3, 4 ]
如果想让一个数组添加到另一个数组后面,在ES5中这样写:
var a = [1, 2];
var b = [3, 4];
Array.prototype.push.apply(a,b); //Array.prototype属性表示Array构造函数的原型,并允许你向所有Array对象添加新的属性和方法。
console.log(a); //[ 1, 2, 3, 4 ]
上述代码中由于push()方法不能为数组,所以通过apply()方法变相使用。
在ES6的拓展运算符后可以直接使用push()方法。
var a = [1, 2];
var b = [3, 4];
a.push(...b)
console.log(a); //[ 1, 2, 3, 4 ]
2、数组复制
拓展运算符还可以用于数组复制,进行复制中其所复制的是指向底层数据结构的指针,并非复制一个全新的数组。
示例:
const x = ['a', 'b'];
const x1 = x;
console.log(x1); //[ 'a', 'b' ]
3、与解构赋值结合
拓展运算符可以和解构赋值结合用于生成新数组。
const [arr1, ...arr2] = [1, 2, 3, 4];
console.log(arr1); //1
console.log(arr2); //[ 2, 3, 4 ]
注意:使用拓展运算符给数组赋值时,必须放在参数最后的位置,不然会报错。
4、函数调用(替代apply()方法)
在ES5中要合并两个数组,写法为:
function add(a, b) {
return a + b;
}
const num = [1, 10];
console.log(add.apply(null, num)); //11
在ES6中可以这样写:
function add(a, b) {
return a + b;
}
const num = [1, 10];
console.log(add(...num)); //11
上述代码使用拓展运算符将一个数组变为参数序列。当然,拓展运算符也可以和普通函数参数相结合使用。例如:
function add(a, b, c, d) {
return a + b + c + d;
}
const num = [1, 10];
console.log(add(1, ...num, -1)); //11
拓展运算符中的表达式如下:
[...(true ? [1, 2] : [3]), 'a']; //[1, 2, 'a']
5、箭头函数
ES6对于函数的拓展中增加了箭头函数=>,用于对函数的定义。
箭头函数语法很简单,先定义自变量,然后是箭头和函数主体。箭头函数相当于匿名函数简化了函数定义。
不引入参数的箭头函数示例:
var sun = () => 1 + 2; //圆括号代表参数部分
//等同于
var sum = function() {
return 1 + 2;
}
引入参数的箭头函数示例:
//单个参数
var sum = value => value; //可以不给参数value加小括号
//等同于
var sum = function(value) {
return value;
};
//多个参数
var sum = (a, b) => a + b;
//等同于
var sum = function(a, b){
return a + b;
}
花括号{}内的函数主体部分写法基本等同于传统的函数写法。
如果箭头函数内要返回自定义对象,需要用小括号把对象括起来。例如:
var getInfo = id => ({
id: 'id',
title: 'hello'
});
//等同于
var getInfo = function(id) {
return {
id: 'id',
title: 'hello'
}
}
箭头函数于传统的JavaScript函数的主要区别:
- 箭头函数内置this不可改变。
- 箭头函数不能使用new关键字来实例化对象。
- 箭头函数没有arguments对象,无法通过arguments对象访问传入的参数。
对this的绑定是JavaScript错误的常见来源。首先容易丢失函数内置数值,或得到意外结果。其次将箭头函数限制为使用固定this引用,有利于JavaScript引擎优化处理。
箭头函数看似是匿名函数的简写,但与匿名函数有明显区别,箭头函数内部的this是词法作用域,由上下文确定。如果使用了箭头函数,就不能对this进行修改,所以用call()或apply()调用箭头函数时都无法对this进行绑定,传入的第一个参数会被忽略。
提示: 词法作用域是定义在词法阶段的作用域,它在代码书写的时候就已经确定。