什么是ES6?
ECMAScript 6.0 (下面简称ES6)是继ECMAScript5.1之后,javascript语言的下一代标准,发布于2015年6月。它的目标,是使得javascript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。下面将介绍ES6的一些新语法,使其在开发中更加语法简洁,效率更高。
1、变量声明const和let
在ES6之前,我们都是用var关键字声明变量。无论声明在何处,因为函数变量的提升,都会被视为声明在函数的最顶部(不在函数内即在全局作用域的最顶部)。例如:
function f(){
var flag=""
if(flag){
var test = "hello,world";
}else{
console.log(test);
}
}
f();
复制代码
以上代码实际是:
function f(){
var flag="";
var test; //变量提升到函数最顶部
if(flag){
var test = "hello,world";
}else{
console.log(test); //这里访问的test,值为undefined
}
}
f();
复制代码
所以不用关心flag是否为 true还是false。实际上,无论如何 test 都会被创建声明。
ES6出现后,我们一般用 let 和 const 来声明,let 表示变量、const 表示常量。let 和 const 都是块级作用域。怎么理解这个块级作用域?
说白了只要在{}花括号内的代码块即可以认为 let 和 const 的作用域。
看下面代码:
function f(){
var flag="";
if(flag){
let test = "hello,world";
}else{
console.log(test); //这里访问不到test,打印出test is not defined
}
}
f();
复制代码
let 的作用域是在它所在当前代码块,但不会被提升到当前函数的最顶部。
const 声明的变量必须提供一个值,而且会被认为是常量,意思就是它的值被设置完成后就不能再修改了。
const a=1;
a=2; //就会报这样的错误:Assignment to constant variable.
复制代码
还有一点,如果 const的是一个对象,对象所包含的值是可以被修改的。也就是说,对象所指向的地址不能改变,而变量成员是可以修改的。如下面代码:
const obj = {name:"xiaoqinag"}
obj.name = "wangcai"
console.log(obj.name); //wangcai
复制代码
2、解构赋值
按照一定模式,从数组和对象中提取,对变量进行赋值,称为解构。
2.1、数组的解构赋值
//一般赋值
var a=1,b=2,c=3;
var [a,b,c] = [1,2,3];
console.log([a,b,c]);
复制代码
//解构赋值
var [a, b, c] = [1, 2, 3];
console.log([a, b, c]);
复制代码
当然不仅仅是var,let和const也可以
let arr = [1, 2, 3];
const [a,b,c] = arr;
console.log([a, b, c]);
复制代码
解构赋值允许指定默认值
看下面代码:
let [a,b,c=666] = [1, 2, 3];
console.log(a); //1
console.log(b); //2
console.log(c); //3
复制代码
也就是说解构赋值的值级别高于自身的值。
let [a=1,b,c=3] = [];
console.log(a); //1
console.log(b); //undefined
console.log(c); //3
复制代码
上面代码说明了:数组解构赋值时,会先找解构赋值的值,然后再找自身的值,如果前两者都没有,会返回undefined.
2.2对象的解构赋值
解构可以用于数组,同样也可以用于对象。对象的解构与数组有一个重要的不同,数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
// 变量名与属性名一致的情况下
let{name,age} = {name:"wangcai",age:25}
console.log(name); //wangcai
console.log(age); //25
//变量名与属性名不一致的情况下,必须这样写
let {a : name, b : age} = {a : 'xiaoqinag', b : 30};
console.log(name); //xiaoqinag
console.log(age); //30
复制代码
这实际上说明,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let {a : name} = {a : 'xiaoqinag'};
console.log(name); //xiaoqinag
console.log(a); //a is not defined
复制代码
上面代码中,a是匹配的模式,name才是变量。真正被赋值的是变量name,而不是模式a。
数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
let arr=["laifu","wnagcai","xiaoqiang"];
let {0:first,1:center,[arr.length - 1]:last} = arr
console.log(first); //laifu
console.log(center); //wnagcai
console.log(last); //xiaoqiang
复制代码
3、数组的扩展
3.1 Array.from()
Array.from方法用于将两类对象转为真正的数组:类似数组的对象和可遍历的对象。
let obj={
"name":"wangcai",
"age":34,
"sex":"man"
}
let arr=Array.from(obj)
console.log(Array.isArray(arr)); //true
复制代码
3.2 Array.of()
作用:将一组值转换为数组。与Array.from功能相似,理解用来创建数组。主要目的是弥补构造器 Array()的不足。
之前使用new创建数组的痛点:
var arr1=[];
var arr2 = new Array(3);
var arr3 = new Array("3");
console.log(arr1,arr2,arr3); //[] [empty × 3] ["3"]
复制代码
使用Array.of来改造,如下:
var arr1 = Array.of(3);
var arr2 = Array.of("3");
console.log(arr1,arr2); //[3] ["3"]
复制代码
3.3 find()和findIndex()
find:用于找出第一个符合条件的数组元素。找不到则是undefined。注意,它是不会返回多个,只找一个,找到了就返回。
findIndex:返回第一个符合条件的数组元素的索引。找不到则是-1;
let arr=[
{name:"xiaoqiang",age:23},
{name:"wangcai",age:25},
{name:"laifu",age:34}
]
let rs = arr.find(function(item){
return item.name =="laifu";
})
console.log(rs); //{name: "laifu", age: 34}
let res = arr.findIndex(function(item){
return item.name =="wangcai";
})
console.log(res); //1
复制代码
3.4 includes()
作用:判断元素是否在数组中存在。返回值是 true | false
代码如下:
let arr = [1, 2, 3];
console.log(arr.includes(3)); //true
console.log(arr.includes(6)); //false
复制代码
indexOf也可以做类似的工作,如下:
let arr = [1, 2, 3];
console.log(arr.indexOf(3)); //2
console.log(arr.indexOf(6)); //-1
复制代码
但是,indexOf 对 NaN的判断是错误的,如下:
let arr = [NaN, 2, 3];
console.log(arr.indexOf(NaN)); //-1
复制代码
上面代码可以得到indexOf对NaN的判断是错误的,而includes()则没有这个问题。
3.5 fill()
作用:给数组填充指定值。fill方法用于空数组的初始化非常方便。已有数据会被覆盖。fill 方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。 代码如下:
let arr = new Array(5);
arr.fill("*");
console.log(arr); //["*", "*", "*", "*", "*"] //["*", "*", "*", "*", "*"]
复制代码
使用fill()会覆盖之前的元素,代码如下:
let arr =[1,2,3,4];
arr.fill("*");
console.log(arr); //["*", "*", "*", "*"]
复制代码
fill()还可以指定填充位置,如下:
let arr = [1,2,3,4,5,6]
arr.fill("*",2,4);
console.log(arr); //[1, 2, "*", "*", 5, 6]
复制代码
2 和 4表示下标,包括起点,不包括终点。
4、数组的扩展运算符
功能:把数据结构转成数组。
let arr = [1,2,3,4,5,6]
let arr2 = [...arr]
console.log(arr2); // [1, 2, 3, 4, 5, 6]
复制代码
可以这样理解:
var arr2 = [ ...arr ];
- [ ]表示一个数组。即 arr2 是一个数组。
- ...arr :把数组 arr 展开。理解分解起一个一个的元素
- 相当于实现了,数组的复制---这里是浅拷贝
还可以利用扩展运算符,把类数组转成数组,如下:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
var lis = document.getElementsByTagName("li");
var arr = [...lis];
console.log(Array.isArray(arr)); //true
</script>
复制代码
把一个类数组转成数组有如下几种方式:
- Array.prototype.slice.call();
- Array.from();
- [...类数组对象]
把字符串转成数组,如下:
var str = "hello";
var arr =[...str];
console.log(arr); //["h", "e", "l", "l", "o"]
复制代码
还可以利用扩展运算符来合并数组,如下:
var arr1 = [1,2];
var arr2 = [3,4];
var arr =[arr1,...arr2];
console.log(arr); //
复制代码
5、箭头函数
ES6可以使用“箭头”(=>)定义函数,注意是函数,不要使用这种方式定义类(构造器)。
5.1 语法
之前的函数写法:
function f(x){
return x*x;
}
let rs = f(2)
console.log(rs); //4
复制代码
箭头函数如下:
var f=(x)=>{
return x*x;
}
let rs = f(2)
console.log(rs); //4
复制代码
再改进,如果箭头函数中只有一个形参,那么()可以不写,如下:
var f=x=>{
return x*x;
}
let rs = f(2)
console.log(rs); //4
复制代码
如果函数体只有一条语句(非return语句),那么{}也可以不写,如下:
var f=()=>
console.log("hello world"); //hello world
f()
复制代码
如果有return 语句,把{}和reutrn 都可以省略了,如下:
var f=x=>x*x;
console.log(f(2)); //4
复制代码
5.2 注意点
1、this固定,不再善变
obj = {
data: ['wangcai', 'xiaoqiang'],
init: function () {
document.onclick = ev => {
console.log(this.data); // ['wangcai', 'xiaoqiang']
}
// 非箭头函数
// document.onclick = function(ev) {
// console.log(this.data) // undefined
// }
}
}
obj.init()
复制代码
2、箭头函数不能用new
var Person = (name, age) => {
this.name = name
this.age = age
}
var p = new Person('wangcai', 33) //Person is not a constructor
复制代码
3、不能使用argument
var func = () => {
console.log(arguments)
}
func(55) //arguments is not defined
复制代码
4、instanceof也返回true,表明也是Function的实例
var func = () => {}
console.log(func instanceof Function); //true
复制代码
6、模板字符串
ES5中采用 + 进行拼接,着实麻烦,ES6解决了ES5在字符串功能上的痛点,简直是开发者的福音。
1、基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定。
//es5语法
var name = "wangcai";
console.log("hello"+name); //hellowangcai
//es6语法
var name = "xiaoqiang";
console.log(`hello ${name}`); //hello xiaoqiang
复制代码
2、如果使用模版字符串表示多行字符串,所有的空格和缩进都会被保存在输出中。
console.log(`It's raining
today`);
//It's raining
// today
复制代码
3、在${}中的大括号里可以放入任意的JavaScript表达式,还可以进行运算,以及引用对象属性。
let a = 12;
let b = 22;
let c = "";
console.log(`c=a+b=${a+b}`); //c=a+b=34
复制代码
4、更强大的是:模版字符串还可以调用函数。
function string(){
return "Learning ES6!!";
}
console.log(`你今天在做什么?
${string()}`); //你今天在做什么?
//Learning ES6!!
复制代码
7、Set 和 Map 数据结构
7.1 Set
ES6 提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 代码如下:
var arr=[1,2,3,1,5,2];
var s = new Set(arr);
console.log(s); //{1, 2, 3, 5}
复制代码
使用add()来添加,如下:
var s = new Set();
s.add(1);
s.add(2);
s.add(3);
s.add(4);
console.log(s); //{1, 2, 3, 4}
复制代码
可以使用forEach遍历s,如下:
var s = new Set();
s.add(1);
s.add(2);
s.add(3);
s.add(4);
s.forEach(item=>console.log(item) //1 2 3 4
)
复制代码
set不是数组,是一个像对象的数组,是一个伪数组,使用Array.isArray测试如下:
var s = new Set();
s.add(1);
s.add(2);
s.add(3);
s.add(4);
console.log(Array.isArray(s)); //false
复制代码
删除set其中的一个元素,如下:
var s = new Set();
s.add(1);
s.add(2);
s.add(3);
s.add(4);
s.delete(2)
console.log(s); //{1, 3, 4}
复制代码
可以使用Set实现数组的去重的问题,简洁明了,代码如下:
var arr = [1,2,7,9,2,7];
var arr1 = [...(new Set(arr))];
console.log(arr1); //[1, 2, 7, 9]
复制代码
7.2 Map
Map类似于对象,里面存放也是键值对,区别在于:对象中的键名只能是字符串,如果使用map,它里面的键可以是任意值。 创建Map,如下:
var m = new Map([
["a","hello"],
["1","123"]
]);
console.log(m); //{"a" => "hello", "1" => "123"}
复制代码
使用set进行添加,如下:
var m = new Map([
["a","hello"],
["1","123"]
]);
m.set(false,"abc");
m.set([1,2,3],{name:"wangcai"})
console.log(m);
//{"a" => "hello", "1" => "123", false => "abc", Array(3) => {…}}
复制代码
还可以获取,通过get(键),如下:
var m = new Map([
["a","hello"],
["1","123"]
]);
m.set(false,"abc");
m.set([1,2,3],{name:"wangcai"})
console.log(m.get("a")); //hello
复制代码
但是,使用m.get([1,2,3]),获取不到值,原因是get()是要比较栈区中的地址,而不是堆区中的数据,可以使用下面这个方法使m.get([1,2,3]),获取到值。
var m = new Map([
["a","hello"],
["1","123"]
]);
m.set(false,"abc");
console.log(m.get("a")); //hello
let a=[1,2,3];
m.set(a,{name:"wangcai"})
console.log(m.get(a)); //{name: "wangcai"}
复制代码
重复的键会覆盖,如下:
var m=new Map();
m.set(1,"aaa");
m.set(1,"bbb");
console.log(m); //Map(1){1 => "bbb"}
复制代码
8、Class 的基本语法
ES6 提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
8.1class创建对象
在ES6中,可以使用class来声明一个类了,如下:
class Person{
constructor(name,age,height){
this.name = name;
this.age = age;
this.height = height;
}
say(){
console.log(`我叫${this.name},今年${this.age}岁,身高为${this.height}`);
}
}
var p = new Person("旺财","23","187");
console.log(p.say()); //我叫旺财,今年23岁,身高为187
复制代码
注意:
- class 是关键字,后面紧跟类名,类名首字母大写,采取的是大驼峰命名法则。类名之后是{}。
- 在{}中,不能直接写语句,只能写方法,方法不需要使用关键字
- 方法和方法之间没有逗号,不是键值对。
8.2 类的静态方法 static
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Person{
static say(){
console.log("hello world");
}
}
var p = new Person();
Person.say() //hello world
p.say(); //p.say is not a function
复制代码
上面代码中,Person类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Person类上调用(Person.classMethod()),而不是在Person类的实例上调用。如果在实例上调用静态方法,会出现一个错误,表示不存在该方法。
8.3 使用extends实现继承
使用ES6中的extends来实现继承,代码如下:
class Person{
constructor(name,age,height){
this.name = name;
this.age = age;
this.height = height;
}
say(){
console.log(`我叫${this.name},今年${this.age}岁,身高为${this.height}`);
}
}
class Student extends Person{
constructor(name,age,height,profession){
super(name,age,height)
this.profession = profession;
}
showProfession(){
console.log(`我叫${this.name},是一个${this.profession}`);
}
}
var s = new Student("旺财","23","187","学生");
s.say() //我叫旺财,今年23岁,身高为187
s.showProfession() //我叫旺财,是一个学生
复制代码
注意:
- 使用 extends 关键字来实现继承。
- 在子类中的构造器 constructor 中,必须要显式调用父类的 super。 方法,如果不调用,则 this 不可用。
总结
ES6新特性远不止于此,本文列举的是一些常见的特性,在日常开发中,能算得上是使用率较高的了,能够正确的使用这些新特性,可以使你的开发事半功倍。