快速学习ES6、ES7、ES8
基础指南
概述
ECMAScript 版本 |
发布时间 | 新增特性 |
---|---|---|
ES5 |
2009年11月 | 扩展了Object 、Array 、Function 的功能 |
ES6 |
2015年5月 | 类,模块化,箭头函数,函数参数默认值等 |
ES7 |
2016年3月 | includes ,指数操作符 |
ES8 |
2017年6月 | sync/await ,Object.values() ,Object.entries() ,Stringpadding 等 |
ES6
的特性
安利几个常用的:
– 类
– 模块化
– 箭头函数
– 函数参数默认值
– 解构赋值
– 延展操作符
– 对象属性简写
– Promise
– let const
1.类 class
让JavaScript
的面向对象编程变得更加简单和易于理解
class Animal {
// 构造函数,实例化的时候将会被调用,如果不指定,那么会有一个不带参数的默认构造函数
constructor(name,color) {
this.name = name;
this.color = color;
}
// toString 原型对象上的属性
toString() {
console.log('name:' + this.name + ',color:' + this.color);
}
}
var animal = new Animal('dog','white'); // 实例化Animal
animal.toString();
console.log(animal.hasOwnProperty('name')); // true
console.log(animal.hasOwnProperty('toString')); // false
console.log(animal.__proto__.hasOwnProperty('toString')); // true
class Cat extends Animal {
constructor(action) {
// 子类必须要在 constructor 中指定 super 函数,否则在新建实例的时候会报错。
// 如果没有置顶 constructor。默认带 super 函数的 constructor 将会被添加。
super('cat','white');
this.action = action;
}
toString() {
console.log(super.toString());
}
}
var cat = new Cat('catch');
cat.toString();
// 实例 cat 是 Cat 和 Animal 的实例,和ES5完全一致
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true
2.模块化Module
ES5不支持原生的模块化,在ES6中模块作为重要的组成部分被添加进来。模块的功能主要有export
和import
组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过export
来规定模块对外暴露的接口,通过import
来引用它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。
导出exprot
ES6允许在一个模块中使用export
来导出多个变量或者函数。
导出变量
// test.js
export var name = 'Rainbow'
ES6不仅支持变量的导出,也支持常量导出。
export const sqrt = Math.sqrt
// 导出常量
ES6将一个文件视为一个模块,上面的模块通过exprot
向外输出了一个变量。一个模块也可以同时往外面输出多个变量。
// test.js
var name = 'Rainbow';
var age = '22';
export {name, age};
导出函数
// myModule.js
export function myModule(someArg) {
return someArg;
}
导入import
定义好模块的输出以后就可以在另外一个模块通过import
引用。
import {myModule} from 'myModule'; // main.js
import {name, age} from 'test'; //test.js
关于
import
语句可以同时导入默认函数和其它变量。import defaultMethod, {otherMethod} from 'xxx.js';
3.箭头Arrow
函数
这是ES6特性之一。=>
不只是关键字function
的简写,箭头函数与包围它的代码共享同一个this
。
箭头函数的结构
// 箭头函数的安利
()=>1
v=>v+1
()=>{
alert("foo");
}
e=>{
if(e == 0){
return 0;
}
return 1000/e;
}
卸载监听器时的注意
错误的做法
class PauseMenu extends React.Component{
componentWillMount(){
AppStateIOS.addEventListener('change', this.onAppPaused.bind(this));
}
componentWillUnmount(){
AppstateIOS.removeEventListener('change', this.onAppPaused.bind(this));
}
onAppPaused(event){
}
}
正确的做法
class PauseMenu extends React.Component{
constructor(props){
super(props);
this._onAppPaused = this.onAppPaused.bind(this);
}
componentWillMount(){
AppStateIOS.addEventListener('change', this._onAppPaused);
}
componentWillUnmount(){
AppstateIOS.removeEventListener('change', this._onAppPaused);
}
onAppPaused(event){
}
}
除上述的做法外,还可以这样做:
class PauseMenu extends React.Component{
componentWillMount(){
AppStateIOS.addEventListener('change', this.onAppPaused.bind(this));
}
componentWillUnmount(){
AppstateIOS.removeEventListener('change', this.onAppPaused.bind(this));
}
onAppPaused = (event) => {
// 把函数直接作为一个 arraw function 的属性来定义,初始化的时候就绑定好了this指针。
}
}
注意:不论是
bind
还是箭头函数,每次被执行都返回的是一个新的函数引用,因此如果还需要函数的引用做其它事情,那么就要保存这个引用。
4.函数参数默认值
ES6支持在定义函数的时候为其设置默认值:
function foo(height = 50, color = 'pink')
{
// ...
}
不是用默认值
function foo(height, color)
{
var height = height || 50;
var color = color || 'pink';
// ...
}
这样一般没问题,但是参数的布尔值为false
时,就会有问题了!例如:
foo(0,"");
因为0的布尔值为false
,这样height的取值就是50了,同理color取值就是pink了。
5.模板字符串
ES6支持模板字符串,使得字符串更加简洁直观。
不使用模板字符串
var name = 'Your name is ' + first + ' ' + last + '.';
使用模板字符串
var name = 'Your name is ${first} ${last}.';
6.解构赋值
解构赋值语法是JavaScript
的一种表达式,可以方便的从数组或者对象中快速提取值赋给定义的变量。
获取数组中的值
从数组中获取值并赋值到变量中,变量的顺序与数组中对象顺序对应。
var foo = ["one", "two", "three" ,"four"];
var [one,two,three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
// 如果要忽略某些值,可以按照下面的写法获取值
var [first, , , last] = foo;
console.log(first); // "one"
console.log(last); // "four"
// 也可以这样编写
var a, b; //先申明变量
[a, b] = [1,2];
console.log(a); // 1
console.log(b); // 2
如果没有从数组中获取到值,可以为变量设置一个默认值。
var a, b;
[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7
通过解构赋值可以方便的交换两个变量的值。
var a=1;
var b=3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
获取对象中的值
const student = {
name:'Ming',
age:'18',
city:'Shanghai'
};
const {name,age,city} = student;
console.log(name); // "Ming"
console.log(age); // "18"
console.log(city); // "Shanghai"
7.延展操作符(Spread operator)
延展操作符...
可以在函数调用数组构造时,将数组表达式或者string
在语法层面展开;还可以在构造对象时,将对象表达式按key-value
的方式展开。
语法
函数调用:
myFunction(...iterableObj);
数组构造或字符串:
[...iterableObj, '4', ...'hello', 6];
构造对象时,进行克隆或者属性拷贝(ECMAScript2018规范新特性):
let objClone = { ...obj };
应用场景
在函数调用时使用延展操作符
function sum(x, y, z){
return x + y + z;
}
const numbers = [1, 2, 3];
// 不使用延展操作符
console.log(sum.apply(null, numbers)); // 6
// 使用延展操作符
console.log(sum(...numbers)); // 6
构造数组
没有展开语法的时候,只能组合使用 push
,splice
,concat
等方法,来将已有数组元素变成新数组的一部分。有了展开语法,构造新数组会变得更简单、更优雅:
const stuendts = ['Jine','Tom'];
const persons = ['Tony', ... stuendts,'Aaron','Anna'];
console.log(persions)// ["Tony", "Jine", "Tom", "Aaron" ,"Anna"];
和参数列表的展开类似,...
在构造数组时,可以在任意位置多次使用。
数组拷贝
var arr = [1, 2, 3];
var arr2 = [...arr]; // 等同于 arr.slice()
arr2.push(4);
console.log(arr2); // [1, 2, 3, 4]
展开语法和Object.assign()
行为一致,执行的都是浅拷贝(只遍历一层)。
链接多个数组
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
var arr3 = [...arr1, ...arr2]; // 将 arr2 中所有元素附加到 arr1 后面并返回
// 等同于
var arr4 = arr1.concat(arr2);
在ECMAScript 2018中延展操作符增加了对对象的支持:
var obj1 = { foo: 'bar', x:42 };
var obj2 = { foo: 'baz', y:13 };
var cloneObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x:42 }
var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象:{ foo: "baz", x: 42 y: 13 }
在React
中的应用
通常我们在封装一个组件时,会对外公开一些props
用于实现功能。大部分情况下在外部使用都应显示的传递props
。但是当传递大量的props
时,会非常繁琐,这时我们可以使用 ...
(延展操作符,用于取出参数对象的所有可遍历属性)来进行传递。
一般情况下应该的安利:
<CustomComponent name ='Jine' age = {21} />
使用
...
,等同于上面的写法
const params = {
name: "Jine",
age: 21
}
<CustomComponent {...params} />
配合结构赋值避免传入一些不需要的参数
var params = {
name: '123',
title: '456',
type: 'aaa',
}
var { type, ...other } = params;
<CustomComponent type='normal' number={2} {...other} />
// 等同于
<CustomComponent type='normal' number={2} name='123' title='456' />
8.对象属性简写
在ES6中允许我们在设置一个对象的属性的时候不指定属性名。
不使用ES6
const name = 'Ming' , age = '18' , city = 'Shanghai';
const student = {
name:name,
age:age,
city:city
}
console.log(student); // {name: "Ming", age: "18", city: "Shanghai"}
对象中必须包含属性和值,显得非常沉余。
使用ES6
const name = "Ming",age="18",city="Shanghai";
const student = {
name,
age,
city
};
console.log(student); // {name: "Ming", age: "18", city: "Shanghai"}
对象中直接写变量,非常简洁。
9.Promise
Promise
是异步编程的一种解决方案,比传统的解决方案callback
更加优雅。它最早是由社区提出和实现的,ES6
将其写进了语言标准,统一了用法,原生提供了Promise
对象。
不使用ES6
嵌套两个setTimeout
回调函数:
setTimeout(function(){
console.log('hello'); // 1秒后输出"hello"
setTimeout(function(){
console.log('hi'); // 2秒后输出"hi"
}, 1000);
}, 1000);
使用ES6
var waitSecond = new Promise(function(resolve, reject){
setTimeout(resolve, 1000);
});
waitSecond.then(function(){
console.log("hello"); // 1秒后输出"hello"
return waitSecond;
}).then(function(){
console.log('hi'); // 2秒后输出"hi"
});
上面的代码使用两个then
来进行异步编程串行化,避免了回调地狱。
10.支持let
与const
在之前JS是没有块级作用域的,const与let填补了这方面的空白,const与let都是块级作用域。
使用
var
定义的变量为函数级作用域:
{
var a = 10;
}
console.log(a); // 输出10
使用
let
与const
定义的变量为块级作用域:
{
let a = 10;
}
console.log(a); // Uncaught ReferenceError: a is not defined
ES7
的新特性
在ES6之后,ES的发布频率更加频繁,基本每年一次,所以自ES6之后,每个版本的特性的数量就比较少。
– includes()
– 指数操作符
1.Array.prototype.includes()
includes()
函数用来判断一个数组是否包含一个指定的值,如果包含则返回true
,否则犯规false
。
includes()
函数与indexOf
函数很相似,下面两个表达式是等价的:
arrincludes(x);
arr.indexOf(x) >= 0;
接下来看判断数字中是否包含某个元素:
在ES7之前的做法
使用indexOf()
验证数组中是否存在某个元素,这时需要根据返回值是否为-1
来判断:
let arr = ['react', 'angular', 'vue'];
if (arr.indexOf('react') !== -1 ) {
console.log('react 存在');
}
使用ES7的
includes()
使用includes()
验证数组中是否存在某个元素,这样更加直观简单:
let arr = ['react', 'angular', 'vue'];
if (arr.includes('react')) {
console.log('react 存在');
}
2.指数操作符
在ES7中引入了指数运算符**
,**
具有与Math.pow(..)
等效的计算结果。
不使用指数操作符
使用自定义的递归函数calculateExponent
或者Math.pow()
进行指数运算:
function calculateExponent(base, exponent){
if (exponent === 1) {
return base;
} else {
return base * calculateExponent(base, exponent-1);
}
}
使用指数操作符
使用指数运算符**
,就像+
、-
等操作符一样:
console.log(2**10); // 输出1024
ES8的特性
async/await
Object.values()
Object.entries()
String padding
- 函数参数列表结尾允许逗号
Object.getOwnPropertyDescriptors()
1.async/await
在ES8中加入了对async/await
的支持,也就是所说的异步函数
,这是一个很实用的功能。async/await
将我们从头痛的回调地狱中解脱出来了,使整个代码看起来很简洁。
使用
async/await
与不使用async/await
的差别:
login(userName){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve('1001');
}, 600);
});
}
getData(userID){
return new Promise((resolve, reject) => {
setTimeout(()=>{
if (userID === '1001') {
resolve('SUCCESS');
} else {
reject('FAIL');
}
},600);
});
}
// 不使用async/await ES7
doLogin(userName){
this.login(userName).then(this.getData).then(result => {
console.log(result);
});
}
// 使用async/await ES8
async doLogin2(userName) {
const userID = await this.login(userName);
const result = await this.getData(userID);
}
this.doLogin(1);// SUCCESS
this.doLogin2(1);// SUCCESS
async/await
的几种应用场景
获取异步函数返回值
异步函数本身返回一个Promise
,所以可以通过then
来获取异步函数的返回值。
async function charCountAdd(data1,data2){
const d1 = await charCount(data1);
const d2 = await charCount(data2);
return d1+d2;
}
charCountAdd('Hello', 'Hi').then(console.log); //通过then获取异步函数的返回值
function charCount(data){
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(data.length);
}, 1000);
});
}
async/await
在并发场景中的应用
对于上诉的例子,我们调用await
两次,每次都是等待1秒一共是2秒,效率比较低,而且两次await
的调用并没有依赖关系,那能不能让其并发执行呢?答案是肯定的,接下来通过Promise.all
来实现await
的并发调用。
async function charCountAdd(data1, data2){
const [d1,d2] = await Promise.all([charCount(data1),charCount(data2)]);
return d1+d2;
}
charCountAdd('Hello','Hi').then(console.log);
function charCount(data) {
return new Promise( (resolve, reject) => {
setTimeout( () => {
resolve(data.length);
}, 1000);
});
}
通过上述代码实现了两次charCount
的并发调用,Promise.all
接受的是一个数组,它可以将数组中的Promise
对象并发执行。
async/await
的几种错误处理
第一种:捕捉整个async/await
函数的错误
async function charCountAdd(data1, data2){
const d1 = await charCount(data1);
const d2 = await charCount(data2);
return d1+d2;
}
charCountAdd('Hello','Hi').then(console.log).catch(console.log);//捕捉整个`async/await`函数的错误
这种方式可以捕捉整个charCountAdd
运行过程中出现的错误,错误可能是由charCountAdd
本身产生的,也可能是由对data1
的计算中或data2
的计算中产生的。
第二种:捕捉单个的await
表达式的错误
async function charCountAdd(data1, data2) {
const d1 = await charCount(data1).catch(e => console.log('d1 is null'));
const d2 = await charCount(data2).catch(e => console.log('d2 is null'));
return d1+d2;
}
charCountAdd('Hello','Hi').then(console.log);
通过这种方式可以捕捉每一个await
表达式错误。如果既要捕捉每个await
表达式错误,又要捕捉整个charCountAdd
函数的错误,可以在调用charCountAdd
的时候加个catch
进行捕捉。
charCountAdd('Hello','Hi').then(console.log).catch(console.log);// 捕捉整个async/await函数错误
第三种:同时捕捉多个await
表达式的错误
async function charCountAdd(data1, data2) {
let d1,d2;
try {
d1 = await charCount(data1);
d2 = await charCount(data2);
} catch (e) {
console.log(e);
}
return d1+d2;
}
charCountAdd('Hello','Hi').then(console.log);
function charCount(data) {
return new Promise( (resolve, reject) => {
setTimeout( () => {
resolve(data.length);
}, 1000);
});
}
2.Object.values()
Object.values()
是一个与Object.keys()
类似的新函数,但返回的是Object
自身属性的所有值,不包括继承的值。
假设我们要遍历如下对象obj
的所有值:
const obj = {a:1, b:2, c:3};
不使用
Object.values()
:ES7
const val = Object.keys(obj).map(key => obj[key]);
console.log(val);// [1,2,3]
使用
Object.values()
:ES8
const values = Object.values(obj);
console.log(values); // [1,2,3]
从上述代码中可以看出Object.values()
为我们省去了遍历key
,并根据这些key
获取value
的步骤。
3.Object.entries
Object.entries()
函数返回一个给定对象自身可枚举属性的键值对的数组。
接下来我们来遍历上文中的obj
对象的所有属性的key
和value
:
不使用
Object.entries()
:ES7
Object.keys(obj).forEach(key => {
console.log('key:' + key + ' value:' + obj[key]);
});
// key:a value:1
// key:b value:2
// key:c value:3
使用
Object.entries()
:ES8
for(let [key,value] of Object.entries(obj)) {
console.log(`key: ${key} value:${value}`);
}
// key:a value:1
// key:b value:2
// key:c value:3
4.String padding
在ES8
中String
新增了两个实例函数String.prototype.padStart
和String.prototype.padEnd
,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。
String.padStart(targetLength,[padString])
targetLength
:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。padString
:(可选)填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为“ ”。
console.log('0.0'.padStart(4,'10')); //10.0
console.log('0.0'.padStart(20)); // 0.0
String.padEnd(targetLength,[padString])
targetLength
:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。padString
:(可选)填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为“ ”。
console.log('0.0'.padEnd(4,'0')); //0.00
console.log('0.0'.padEnd(10,'0')); //0.00000000
5.函数参数列表结尾允许逗号
一个小更新而已,不用多说了。只是方便应用而已。
var f = function(a, b, c,){
// tudo
}
6.Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors()
函数用来获取一个对象的所有自身属性的描述符,如果没有任何属性,则返回空对象。
函数原型:
Object.getOwnPropertyDescriptors(obj)
返回obj
对象的所有自身属性的描述符,如果没有任何属性,则返回空对象。
const obj = {
name: 'eson',
get age() {
return '18';
}
};
Object.getOwnPropertyDescriptors(obj);
总结
技术更替的车轮一直在前进中,JavaScript
的规范和标准也在不断的制定和完善。发现ECMAScript
新版的很多特效已经是Typescript
,浏览器或其他polyfills
的一部分,例如ES8
的async/await
来说,它是2017年6月被纳入ECMAScript
的,但是2016年的时候就已经开始使用这个特性了,这些特性通常由ECMAScript
议员提交,然后会出现在未来的某个ECMAScript
版本中。
2 条评论
小鸡鸡 · 2020年10月6日 下午8:59
总结
技术更替的车轮一直在前进中,JavaScript的规范和标准也在不断的制定和完善。发现ECMAScript新版的很多特效已经是Typescript,浏览器或其他polyfills的一部分,例如ES8的async/await来说,它是2017年6月被纳入ECMAScript的,但是2016年的时候就已经开始使用这个特性了,这些特性通常由ECMAScript议员提交,然后会出现在未来的某个ECMAScript版本中。
博主总结得很到位,学到了很多东西,加油!!!!!!!
小鸡鸡 · 2020年10月6日 下午7:44
666666666