前端面试八
10)Reflect:ES6 为了操作对象而提供的新 APIReflect对象设计的目的:(1)将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法...
10)Reflect: ES6 为了操作对象而提供的新 API
Reflect对象设计的目的:
(1)将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法
(2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false
(3)让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为
(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为
Reflect对象一共有13个静态方法---与Proxy对象的方法一一对应
(3)Reflect.get(target, name, receiver)---查找并返回target对象的name属性,如果没有,返回undefined
//例1
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
}
Reflect.get(myObject, 'foo') // 1
Reflect.get(myObject, 'bar') // 2
Reflect.get(myObject, 'baz') // 3
//例2
//如果name属性部署了读取函数(getter),则读取函数的this绑定receiver
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
};
var myReceiverObject = {
foo: 4,
bar: 4,
};
Reflect.get(myObject, 'baz', myReceiverObject) // 8
//例3如果第一个参数不是对象,
Reflect.get
方法会报错
Reflect.get(1, 'foo') // 报错
Reflect.get(false, 'foo') // 报错
(4)Reflect.set(target, name, value, receiver)----设置target对象的name属性等于value
//例1
var myObject = {
foo: 1,
set bar(value) {
return this.foo = value;
},
}
myObject.foo // 1
Reflect.set(myObject, 'foo', 2);
myObject.foo // 2
Reflect.set(myObject, 'bar', 3)
myObject.foo // 3
//例2
//如果name属性设置了赋值函数,则赋值函数的this绑定receiver
var myObject = {
foo: 4,
set bar(value) {
return this.foo = value;
},
};
var myReceiverObject = {
foo: 0,
};
Reflect.set(myObject, 'bar', 1, myReceiverObject);
myObject.foo // 4
myReceiverObject.foo // 1
//例3
//如果第一个参数不是对象,Reflect.set会报错
Reflect.set(1, 'foo', {}) // 报错
Reflect.set(false, 'foo', {}) // 报错
(7)Reflect.has(target, name)----对应name in obj里面的in运算符,返回boolean类型值
//如果第一个参数不是对象,Reflect.has和in运算符都会报错。
var myObject = {
foo: 1,
};
// 旧写法
'foo' in myObject // true
// 新写法
Reflect.has(myObject, 'foo') // true
(5)Reflect.defineProperty(target, name, desc)----等同于Object.defineProperty,用来为对象定义属性
//例1
function MyDate() {
/*…*/
}
// 旧写法
Object.defineProperty(MyDate, 'now', {
value: () => Date.now()
});
// 新写法
Reflect.defineProperty(MyDate, 'now', {
value: () => Date.now()
});
//例2
//第一个参数不是对象,就会抛出错误
Reflect.defineProperty(1, 'foo') //抛出错误
(6)Reflect.deleteProperty(target, name)---等同于delete obj[name],用于删除对象的属性,返回boolean
//删除成功或者删除对象不存在,返回true
const myObj = { foo: 'bar' };
// 旧写法
delete myObj.foo;
// 新写法
Reflect.deleteProperty(myObj, 'foo');
(11)Reflect.getOwnPropertyDescriptor(target, name)---等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象
//第一个参数不是对象,Object.getOwnPropertyDescriptor(1, 'foo')不报错,返回undefined,
//而Reflect.getOwnPropertyDescriptor(1, 'foo')会抛出错误,表示参数非法
var myObject = {};
Object.defineProperty(myObject, 'hidden', {
value: true,
enumerable: false,
});
// 旧写法
var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');
// 新写法
var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');
(12)Reflect.getPrototypeOf(target)---读取对象的__proto__属性,对应Object.getPrototypeOf(obj)
//例1
const myObj = new FancyThing();
// 旧写法
Object.getPrototypeOf(myObj) === FancyThing.prototype;
// 新写法
Reflect.getPrototypeOf(myObj) === FancyThing.prototype;
//例2
//如果参数不是对象,Object.getPrototypeOf会将这个参数转为对象,然后再运行,而Reflect.getPrototypeOf会报错
Object.getPrototypeOf(1) // Number {[[PrimitiveValue]]: 0}
Reflect.getPrototypeOf(1) // 报错
(13)Reflect.setPrototypeOf(target, prototype)----设置目标对象的原型,返回boolean表示设置是否成功
//例1
const myObj = {};
// 旧写法
Object.setPrototypeOf(myObj, Array.prototype);
// 新写法
Reflect.setPrototypeOf(myObj, Array.prototype);
myObj.length // 0
//例2
//无法设置目标对象的原型
Reflect.setPrototypeOf({}, null)
// true
Reflect.setPrototypeOf(Object.freeze({}), null)
// false
//例3
//如果第一个参数不是对象,Object.setPrototypeOf会返回第一个参数本身,而Reflect.setPrototypeOf会报错
Object.setPrototypeOf(1, {})
// 1
Reflect.setPrototypeOf(1, {})
// TypeError: Reflect.setPrototypeOf called on non-object
//例4
//第一个参数是undefined或null,Object.setPrototypeOf和Reflect.setPrototypeOf都会报错
Object.setPrototypeOf(null, {})
// TypeError: Object.setPrototypeOf called on null or undefined
Reflect.setPrototypeOf(null, {})
// TypeError: Reflect.setPrototypeOf called on non-object
(8)Reflect.ownKeys(target)----返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和
var myObject = {
foo: 1,
bar: 2,
[Symbol.for('baz')]: 3,
[Symbol.for('bing')]: 4,
};
// 旧写法
Object.getOwnPropertyNames(myObject)
// ['foo', 'bar']
Object.getOwnPropertySymbols(myObject)
//[Symbol(baz), Symbol(bing)]
// 新写法
Reflect.ownKeys(myObject)
// ['foo', 'bar', Symbol(baz), Symbol(bing)]
(9)Reflect.isExtensible(target)---对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展
//例1
const myObject = {};
// 旧写法
Object.isExtensible(myObject) // true
// 新写法
Reflect.isExtensible(myObject) // true
//例2
Object.isExtensible(1) // false
Reflect.isExtensible(1) // 报错
(10)Reflect.preventExtensions(target)----对应Object.preventExtensions方法,用于让一个对象变为不可扩展,返回布尔值,表示是否操作成功
//例1
var myObject = {};
// 旧写法
Object.preventExtensions(myObject) // Object {}
// 新写法
Reflect.preventExtensions(myObject) // true
//例2,参数不是对象,object.preventExtensions
在 ES5 环境报错,在 ES6 环境返回传入的参数,而Reflect.preventExtensions
会报错
// ES5 环境
Object.preventExtensions(1) // 报错
// ES6 环境
Object.preventExtensions(1) // 1
// 新写法
Reflect.preventExtensions(1) // 报错
(1)Reflect.apply(target, thisArg, args)----同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数
如果要绑定一个函数的this对象,可以这样写fn.apply(obj, args),但是如果函数定义了自己的apply方法,就只能写成Function.prototype.apply.call(fn, obj, args),采用Reflect对象可以简化这种操作
const ages = [11, 33, 12, 54, 18, 96];
// 旧写法
const youngest = Math.min.apply(Math, ages);
const oldest = Math.max.apply(Math, ages);
const type = Object.prototype.toString.call(youngest);
// 新写法
const youngest = Reflect.apply(Math.min, Math, ages);
const oldest = Reflect.apply(Math.max, Math, ages);
const type = Reflect.apply(Object.prototype.toString, youngest, []);
(2)Reflect.construct(target, args)----等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法
function Greeting(name) {
this.name = name;
}
// new 的写法
const instance = new Greeting('张三');
// Reflect.construct 的写法
const instance = Reflect.construct(Greeting, ['张三']);
11)Iterator( for-of循环 ): ES6
具有Iterator接口的数据结构: Array String set map NodeList 还可以组合它们
Iterator的定义(遍历器--迭代器)
1)它是一种接口,为各种不同的数据结构提供统一的访问机制
2)任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator的作用
1)为各种数据结构,提供一个统一的、简便的访问接口
2)数据结构的成员能够按某种次序排列
3)ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费
原生具备Iterator接口的数据结构
Array String Set Map arguments NodeList
Iterator 的遍历过程
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,true表示遍历结束
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
12)Generator( 生成器 ): ES6 提供的一种异步编程解决方案
(1)语法上,Generator 函数是一个状态机,封装了多个内部状态
(2)执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态
Generator 函数在形式上有2个特征
(1)function关键字与函数名之间有一个星号
(2)函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
调用 Generator 函数后,返回的是一个指向内部状态的指针对象,也就是遍历器
接着调用遍历器对象的next方法,使指针移向下一个状态。每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。yield表达式是暂停执行的标记,而next方法可以恢复执行
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
yield表达式只能用在Generator函数里面
//yield表达式如果用在另一个表达式之中,必须放在圆括号里面
function* demo() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
//yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}
ES6 没有规定,function关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· } //常用形式
function*foo(x, y) { ··· }
Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数
function* f() {
console.log('执行了!')
}
var generator = f(); //如果是普通函数,这里会马上执行
setTimeout(function () {
generator.next() //只有调用next()才会执行
}, 2000);
next()方法的参数
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false },第一次调用next()不需要传参
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
// 将generator赋给变量a
// 第一个next()用来启动遍历器对象,所以不用带有参数
// 从第二次使用next方法开始,参数才是有效的,
// next方法的参数表示上一个yield表达式的返回值
var a = foo(5);
console.log(a.next()) // Object{value:6, done:false}
console.log(a.next()) // Object{value:NaN, done:false}
console.log(a.next()) // Object{value:NaN, done:true}
var b = foo(5);
console.log(b.next()) // { value:6, done:false }
console.log(b.next(12)) // { value:8, done:false },12作为yield (x + 1)中的额(x+1)的值
console.log(b.next(13)) // { value:42, done:true },13作为yield (y / 3)中(y / 3)的值
for...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法
//一旦next方法的返回对象的done属性为true,for...of循环就会中止,
//且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
Generator.prototype原型上的两个方法
1)Generator.prototype.throw()---Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内通过try....catch捕获,throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
如果 Generator 函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获
ar g = function* () {
while (true) {
yield;
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 外部捕获 a
如果 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行
var gen = function* gen(){
yield console.log('hello');
yield console.log('world');
}
var g = gen();
g.next();
g.throw();
// hello
// Uncaught undefined
throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法
var gen = function* gen(){
try {
yield console.log('a');
} catch (e) {
// ...
}
yield console.log('b');
yield console.log('c');
}
var g = gen();
g.next() // a
g.throw() // b
g.next() // c
throw命令与g.throw方法是无关的,两者互不影响
var gen = function* gen(){
yield console.log('hello');
yield console.log('world');
}
var g = gen();
g.next();
try {
throw new Error();
} catch (e) {
g.next();
}
// hello
// world
Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 函数体内抛出的错误,也可以被函数体外的catch捕获
unction* foo() {
var x = yield 3;
var y = x.toUpperCase();
yield y;
}
var it = foo();
it.next(); // { value:3, done:false }
try {
it.next(42);
} catch (err) {
console.log(err);
}
一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用next方法,将返回一个value属性等于undefined、done属性
function* g() {
yield 1;
console.log('throwing an exception');
throw new Error('generator broke!');
yield 2;
yield 3;
}
function log(generator) {
var v;
console.log('starting generator');
try {
v = generator.next();
console.log('第一次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
try {
v = generator.next();
console.log('第二次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
try {
v = generator.next();
console.log('第三次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
console.log('caller done');
}
log(g());
// starting generator
// 第一次运行next方法 { value: 1, done: false }
// throwing an exception
// 捕捉错误 { value: 1, done: false }
// 第三次运行next方法 { value: undefined, done: true }
// caller done
2)Generator.prototype.return()-----可以返回给定的值,并且终结遍历 Generator 函数
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true },如果return方法调用时,不提供参数,则返回值的value属性为undefined
g.next() // { value: undefined, done: true }
如果 Generator 函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
next() throw() return()
共同点:都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式
//1、next()是将yield表达式替换成一个值
const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
//2、throw()是将yield表达式替换成一个throw语句
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
//3、return()是将yield表达式替换成一个return语句
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
yield*-----在 Generator 函数内部,调用另一个 Generator 函数
//1、默认在一个Generator里面调用另外一个Generator不生效
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "y"
//2、使用yield*
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
//3、function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
// 例1、使用generator处理异步调用
// 用setTimeout来设置异步执行回调函数
// let getDataOne = (cb)=>{
// setTimeout( ()=>{ cb('dataOne') }, 1000 );
// };
// let getDataTwo = (cb)=>{
// setTimeout( ()=>{ cb('dataTwo') }, 1000 );
// };
// // 调用
// getDataOne( (data)=>{console.log(data)} );
// getDataTwo( (data)=>{console.log(data)} );
// 例2、下面用generator实现
// 修改getDataOne、getDataTwo的函数
let generator;
let getDataOne = ()=>{
setTimeout( ()=>{ generator.next('dataOne') }, 1000 );//调用generator.next(val)进行传参,并调用函数
};
let getDataTwo = ()=>{
setTimeout( ()=>{ generator.next('dataTwo') }, 1000 );
}
// 定义generator函数
function* main(){
let dataone = yield getDataOne();//使用yield来暂停执行程序
let datatwo = yield getDataTwo();
console.log(dataone);
console.log(datatwo);
}
// 定义generator实例,并触发next()函数
generator = main();
generator.next(); //输出dataOne dataTwo
Generator的应用
1)异步操作的同步化表达
用Generator实现ajax
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response);
});
}
var it = main();
it.next();
2)控制流管理,如果一个函数的返回值是下一个函数的输入值如此循环
//同步操作
function* longRunningTask(value1) {
try {
var value2 = yield step1(value1);
var value3 = yield step2(value2);
var value4 = yield step3(value3);
var value5 = yield step4(value4);
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
}
}
//使用一个函数,按次序自动执行所有步骤
scheduler(longRunningTask(initialValue));
function scheduler(task) {
var taskObj = task.next(task.value);
// 如果Generator函数未结束,就继续调用
if (!taskObj.done) {
task.value = taskObj.value
scheduler(task);
}
}
3)部署Iteretor接口
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
// foo 3
// bar 7
13)async: ES6 提供的一种异步编程解决方案
定义:async是 Generator 函数的语法糖,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
async函数对 Generator 函数的改进
a)内置执行器----Generator 函数的执行必须靠执行器(显示调用next()),才有了co模块;async函数的执行,与普通函数一模一样,只要一行
asyncReadFile();
b)更好的语义----async(vs *)表示函数里有异步操作,await( yield )表示紧跟在后面的表达式需要等待结果。
c)更广的适用性-----co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象;await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)----被转成一个立即resolve的 Promise 对象。
async function f() {
return await 123;
}
f().then(v => console.log(v))
// 123
await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到
async function f() {
await Promise.reject('出错了');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
d)返回值是 Promise-----Generator 函数的返回值是 Iterator 对象方便多,可以调用then()指定下一步的操作
基本用法
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
// 指定 50 毫秒以后,输出hello world
function timeout(ms) {
return new Promise((resolve) => {
console.log(1);
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
async函数声明的 方式-----5种
// 1、函数声明
async function foo() {}
///2、函数表达式
const foo = async function () {};
///3、对象的方法
let obj = { async foo() {} };
obj.foo().then(...)
///4、Class 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 5、箭头函数
const foo = async () => {};
语法
返回值:
async函数返回一个 Promise 对象,内部return语句返回的值,会成为then方法回调函数的参数
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
// "hello world"
错误处理--
async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到
async function f() {
throw new Error('出错了');
}
f().then(
v => console.log(v),
e => console.log(e)
)
// Error: 出错了
前一个异步操作失败,也不要中断后面的异步操作
//1、第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// hello world
//2、await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// 出错了
// hello world
Promise对象状态变化
只有async函数内部所有的异步操作执行完,才会执行then方法指定的回调函数,除非遇到return语句或者抛出错误
//函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,
//才会执行then方法里面的console.log
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
使用注意点
a)await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}
b)多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发
//继发关系,互不依赖,耗时
let foo = await getFoo();
let bar = await getBar();
//非继发关系
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
c)await命令只能用在async函数之中,如果用在普通函数,就会报错
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}
function dbFuc(db) { //这里不需要 async
let docs = [{}, {}, {}];
// 可能得到错误结果,三个db.post操作将是并发执行
docs.forEach(async function (doc) {
await db.post(doc);
});
}
//正确
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
async函数实现原理:将 Generator 函数和自动执行器,包装在一个函数里
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () { //spawn函数就是自动执行器
// ...
});
}
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
js中实现异步编程的方法
回调、事件监听 、发布-订阅、Promise对象、Generator、Async
回调----通过函数的嵌套
Promise----将回调函数的嵌套,改成链式调用,通过then()方法实现
Generator----可以暂停执行和恢复执行,这是它能封装异步任务的根本原因.函数体内外的数据交换next()和错误处理机制throw()
异步方法比较---Promise Genarator async
假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值
//1、Promise 的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是 Promise
//的 API(then、catch等等),操作本身的语义反而不容易看出来
function chainAnimationsPromise(elem, animations) {
// 变量ret用来保存上一个动画的返回值
let ret = null;
// 新建一个空的Promise
let p = Promise.resolve();
// 使用then方法,添加所有动画
for(let anim of animations) {
p = p.then(function(val) {
ret = val;
return anim(elem);
});
}
// 返回一个部署了错误捕捉机制的Promise
return p.catch(function(e) {
/* 忽略错误,继续执行 */
}).then(function() {
return ret;
});
}
//2、Generator
//用 Generator 函数遍历了每个动画,语义比 Promise 写法更清晰,用户定义的操作全部都出
//现在spawn函数的内部
//问题,必须有一个自动执行器,spawn函数就是自动执行器,它返回一个 Promise 对象,而且必须保证//yield语句后面的表达式,必须返回一个 Promise。
function chainAnimationsGenerator(elem, animations) {
return spawn(function*() {
let ret = null;
try {
for(let anim of animations) {
ret = yield anim(elem);
}
} catch(e) {
/* 忽略错误,继续执行 */
}
return ret;
});
}
//3、async函数
//实现最简洁,最符合语义
async function chainAnimationsAsync(elem, animations) {
let ret = null;
try {
for(let anim of animations) {
ret = await anim(elem);
}
} catch(e) {
/* 忽略错误,继续执行 */
}
return ret;
}
41、ES7----ECMAScript2016
ES7比ES6就多了3种新特性:新的数学运算符 新的数组方法 新的语法错误
1)新的数学运算符 -----指数运算符 (唯一一个js语法变化)------求幂运算符 ** 基数**指数
已经有Math.pow(基数,指数)
运算顺序----大于+ - * / % 运算限制-----左侧的一元表达式只能使用++ --
// 1
let num = 3;
console.log( num--**2 )//9 num=2
// 2
let num = 3;
console.log( ++num**2 )//16 num=4
// 3
console.log( -5**2 )//出错
// 4
console.log( 2*5**2 )//50
2)新的数组方法 -----Array.prototype.includes( elem [,index] )----elem要搜索的值,index开始检索的地方(可省略,默认为0)
返回值:存在----true 不存在----false
值的比较---NaN在indexOf()中不相等,在includes()中相等;+0和-0在indexOf()和includes()相等,在Object.is()不相等
3)函数作用域严格模式的一处改动---只有参数为不包含解构、默认值的简单参数列表时,才可以在函数中使用use strict
更多推荐



所有评论(0)