什么是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); //1console.log(b); //2console.log(c); //3复制代码
也就是说解构赋值的值级别高于自身的值。
let [a=1,b,c=3] = [];console.log(a); //1console.log(b); //undefinedconsole.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 展开。理解分解起一个一个的元素
- 相当于实现了,数组的复制---这里是浅拷贝
还可以利用扩展运算符,把类数组转成数组,如下:
- 1
- 2
- 3
- 4
- 5
把一个类数组转成数组有如下几种方式:
- 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 = namethis.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")); //hellolet 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新特性远不止于此,本文列举的是一些常见的特性,在日常开发中,能算得上是使用率较高的了,能够正确的使用这些新特性,可以使你的开发事半功倍。