ES6、ES7、ES8
ES7
1.Array.prototype.includes()
includes()作用,是查找一个值在不在数组里,若是存在则返回true,不存在返回false.
1.基本用法:
['a', 'b', 'c'].includes('a') // true
['a', 'b', 'c'].includes('d') // false
2.接收俩个参数:要搜索的值和搜索的开始索引
['a', 'b', 'c', 'd'].includes('b') // true
['a', 'b', 'c', 'd'].includes('b', 1) // true
['a', 'b', 'c', 'd'].includes('b', 2) // false
3.与ES6中的indexOf()
比较
有些时候是等效的
['a', 'b', 'c'].includes('a') //true
['a', 'b', 'c'].indexOf('a') > -1 //true
var arr = [1, 2, 3]
var a = 1;
arr.includes(a) //true
arr.indexOf(a) //0
- 在判断 +0 与 -0 时,被认为是相同的。
[1, +0, 3, 4].includes(-0) //true
[1, +0, 3, 4].indexOf(-0) //1
- 只能判断简单类型的数据,对于复杂类型的数据,比如对象类型的数组,二维数组,这些,是无法判断的.
var arr = [1, [2, 3], 4]
arr.includes([2, 3]) //false
arr.indexOf([2, 3]) //-1
优缺点比较
- 简便性
includes()
返回的是布尔值,能直接判断数组中存不存在这个值,而indexOf()
返回的是索引,这一点上前者更加方便。
精确性
两者都是采用
===
的操作符来作比较的,不同之处在于:对于NaN
的处理结果不同。我们知道js中
NaN === NaN
的结果是false,indexOf()
也是这样处理的,但是includes()
不是这样的。let demo = [1, NaN, 2, 3] demo.indexOf(NaN) //-1
demo.includes(NaN) //true
总结:
由于它对NaN的处理方式与indexOf不同,假如你只想知道某个值是否在数组中而并不关心它的索引位置,建议使用includes()。如果你想获取一个值在数组中的位置,那么你只能使用indexOf方法。
2.求幂运算符
基本用法:
3 ** 2 //9
效果同
Math.pow(3, 2) //9
由于是运算符,所以可以和 +=
一样的用法
var b = 3;
b **= 2;
console.log(b); //9
ES8
1.async await
异步函数async function()
1.1作用
避免有更多的请求操作,出现多重嵌套,也就是俗称的“回调地狱”
this.$http.jsonp('/login', (res) => {
this.$http.jsonp('/getInfo', (info) => {
// do something
})
})
因此提出了ES6的Promise,将回调函数的嵌套,改为了链式调用:
var promise = new Promise((resolve, reject) => {
this.login(resolve);
})
.then(() => {
this.getInfo()
})
.catch(() => {
console.log('Error')
})
1.2声明方式
异步函数存在以下四种使用形式:
- 函数声明:
async function foo() {}
- 函数表达式:
const foo = async function() {}
- 对象的方式:
let obj = { async foo() {} }
- 箭头函数:
const foo = async () => {}
1.3支持返回Promise和同步的值
async用于定义一个异步函数,该函数返回一个Promise。
如果async函数返回的是一个同步的值,这个值将被包装成一个理解resolve的Promise,等同于return Promise.resolve(value)
。
await用于一个异步操作之前,表示要“等待”这个异步操作的返回值。await也可以用于一个同步的值。
//async await
//返回Promise
let timer = async function timer() {
return new Promise((reslove, reject) => {
setTimeout(() => {
reslove('a');
}, 1000);
})
}
timer().then(result => {
console.log(result);
}).catch(err => {
console.log(err.message);
})
//返回同步的值
let sayHello = async function sayHello() {
let hi = 'hello world'//等同于return Promise.resolve(hi);
return hi
}
sayHello().then(res => {
console.log(res)
}).catch(err => {
console.log(err.message);
})
1.4对异常的处理
首先来看下
Promise
中对异常的处理
1.使用reject
let promise = new Promise((reslove, reject) => {
setTimeout(() => {
reject('promise使用reject抛出异常')
}, 1000)
})
promise().then(res => {
console.log(res)
})
.catch(err => {
console.log(err) //'promise使用reject抛出异常'
})
2.使用new Error()
let promise = new Promise((reslove, reject) => {
throw new Error('promise使用Error抛出异常') //使用throw异常不支持放在定时器中
})
promise().then(res => {
console.log(res)
})
.catch(err => {
console.log(err.message) //'promise使用Error抛出异常'
})
3.reject
一个new Error()
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('promise抛出异常'));
}, 1000);
})
promise.then(res => {
console.log(res);
})
.catch(err => {
console.log(err.message); //'promise抛出异常'
})
async
对异常的处理也可以直接用.catch()
捕捉到
//async抛出异常
let sayHi = async sayHi => {
throw new Error('async抛出异常');
}
sayHi().then(res => {
console.log(res);
})
.catch(err => {
console.log(err.message);
})
和Promise链的对比:
我们的async函数中可以包含多个异步操作,其异常和Promise链有相同之处,如果有一个Promise被reject()那么后面的将不会再进行。
let count = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('promise故意抛出异常')
}, 1000);
})
}
let list = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([1, 2, 3])
}, 1000);
})
}
let getList = async () => {
let c = await count()
console.log('async') //此段代码并没有执行
let l = await list()
return { count: c, list: l }
}
console.time('start');
getList().then(res => {
console.log(res)
})
.catch(err => {
console.timeEnd('start')
console.log(err)
})
//start: 1000.81494140625ms
//promise故意抛出异常
可以看到上面的案例,async
捕获到了一个错误之后就会立马进入.catch()
中,不执行之后的代码
1.5并行
上面的案例中,async采用的是串行处理
count()和list()是有先后顺序的
let c = await count()
let l = await list()
实际用法中,若是请求的两个异步操作没有关联和先后顺序性可以采用下面的做法
let res = await Promise.all([count(), list()])
return res
//res的结果为
//[ 100, [ 1, 2, 3 ] ]
案例详情为:
let count = ()=>{
return new Promise((resolve,reject) => {
setTimeout(()=>{
resolve(100);
},500);
});
}
let list = ()=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve([1,2,3]);
},500);
});
}
let getList = async ()=>{
let result = await Promise.all([count(),list()]);
return result;
}
console.time('begin');
getList().then(result => {
console.timeEnd('begin'); //begin: 505.557ms
console.log(result); //[ 100, [ 1, 2, 3 ] ]
}).catch(err => {
console.timeEnd('begin');
console.log(err);
});
我们将count()和list()使用Promise.all()“同时”执行,这里count()和list()可以看作是“并行”执行的,所耗时间将是两个异步操作中耗时最长的耗时。
最后得到的结果是两个操作的结果组成的数组。我们只需要按照顺序取出数组中的值即可。
1.6与Generator的关系
先来回顾一下ES6中Generator
函数的用法:
function* getList() {
const c = yield count()
const l = yield list()
return 'end'
}
var gl = getList()
console.log(gl.next()) // {value: Promise, done: false}
console.log(gl.next()) // {value: Promise, done: false}
console.log(gl.next()) // {value: 'end', done: true}
虽然Generator将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。此时,我们便希望能出现一种能自动执行Generator函数的方法。我们的主角来了:async/await。
ES8引入了async函数,使得异步操作变得更加方便。简单说来,它就是Generator函数的语法糖。
let getList = async () => {
const c = await count()
const l = await list()
}
2.Object.entries()
2.1作用
作用:将一个对象中可枚举属性的键名和键值按照二维数组的方式返回。
若对象是数组,则会将数组的下标作为键值返回。
Object.entries({ one: 1, two: 2 }) //[['one', 1], ['two', 2]]
Object.entries([1, 2]) //[['0', 1], ['1', 2]]
2.2要点
1.若是键名是Symbol
,编译时会被自动忽略
Object.entries({[Symbol()]:1, two: 2}) //[['two', 2]]
2.entries()
返回的数组顺序和for
循环一样,即如果对象的key值是数字,则返回值会对key值进行排序,返回的是排序后的结果
Object.entries({ 3: 'a', 4: 'b', 1: 'c' }) //[['1', 'c'], ['3', 'a'], ['4', 'b']]
3.利用Object.entries()
创建一个真正的Map
var obj = { foo: 'bar', baz: 42 };
var map1 = new Map([['foo', 'bar'], ['baz', 42]]); //原本的创建方式
var map2 = new Map(Object.entries(obj)); //等同于map1
console.log(map1);// Map { foo: "bar", baz: 42 }
console.log(map2);// Map { foo: "bar", baz: 42 }
2.3自定义Object.entries()
Object.entries
的原理其实就是将对象中的键名和值分别取出来然后推进同一个数组中
//自定义entries()
var obj = { foo: 'bar', baz: 42 };
function myEntries(obj) {
var arr = []
for (var key of Object.keys(obj)) {
arr.push([key, obj[key]])
}
return arr
}
console.log(myEntries(obj))
//Generator版本
function* genEntryies(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]]
}
}
var entryArr = genEntryies(obj);
console.log(entryArr.next().value) //["foo", "bar"]
console.log(entryArr.next().value) //["baz", 42]
3.Object.values()
3.1作用
作用:只返回自己的键值对中属性的值。它返回的数组顺序,也跟
Object.entries()
保持一致
Object.values({ one: 1, two: 2 }) //[1, 2]
Object.values({ 3: 'a', 4: 'b', 1: 'c' }) //['c', 'a', 'b']
3.2与Object.keys()比较
ES6中的
Object.keys()
返回的是键名
var obj = { foo: 'bar', baz: 42 };
console.log(Object.keys(obj)) //["foo", "baz"]
console.log(Object.values(obj)) //["bar", 42]
//Object.keys()的作用就类似于for...in
function myKeys() {
let keyArr = []
for (let key in obj1) {
keyArr.push(key)
console.log(key)
}
return keyArr
}
console.log(myKeys(obj1)) //["foo", "baz"]
3.3entries()、values()总结
var obj = { foo: 'bar', baz: 42 };
console.log(Object.keys(obj)) //["foo", "baz"]
console.log(Object.values(obj)) //["bar", 42]
console.log(Object.entries(obj)) //[["foo", "bar"], ["baz", 42]]
4.字符串填充
4.1padStart()和padEnd()
字符串填充
padStart()
和padEnd()
用法
String.padStart(targetLength, padding)
参数:字符串目标长度和填充字段
'Vue'.padStart(10) //' Vue'
'React'.padStart(10) //' React'
'JavaScript'.padStart(10) //'JavaScript'
4.2要点
1.填充函数只有在字符长度小于目标长度时才有效,而且目标长度如果小于字符串本身长度时,字符串也不会做截断处理,只会原样输出
'Vue'.padEnd(10, '_*') //'Vue_*_*_*_'
'React'.padEnd(10, 'Hello') //'ReactHello'
'JavaScript'.padEnd(10, 'Hi') //'JavaScript'
'JavaScript'.padEnd(8, 'Hi') //'JavaScript'
5.Object.getOwnPropertyDescriptors()
5.1作用
该方法会返回目标对象中所有属性的属性描述符,该属性必须是对象自己定义的,不能是从原型链继承来的。
var obj = {
id: 1,
name: '霖呆呆',
get gender() {
console.log('gender')
},
set grad(d) {
console.log(d)
}
}
console.log(Object.getOwnPropertyDescriptors(obj))
//输出
{
gender: {
configurable: true,
enumerable: true,
get: f gender(),
set: undefined
},
grade: {
configurable: true,
enumerable: true,
get: undefined,
set: f grade(g)
},
id: {
configurable: true,
enumerable: true,
value: 1,
writable: true
},
name: {
configurable: true,
enumerable: true,
value: '霖呆呆',
writable: true
}
}
5.2与getOwnPropertyDescriptor()
比较
ES6中也有一个返回目标对象可枚举属性的方法
var obj = {
id: 1,
name: '霖呆呆',
get gender() {
console.log('gender')
},
set grad(d) {
console.log(d)
}
}
console.log(Object.getOwnPropertyDescriptor(obj, 'id'))
//输出结果
{
id: {
configurable: true,
enumerable: true,
value: 1,
writable: true
}
}
两者的区别:一个是只返回知道属性名的描述对象,一个返回目标对象所有自身属性的描述对象
5.3自定义该方法
function myDescriptors(obj) {
let descriptors = {}
for (let key in obj) {
descriptors[key] = Object.getOwnPropertyDescriptor(obj, key)
}
return descriptors
}
console.log(myDescriptors(obj))
//返回的结果和该方法一样
//其中上面自定义方法的for...in也可以换成,效果也是一样的
for (let key of Object.keys(obj)) {
descriptors[key] = Object.getOwnPropertyDescriptor(obj, key)
}
6.函数参数支持尾部逗号
该特性允许我们在定义或者调用函数时添加尾部逗号而不报错
let foo = function (
a,
b,
c,
) {
console.log('a:', a)
console.log('b:', b)
console.log('c:', c)
}
foo(1, 3, 4, )
//输出结果为:
a: 1
b: 3
c: 4
它适用于那种多行参数并且参数名很长的情况,开发过程中,如果忘记删除尾部逗号也没关系,ES8已经支持这种写法。
7.修饰器Decorator
ES8神器Decorator,修饰器,也称修饰器模式
7.1 伪Decorator
在介绍Decorator
之前,我们先来实现这样一个功能:
定义一个函数,在调用这个函数时,能够执行一些其他额外操作
如下代码,定义doSometing()
,在调用它时再执行其他代码
function doSometing(name) {
console.log('Hello ' + name)
}
function myDecorator(fn) {
return function() {
console.log('start')
const res = fn.apply(this, arguments)
console.log('end')
return res
}
}
const wrapped = myDecorator(doSometing)
doSometing('lindaidai')
//Hello lindaidai
wrapped('lindaidai')
//start
//Hello lindaidai
//end
可以看到上面的操作:其实就是一个函数包装成另一个函数,这样的方式我们称之为“修饰器”
同理,我们是不是能用一个什么东西附着在我们的类或者类的属性上,让它们也有一些附加的属性或者功能呢,比如这样:
@addSkill
class Person { }
function addSkill(target) {
target.say = "hello world";
}
在Person
这个类中,开始定义的时候是什么属性都没有的,在其上面使用@
来附着上一个函数,这个函数的功能是给目标对象添加额外的属性say
。
这样Person
这个类就有了say
这个属性了。
此时控制台输出:
console.log(Person['say']) //'hello world'
同样的,如果想使用Person
这个类创建出来的对象也能附加上一些属性,可以在目标对象的原型对象中进行添加:
@addSkill
class Person { }
function addSkill(target) {
target.say = "hello world"; //直接添加到类中
target.prototype.eat = "apple"; //添加到类的原型对象中
}
var personOne = new Person()
console.log(Person['say']) // 'hello world'
console.log(personOne['eat']) // 'apple'
上面案例中的
@addSkill
其实就是一个最简单的修饰器。
当然,如果你将上面案例中的代码复制到你html文件中,会发现它并不能如愿的执行:
那是因为decorator是es7提供的方法,在浏览器中是无法直接运行的,如果你想要使用它,我们需要提前做一些准备,对它进行编译。
如果你不想深入其中,只是想单纯的了解并使用它可以参考下面的简易教程。
7.2 快速使用
网上使用Decorator
的教材有很多,大多都是要需要使用插件来让浏览器支持Decorator
。这里长话短说,贴上一个最精简的使用教程:
1.创建一个名为:Decorator的文件夹
2.在文件夹目录下执行命令行
npm i babel-plugin-transform-decorators-legacy babel-register --save-dev
此时文件夹下会出现俩个文件: node_modules 依赖文件夹和package.json-lock.json
3.创建文件 complie.js
require('babel-register')({
plugins: ['transform-decorators-legacy']
});
require("./app.js")
4.创建文件 app.js
@addSkill
class Person { }
function addSkill(target) {
target.say = "hello world";
}
console.log(Person.say) //'hello world'
5.在根目录下执行指令:
node complie.js
此时可以看到命令行中打印出了 hello world
简单介绍下上面步骤的原理:
第二步中使用了俩个基础插件:
transform-decorators-legacy:
//是第三方插件,用于支持decorators
babel-register:
//用于接入node api
第三步、第四步创建的俩个文件
complie.js //用来编译app
app.js //使用了装饰器的js文件
第五步:
原理:
1,node执行complie.js文件;
2,complie文件改写了node的require方法;
3,complie在引用app.js,使用了新的require方法;
4,app.js在加载过程中被编译,并执行。
当然你也可以将app.js替换为app.ts 不过别忘了把complie.js中的app.js修改为app.ts
// app.ts
@addSkill
class Person { }
function addSkill(target) {
target.say = "hello world";
}
console.log(Person['say'])
//这里如果直接使用Person.say会提示say属性不存在,如我使用的vscode编辑器就会报错,是因为ts的原因,只需要用[]的形式获取对象属性即可。
注:ts中有些语法是和js中不一样的,比如有些对象上提示没有属性的时候,只需要换一种获取对象属性的方式即可。
7.3 类修饰器
直接作用在类上面的修饰器,我们可以称之为类修饰器。
如上面案例中的@addSkill
就是一个类修饰器,它修改了Person
这个类的行为,为它加上了静态属性say
。
addSkill
函数的参数target是Person
这个类本身。
1.修饰器的执行原理基本就是这样:
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
换句话说,类修饰器是一个对类进行处理的函数。
它的第一个参数target就是函数要处理的目标类。
2.多参数
当然如果你想要有多个参数也是可以的,我们可以在修饰器外面再封装一层函数:
@addSkill("hello world")
class Person { }
function addSkill(text) {
return function(target) {
target.say = text;
}
}
console.log(Person.say) //'hello world'
上面代码中,修饰器addSkill
可以接受参数,这就等于可以修改修饰器的行为。
3.修饰器在什么时候执行。
先来看一个案例:
@looks
class Person { }
function looks(target) {
console.log('I am handsome')
target.looks = 'handsome'
}
console.log(Person['looks'])
//I am handsome
//handsome
在修饰器@looks
中添加一个console.log()
语句,却发现它是最早执行的,其次才打印出handsome
。
这是因为装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。
装饰器是在编译时就执行的函数
7.4 方法修饰器
上面的案例中,修饰器作用的对象是类本身。
当然修饰器不仅仅这么简单,它也可以作用在类里的某个方法或者属性上,这样的修饰器我们称它为方法修饰器。
如下面的案例:
class Person {
constructor() {}
@myname //方法修饰器
name() {
console.log('霖呆呆')
}
}
function myname(target, key, descriptor) {
console.log(target);
console.log(key);
console.log(descriptor);
descriptor.value = function() {
console.log('霖呆呆')
}
}
var personOne = new Person() //实例化
personOne.name() //调用name()方法
//打印结果:
Person {}
name
{ value: [Function: name],
writable: true,
enumerable: false,
configurable: true
}
霖呆呆
上面案例中的修饰器@myname
是放在name()
方法上的,myname
函数有三个参数:
target: 类的原型对象,上例是Person.prototype
key: 所要修饰的属性名 name
descriptor: 该属性的描述对象
我们改变了descriptor
中的value
,使之打印出霖呆呆。
7.5 多个修饰器的执行顺序
若是同一个方法上有多个修饰器,会像剥洋葱一样,先从外到内进入,然后由内向外执行。
class Person {
constructor() {}
@dec(1)
@dec(2)
name() {
console.log('霖呆呆')
}
}
function dec(id) {
console.log('out', id);
return function(target, key, descriptor) {
console.log(id);
}
}
var person = new Person()
person.name()
//结果
out 1
out 2
2
1
霖呆呆
如上所属,外层修饰器dec(1)
先进入,但是内层修饰器dec(2)
先执行。
7.6 不能作用于函数
修饰器不能作用于函数之上,这是因为函数和变量一样都会提升
var counter = 0;
var add = function () {
counter++;
};
@add
function foo() {
}
如上面的例子所示,给函数foo()
定义了修饰器@add
,作用是想将counter++
预计的结果counter
为1,但实际上却还是为0
原因:
定义的函数foo()
会被提升至最上层,定义的变量counter
和add
也会被提升,效果如下:
@add
function foo() {
}
var counter;
var add;
counter = 0;
add = function () {
counter++;
};
总之,由于存在函数提升,使得修饰器不能用于函数。类是不会提升的,所以就没有这方面的问题。
另一方面,如果一定要修饰函数,可以采用高阶函数的形式直接执行。
如在7.1中的例子所示:
function doSometing(name) {
console.log('Hello' + name)
}
function myDecorator(fn) {
return function() {
console.log('start')
const res = fn.apply(this, arguments)
console.log('end')
return res
}
}
const wrapped = myDecorator(doSometing)
doSometing('lindaidai')
//Hellowlindaidai
wrapped('lindaidai')
//start
//Hellowlindaidai
//end
作者:LinDaiDai_霖呆呆
链接:https://www.jianshu.com/p/13c5d002478b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
ES7
1.Array.prototype.includes()
includes()作用,是查找一个值在不在数组里,若是存在则返回true,不存在返回false.
1.基本用法:
['a', 'b', 'c'].includes('a') // true
['a', 'b', 'c'].includes('d') // false
2.接收俩个参数:要搜索的值和搜索的开始索引
['a', 'b', 'c', 'd'].includes('b') // true
['a', 'b', 'c', 'd'].includes('b', 1) // true
['a', 'b', 'c', 'd'].includes('b', 2) // false
3.与ES6中的indexOf()
比较
有些时候是等效的
['a', 'b', 'c'].includes('a') //true
['a', 'b', 'c'].indexOf('a') > -1 //true
var arr = [1, 2, 3]
var a = 1;
arr.includes(a) //true
arr.indexOf(a) //0
- 在判断 +0 与 -0 时,被认为是相同的。
[1, +0, 3, 4].includes(-0) //true
[1, +0, 3, 4].indexOf(-0) //1
- 只能判断简单类型的数据,对于复杂类型的数据,比如对象类型的数组,二维数组,这些,是无法判断的.
var arr = [1, [2, 3], 4]
arr.includes([2, 3]) //false
arr.indexOf([2, 3]) //-1
优缺点比较
- 简便性
includes()
返回的是布尔值,能直接判断数组中存不存在这个值,而indexOf()
返回的是索引,这一点上前者更加方便。
精确性
两者都是采用
===
的操作符来作比较的,不同之处在于:对于NaN
的处理结果不同。我们知道js中
NaN === NaN
的结果是false,indexOf()
也是这样处理的,但是includes()
不是这样的。let demo = [1, NaN, 2, 3] demo.indexOf(NaN) //-1
demo.includes(NaN) //true
总结:
由于它对NaN的处理方式与indexOf不同,假如你只想知道某个值是否在数组中而并不关心它的索引位置,建议使用includes()。如果你想获取一个值在数组中的位置,那么你只能使用indexOf方法。
2.求幂运算符
基本用法:
3 ** 2 //9
效果同
Math.pow(3, 2) //9
由于是运算符,所以可以和 +=
一样的用法
var b = 3;
b **= 2;
console.log(b); //9
ES8
1.async await
异步函数async function()
1.1作用
避免有更多的请求操作,出现多重嵌套,也就是俗称的“回调地狱”
this.$http.jsonp('/login', (res) => {
this.$http.jsonp('/getInfo', (info) => {
// do something
})
})
因此提出了ES6的Promise,将回调函数的嵌套,改为了链式调用:
var promise = new Promise((resolve, reject) => {
this.login(resolve);
})
.then(() => {
this.getInfo()
})
.catch(() => {
console.log('Error')
})
1.2声明方式
异步函数存在以下四种使用形式:
- 函数声明:
async function foo() {}
- 函数表达式:
const foo = async function() {}
- 对象的方式:
let obj = { async foo() {} }
- 箭头函数:
const foo = async () => {}
1.3支持返回Promise和同步的值
async用于定义一个异步函数,该函数返回一个Promise。
如果async函数返回的是一个同步的值,这个值将被包装成一个理解resolve的Promise,等同于return Promise.resolve(value)
。
await用于一个异步操作之前,表示要“等待”这个异步操作的返回值。await也可以用于一个同步的值。
//async await
//返回Promise
let timer = async function timer() {
return new Promise((reslove, reject) => {
setTimeout(() => {
reslove('a');
}, 1000);
})
}
timer().then(result => {
console.log(result);
}).catch(err => {
console.log(err.message);
})
//返回同步的值
let sayHello = async function sayHello() {
let hi = 'hello world'//等同于return Promise.resolve(hi);
return hi
}
sayHello().then(res => {
console.log(res)
}).catch(err => {
console.log(err.message);
})
1.4对异常的处理
首先来看下
Promise
中对异常的处理
1.使用reject
let promise = new Promise((reslove, reject) => {
setTimeout(() => {
reject('promise使用reject抛出异常')
}, 1000)
})
promise().then(res => {
console.log(res)
})
.catch(err => {
console.log(err) //'promise使用reject抛出异常'
})
2.使用new Error()
let promise = new Promise((reslove, reject) => {
throw new Error('promise使用Error抛出异常') //使用throw异常不支持放在定时器中
})
promise().then(res => {
console.log(res)
})
.catch(err => {
console.log(err.message) //'promise使用Error抛出异常'
})
3.reject
一个new Error()
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('promise抛出异常'));
}, 1000);
})
promise.then(res => {
console.log(res);
})
.catch(err => {
console.log(err.message); //'promise抛出异常'
})
async
对异常的处理也可以直接用.catch()
捕捉到
//async抛出异常
let sayHi = async sayHi => {
throw new Error('async抛出异常');
}
sayHi().then(res => {
console.log(res);
})
.catch(err => {
console.log(err.message);
})
和Promise链的对比:
我们的async函数中可以包含多个异步操作,其异常和Promise链有相同之处,如果有一个Promise被reject()那么后面的将不会再进行。
let count = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('promise故意抛出异常')
}, 1000);
})
}
let list = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([1, 2, 3])
}, 1000);
})
}
let getList = async () => {
let c = await count()
console.log('async') //此段代码并没有执行
let l = await list()
return { count: c, list: l }
}
console.time('start');
getList().then(res => {
console.log(res)
})
.catch(err => {
console.timeEnd('start')
console.log(err)
})
//start: 1000.81494140625ms
//promise故意抛出异常
可以看到上面的案例,async
捕获到了一个错误之后就会立马进入.catch()
中,不执行之后的代码
1.5并行
上面的案例中,async采用的是串行处理
count()和list()是有先后顺序的
let c = await count()
let l = await list()
实际用法中,若是请求的两个异步操作没有关联和先后顺序性可以采用下面的做法
let res = await Promise.all([count(), list()])
return res
//res的结果为
//[ 100, [ 1, 2, 3 ] ]
案例详情为:
let count = ()=>{
return new Promise((resolve,reject) => {
setTimeout(()=>{
resolve(100);
},500);
});
}
let list = ()=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve([1,2,3]);
},500);
});
}
let getList = async ()=>{
let result = await Promise.all([count(),list()]);
return result;
}
console.time('begin');
getList().then(result => {
console.timeEnd('begin'); //begin: 505.557ms
console.log(result); //[ 100, [ 1, 2, 3 ] ]
}).catch(err => {
console.timeEnd('begin');
console.log(err);
});
我们将count()和list()使用Promise.all()“同时”执行,这里count()和list()可以看作是“并行”执行的,所耗时间将是两个异步操作中耗时最长的耗时。
最后得到的结果是两个操作的结果组成的数组。我们只需要按照顺序取出数组中的值即可。
1.6与Generator的关系
先来回顾一下ES6中Generator
函数的用法:
function* getList() {
const c = yield count()
const l = yield list()
return 'end'
}
var gl = getList()
console.log(gl.next()) // {value: Promise, done: false}
console.log(gl.next()) // {value: Promise, done: false}
console.log(gl.next()) // {value: 'end', done: true}
虽然Generator将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。此时,我们便希望能出现一种能自动执行Generator函数的方法。我们的主角来了:async/await。
ES8引入了async函数,使得异步操作变得更加方便。简单说来,它就是Generator函数的语法糖。
let getList = async () => {
const c = await count()
const l = await list()
}
2.Object.entries()
2.1作用
作用:将一个对象中可枚举属性的键名和键值按照二维数组的方式返回。
若对象是数组,则会将数组的下标作为键值返回。
Object.entries({ one: 1, two: 2 }) //[['one', 1], ['two', 2]]
Object.entries([1, 2]) //[['0', 1], ['1', 2]]
2.2要点
1.若是键名是Symbol
,编译时会被自动忽略
Object.entries({[Symbol()]:1, two: 2}) //[['two', 2]]
2.entries()
返回的数组顺序和for
循环一样,即如果对象的key值是数字,则返回值会对key值进行排序,返回的是排序后的结果
Object.entries({ 3: 'a', 4: 'b', 1: 'c' }) //[['1', 'c'], ['3', 'a'], ['4', 'b']]
3.利用Object.entries()
创建一个真正的Map
var obj = { foo: 'bar', baz: 42 };
var map1 = new Map([['foo', 'bar'], ['baz', 42]]); //原本的创建方式
var map2 = new Map(Object.entries(obj)); //等同于map1
console.log(map1);// Map { foo: "bar", baz: 42 }
console.log(map2);// Map { foo: "bar", baz: 42 }
2.3自定义Object.entries()
Object.entries
的原理其实就是将对象中的键名和值分别取出来然后推进同一个数组中
//自定义entries()
var obj = { foo: 'bar', baz: 42 };
function myEntries(obj) {
var arr = []
for (var key of Object.keys(obj)) {
arr.push([key, obj[key]])
}
return arr
}
console.log(myEntries(obj))
//Generator版本
function* genEntryies(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]]
}
}
var entryArr = genEntryies(obj);
console.log(entryArr.next().value) //["foo", "bar"]
console.log(entryArr.next().value) //["baz", 42]
3.Object.values()
3.1作用
作用:只返回自己的键值对中属性的值。它返回的数组顺序,也跟
Object.entries()
保持一致
Object.values({ one: 1, two: 2 }) //[1, 2]
Object.values({ 3: 'a', 4: 'b', 1: 'c' }) //['c', 'a', 'b']
3.2与Object.keys()比较
ES6中的
Object.keys()
返回的是键名
var obj = { foo: 'bar', baz: 42 };
console.log(Object.keys(obj)) //["foo", "baz"]
console.log(Object.values(obj)) //["bar", 42]
//Object.keys()的作用就类似于for...in
function myKeys() {
let keyArr = []
for (let key in obj1) {
keyArr.push(key)
console.log(key)
}
return keyArr
}
console.log(myKeys(obj1)) //["foo", "baz"]
3.3entries()、values()总结
var obj = { foo: 'bar', baz: 42 };
console.log(Object.keys(obj)) //["foo", "baz"]
console.log(Object.values(obj)) //["bar", 42]
console.log(Object.entries(obj)) //[["foo", "bar"], ["baz", 42]]
4.字符串填充
4.1padStart()和padEnd()
字符串填充
padStart()
和padEnd()
用法
String.padStart(targetLength, padding)
参数:字符串目标长度和填充字段
'Vue'.padStart(10) //' Vue'
'React'.padStart(10) //' React'
'JavaScript'.padStart(10) //'JavaScript'
4.2要点
1.填充函数只有在字符长度小于目标长度时才有效,而且目标长度如果小于字符串本身长度时,字符串也不会做截断处理,只会原样输出
'Vue'.padEnd(10, '_*') //'Vue_*_*_*_'
'React'.padEnd(10, 'Hello') //'ReactHello'
'JavaScript'.padEnd(10, 'Hi') //'JavaScript'
'JavaScript'.padEnd(8, 'Hi') //'JavaScript'
5.Object.getOwnPropertyDescriptors()
5.1作用
该方法会返回目标对象中所有属性的属性描述符,该属性必须是对象自己定义的,不能是从原型链继承来的。
var obj = {
id: 1,
name: 'tsx',
get gender() {
console.log('gender')
},
set grad(d) {
console.log(d)
}
}
console.log(Object.getOwnPropertyDescriptors(obj))
//输出
{
gender: {
configurable: true,
enumerable: true,
get: f gender(),
set: undefined
},
grade: {
configurable: true,
enumerable: true,
get: undefined,
set: f grade(g)
},
id: {
configurable: true,
enumerable: true,
value: 1,
writable: true
},
name: {
configurable: true,
enumerable: true,
value: 'tsx',
writable: true
}
}
5.2与getOwnPropertyDescriptor()
比较
ES6中也有一个返回目标对象可枚举属性的方法
var obj = {
id: 1,
name: 'tsx',
get gender() {
console.log('gender')
},
set grad(d) {
console.log(d)
}
}
console.log(Object.getOwnPropertyDescriptor(obj, 'id'))
//输出结果
{
id: {
configurable: true,
enumerable: true,
value: 1,
writable: true
}
}
两者的区别:一个是只返回知道属性名的描述对象,一个返回目标对象所有自身属性的描述对象
5.3自定义该方法
function myDescriptors(obj) {
let descriptors = {}
for (let key in obj) {
descriptors[key] = Object.getOwnPropertyDescriptor(obj, key)
}
return descriptors
}
console.log(myDescriptors(obj))
//返回的结果和该方法一样
//其中上面自定义方法的for...in也可以换成,效果也是一样的
for (let key of Object.keys(obj)) {
descriptors[key] = Object.getOwnPropertyDescriptor(obj, key)
}
6.函数参数支持尾部逗号
该特性允许我们在定义或者调用函数时添加尾部逗号而不报错
let foo = function (
a,
b,
c,
) {
console.log('a:', a)
console.log('b:', b)
console.log('c:', c)
}
foo(1, 3, 4, )
//输出结果为:
a: 1
b: 3
c: 4
它适用于那种多行参数并且参数名很长的情况,开发过程中,如果忘记删除尾部逗号也没关系,ES8已经支持这种写法。
7.修饰器Decorator
ES8神器Decorator,修饰器,也称修饰器模式
7.1 伪Decorator
在介绍Decorator
之前,我们先来实现这样一个功能:
定义一个函数,在调用这个函数时,能够执行一些其他额外操作
如下代码,定义doSometing()
,在调用它时再执行其他代码
function doSometing(name) {
console.log('Hello ' + name)
}
function myDecorator(fn) {
return function() {
console.log('start')
const res = fn.apply(this, arguments)
console.log('end')
return res
}
}
const wrapped = myDecorator(doSometing)
doSometing('lindaidai')
//Hello lindaidai
wrapped('lindaidai')
//start
//Hello lindaidai
//end
可以看到上面的操作:其实就是一个函数包装成另一个函数,这样的方式我们称之为“修饰器”
同理,我们是不是能用一个什么东西附着在我们的类或者类的属性上,让它们也有一些附加的属性或者功能呢,比如这样:
@addSkill
class Person { }
function addSkill(target) {
target.say = "hello world";
}
在Person
这个类中,开始定义的时候是什么属性都没有的,在其上面使用@
来附着上一个函数,这个函数的功能是给目标对象添加额外的属性say
。
这样Person
这个类就有了say
这个属性了。
此时控制台输出:
console.log(Person['say']) //'hello world'
同样的,如果想使用Person
这个类创建出来的对象也能附加上一些属性,可以在目标对象的原型对象中进行添加:
@addSkill
class Person { }
function addSkill(target) {
target.say = "hello world"; //直接添加到类中
target.prototype.eat = "apple"; //添加到类的原型对象中
}
var personOne = new Person()
console.log(Person['say']) // 'hello world'
console.log(personOne['eat']) // 'apple'
上面案例中的
@addSkill
其实就是一个最简单的修饰器。
当然,如果你将上面案例中的代码复制到你html文件中,会发现它并不能如愿的执行:
那是因为decorator是es7提供的方法,在浏览器中是无法直接运行的,如果你想要使用它,我们需要提前做一些准备,对它进行编译。
如果你不想深入其中,只是想单纯的了解并使用它可以参考下面的简易教程。
7.2 快速使用
网上使用Decorator
的教材有很多,大多都是要需要使用插件来让浏览器支持Decorator
。这里长话短说,贴上一个最精简的使用教程:
1.创建一个名为:Decorator的文件夹
2.在文件夹目录下执行命令行
npm i babel-plugin-transform-decorators-legacy babel-register --save-dev
此时文件夹下会出现俩个文件: node_modules 依赖文件夹和package.json-lock.json
3.创建文件 complie.js
require('babel-register')({
plugins: ['transform-decorators-legacy']
});
require("./app.js")
4.创建文件 app.js
@addSkill
class Person { }
function addSkill(target) {
target.say = "hello world";
}
console.log(Person.say) //'hello world'
5.在根目录下执行指令:
node complie.js
此时可以看到命令行中打印出了 hello world
简单介绍下上面步骤的原理:
第二步中使用了俩个基础插件:
transform-decorators-legacy:
//是第三方插件,用于支持decorators
babel-register:
//用于接入node api
第三步、第四步创建的俩个文件
complie.js //用来编译app
app.js //使用了装饰器的js文件
第五步:
原理:
1,node执行complie.js文件;
2,complie文件改写了node的require方法;
3,complie在引用app.js,使用了新的require方法;
4,app.js在加载过程中被编译,并执行。
当然你也可以将app.js替换为app.ts 不过别忘了把complie.js中的app.js修改为app.ts
// app.ts
@addSkill
class Person { }
function addSkill(target) {
target.say = "hello world";
}
console.log(Person['say'])
//这里如果直接使用Person.say会提示say属性不存在,如我使用的vscode编辑器就会报错,是因为ts的原因,只需要用[]的形式获取对象属性即可。
注:ts中有些语法是和js中不一样的,比如有些对象上提示没有属性的时候,只需要换一种获取对象属性的方式即可。
7.3 类修饰器
直接作用在类上面的修饰器,我们可以称之为类修饰器。
如上面案例中的@addSkill
就是一个类修饰器,它修改了Person
这个类的行为,为它加上了静态属性say
。
addSkill
函数的参数target是Person
这个类本身。
1.修饰器的执行原理基本就是这样:
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
换句话说,类修饰器是一个对类进行处理的函数。
它的第一个参数target就是函数要处理的目标类。
2.多参数
当然如果你想要有多个参数也是可以的,我们可以在修饰器外面再封装一层函数:
@addSkill("hello world")
class Person { }
function addSkill(text) {
return function(target) {
target.say = text;
}
}
console.log(Person.say) //'hello world'
上面代码中,修饰器addSkill
可以接受参数,这就等于可以修改修饰器的行为。
3.修饰器在什么时候执行。
先来看一个案例:
@looks
class Person { }
function looks(target) {
console.log('I am handsome')
target.looks = 'handsome'
}
console.log(Person['looks'])
//I am handsome
//handsome
在修饰器@looks
中添加一个console.log()
语句,却发现它是最早执行的,其次才打印出handsome
。
这是因为装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。
装饰器是在编译时就执行的函数
7.4 方法修饰器
上面的案例中,修饰器作用的对象是类本身。
当然修饰器不仅仅这么简单,它也可以作用在类里的某个方法或者属性上,这样的修饰器我们称它为方法修饰器。
如下面的案例:
class Person {
constructor() {}
@myname //方法修饰器
name() {
console.log('tsx')
}
}
function myname(target, key, descriptor) {
console.log(target);
console.log(key);
console.log(descriptor);
descriptor.value = function() {
console.log('tsx')
}
}
var personOne = new Person() //实例化
personOne.name() //调用name()方法
//打印结果:
Person {}
name
{ value: [Function: name],
writable: true,
enumerable: false,
configurable: true
}
上面案例中的修饰器@myname
是放在name()
方法上的,myname
函数有三个参数:
target: 类的原型对象,上例是Person.prototype
key: 所要修饰的属性名 name
descriptor: 该属性的描述对象
我们改变了descriptor
中的value
,使之打印出tsx。
7.5 多个修饰器的执行顺序
若是同一个方法上有多个修饰器,会像剥洋葱一样,先从外到内进入,然后由内向外执行。
class Person {
constructor() {}
@dec(1)
@dec(2)
name() {
console.log('tsx')
}
}
function dec(id) {
console.log('out', id);
return function(target, key, descriptor) {
console.log(id);
}
}
var person = new Person()
person.name()
//结果
out 1
out 2
2
1
tsx
如上所属,外层修饰器dec(1)
先进入,但是内层修饰器dec(2)
先执行。
7.6 不能作用于函数
修饰器不能作用于函数之上,这是因为函数和变量一样都会提升
var counter = 0;
var add = function () {
counter++;
};
@add
function foo() {
}
如上面的例子所示,给函数foo()
定义了修饰器@add
,作用是想将counter++
预计的结果counter
为1,但实际上却还是为0
原因:
定义的函数foo()
会被提升至最上层,定义的变量counter
和add
也会被提升,效果如下:
@add
function foo() {
}
var counter;
var add;
counter = 0;
add = function () {
counter++;
};
总之,由于存在函数提升,使得修饰器不能用于函数。类是不会提升的,所以就没有这方面的问题。
另一方面,如果一定要修饰函数,可以采用高阶函数的形式直接执行。
如在7.1中的例子所示:
function doSometing(name) {
console.log('Hello' + name)
}
function myDecorator(fn) {
return function() {
console.log('start')
const res = fn.apply(this, arguments)
console.log('end')
return res
}
}
const wrapped = myDecorator(doSometing)
doSometing('lindaidai')
//Hellowlindaidai
wrapped('lindaidai')
//start
//Hellowlindaidai
//end
ES6、ES7、ES8的更多相关文章
- ES6、ES7、ES8、ES9、ES10
ES6新特性(2015) ES6的特性比较多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化.两个发布版本之间时间跨度很大,所以ES6中的特性比较多.在这里列举几个常用 ...
- ES6、ES7、ES8、ES9、ES10新特性
ES6新特性(2015) ES6的特性比较多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化.两个发布版本之间时间跨度很大,所以ES6中的特性比较多. 在这里列举几个常 ...
- ES6,ES7,ES8 常用特性总结
一. ES6(ES2015) 1. 变量 let 和常量 const var 的问题 可以重复声明,没有报错和警告 无法限制修改 没有块级作用域, { } let 和 const 不能重复声明 都是块 ...
- ES6、ES7、ES8语法总结
ES6 1. var let const let,const具有块级作用域,不具有变量提升 const 用于不能被重新赋值的变量 2. 箭头函数 我们经常要给回调函数给一个父级的this 常用办法就是 ...
- ES6,ES7,ES8 常用
ES6常用新特性 let && const let 命令也用于变量声明,但是作用域为局部 { let a = 10; var b = 1; } 在函数外部可以获取到b,获取不到a,因此 ...
- webgl图库研究(包括BabylonJS、Threejs、LayaboxJS、SceneJS、ThingJS等框架的特性、适用范围、支持格式、优缺点、相关网址)
3D图库框架范围与示例 摘要: 为实现企业80%以上的生产数据进行智能转化,在烟草.造纸.能源.电力.机床.化肥等行业,赢得领袖企业青睐,助力企业构建AI赋能中心,实现智能化转型升级.“远舢文龙数据处 ...
- 上篇:es5、es6、es7中的异步写法
本作品采用知识共享署名 4.0 国际许可协议进行许可.转载联系作者并保留声明头部与原文链接https://luzeshu.com/blog/es-async 本博客同步在http://www.cnbl ...
- 石川es6课程---17、ES7 预览
石川es6课程---17.ES7 预览 一.总结 一句话总结: 人的价值恒定规律:无论得意还是迷茫之时,你的价值都不靠外界的评判或者你内心的悲喜而决定.而是当时的恒定的.能够提升他只能靠你提升自己的能 ...
- 使用express、react、webpack打包、socket.io、mongodb、ant.design、less、es6实现聊天室
拿到一个项目,我们应该如何去完成这个项目呢. 是直接上手? 还是先进行分析,然后再去解决呢?毫无疑问,如果直接上手解决,那么可能会因为知道目标所在,而导致出现各种问题. 所以,我们应该系统的分析这个项 ...
随机推荐
- CVE-2017-11882利用
CVE-2017-11882是微软公布的远程执行漏洞,通杀所有office版本及Windows操作系统 工具使用 本文使用的EXP来源于unamer/CVE-2017-11882,然后结合MSF进行渗 ...
- sqlilab less19-less22
less19 当账号密码正确时,会将当前的refer和ip存入数据库.对这两个值同时没有进行过滤.考虑使用sqlmap对这两个参数进行注入 less-20 当cookie uname存在时,并且不是p ...
- redhat-NFS服务的配置与应用
---恢复内容开始--- 1.NFS服务简述 NFS实现Windows系统中资源共享的功能.NFS采用客户/服务器工作模式,客户端将服务器中的共享目录挂载到自己的某个目录下,这个目录可以与其他客户机的 ...
- 超强工具集——GitHub 热点速览 Vol.47
作者:HelloGitHub-小鱼干 本周 GitHub 趋势榜精彩至极,先是 JetBrains 开源的图像渲染引擎 Skia 有了 Java 封装,一开源便获得 500+ star,再是支持开发者 ...
- Guitar Pro编辑歌词教程
吉他是常见的乐器之一,弹唱亦是音乐爱好者们最常见的表演方式.Guitar Pro(win系统)是吉他爱好者最常用的吉他谱制作软件,大家可以在该软件上创作自己的吉他谱.既然有了谱子,那自然是少不了歌词了 ...
- 对KVC和KVO的理解
html { overflow-x: initial !important } :root { --bg-color: #ffffff; --text-color: #333333; --select ...
- python 工业日志模块 未来的python日志最佳实践
目录 介绍 好的功能 安装方法 参数介绍 呆log 参数与 使用方法 版本说明 后期版本规划 todo 感谢 介绍 呆log:工业中,python日志模块,安装即用.理论上支持 python2, py ...
- 解决-Chrome插件安装时程序包无效:"CRX_HEADER_INVALID"
最近安装新的谷歌插件出现 :程序包无效:"CRX_HEADER_INVALID" 原因如下: 在地址栏输入chrome://settings/help 如果,你的 Chrome 浏 ...
- Java基础教程——二维数组
二维数组 Java里的二维数组其实是数组的数组,即每个数组元素都是一个数组. 每个数组的长度不要求一致,但最好一致. // 同样有两种风格的定义方法 int[][] _arr21_推荐 = { { 1 ...
- Hibernate框架session的方法
package Test; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernat ...