1.async 函数
•定义:就是 Generator 函数的语法糖。
•基本用法:
–async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,
一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
•Promise对象的状态变化:
–async 函数返回的 Promise 对象,必须要等到内部所有的await命令后面的 Promise对象执行完,
才会发生状态变化,除非遇到 return 语句或者抛出错误。也就是说,只有 async 函数内部的一步操作执行完,
才会执行 then 方法指定的回调函数。
•await 命令
–正常情况下, await 命令后面是一个 Promise 对象,返回改对象的结果。如果不是 Promise 对象,
就直接返回对应的值。
async function f() { // 等同于 // return 123; return await 123; } f().then(v => console.log(v)) // 123
•任何一个 await 语句后面的 Promise 对象变为 rejected 状态,那么整个 async 函数都中断执行。
asyncfunctionf() {
awaitPromise.reject('出错了');
awaitPromise.resolve('hello world');// 不会执行
}
•有时,我们希望即使前一个一步操作失败,也不要中断后面的异步操作。这是可以将第一个 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
•错误处理
•解决办法是将其放在 try...catch 中。
eg1:
async function main() { try { const val1 = await firstStep(); const val2 = await secondStep(val1); const val3 = await thirdStep(val1, val2); console.log('Final: ', val3); } catch (err) { console.error(err); } } eg2: const superagent = require('superagent'); const NUM_RETRIES = 3; async function test() { let i; for (i = 0; i << span=""> NUM_RETRIES; ++i) { try { await superagent.get('http://google.com/this-throws-an-error'); break; } catch(err) {} } console.log(i); // 3 } test();
注:上面的两个案例,如果 await 操作成功,就会使用 break 语句退出循环;如果失败,会被 catch 语句捕捉,然后进入下一轮循环。
•使用注意点:
1.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); }); }
1.多个 await 命令后面的异步操作,如果不存在继发关系,最好让他们同事触发。
let foo = await getFoo(); let bar = await getBar(); /**
上面代码中,gooFoo 和 getBar 是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有 getFoo 完成之后,才会执行 getBar,完全可以让他们同时触发。
*/ // 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;
// 上面两种写法,getFoo 和 getBar 都是同时触发,这样就会缩短程序的执行时间。
1.await 命令只能用在 async 函数之中,如果用在普通函数,就会报错
2.async 函数可以保留运行堆栈
const a = async () => { await b(); c(); }; /** * 上面代码中,b() 运行的同时,a() 是暂停执行的,上下文环境都保存着,一旦b() 或 c() 报错,错误堆栈 将包括a() */
•async 函数的实现原理
•async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args) { // ... } // 等同于 function fn(args) { return spawn(function* () { // ... }); }
•所有的 async 函数都可以写成上面的第二种形式,其中的 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); }); }); }
1.Class 的基本语法
简介:类的由来
•JavaScript 中,生成实例对象的传统方法是通过构造函数。ES6 通过 class 关键字来定义类。
•ES6 的 class 可以看做是一个语法糖,他的绝大部分功能,ES5 都可以做到。
eg:
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } }
初始化类的注意点
•可以看到一个类,里面有一个 constructor 方法,就是构造方法,而 this 关键字是代表实例对象。
•类除了构造方法,还可以定义其他的方法,但在定义方法的前面不可以使用逗号进行分割,加了会报错。
•类的数据类型就是函数,类本身就指向构造函数
•类的所有方法都是定义在了类的 prototype 属性上面。
•在类的实例上面调用方法,就是调用原型上的方法
•Object.assign() 方法可以很方便地一次向类添加多个方法
•类必须使用 new 调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用 new 也可以执行。
•类的所有实例共享一个原型对象。
•可以通过实例的 __proto__ 属性为“类”添加方法
•生产环境中,我们可以通过 Object.getPrototypeOf() 方法来获取实例对象的原型,然后再为原型添加方法
或者属性
class Point { // ... } typeof Point // "function" Point === Point.prototype.constructor // true
注:类的所有方法都是定义在了类的 prototype 属性上面。
•在类的实例上面调用方法,就是调用原型上的方法
class B {} const b = new B(); b.constructor === B.prototype.constructor // true
注:上面代码中,b是B类的实例,它的 constructor() 方法就是B类原型的 constructor() 方法。
•由于类的方法都定义在 prototype 对象上面,所以类的新方法可以添加在 prototype 对象上面。
Object.assign() 方法可以很方便地一次向类添加多个方法。
class Point { constructor(){ // ... } } Object.assign(Point.prototype, { toString(){}, toValue(){} });
注:prototype 对象的 constructor() 属性,直接指向“类”的本身,这与 ES5 的行为是一致的。
Point.prototype.constructor===Point// true
•另外,类的内部所有定义的方法,都是不可枚举的 (non-enumerable)。
class Point { constructor(x, y) { // ... } toString() { // ... } } Object.keys(Point.prototype) // [] Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"] /**
* 上面代码中,toString()方法是Point类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。
*/ var Point = function (x, y) { // ... }; Point.prototype.toString = function () { // ... }; Object.keys(Point.prototype) // ["toString"] Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"]
constructor 方法
•constructor 方法是类的默认方法,通过 new 关键字来生成对象的实例时,自动调用该方法,一个类必需有
constructor 方法,如果没有显示定义,一个空的 constructor 方法就会被默认添加。
class Point { } // 等同于 class Point { constructor() {} }
•constructor() 方法默认返回实例对象 (即this) ,完全可以指定返回另外一个对象。
class Foo { constructor() { return Object.create(null); } } new Foo() instanceof Foo // false
•类必须使用 new 调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用 new 也可以执行。
class Foo { constructor() { return Object.create(null); } } Foo() // TypeError: Class constructor Foo cannot be invoked without 'new'
•与 ES5 一样,实例的属性除非显示定义在其本身(也就是定义在 this 对象上),否则都是定义在原型上
(即定义在 class 上 )
//定义类 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } var point = new Point(2, 3); point.toString() // (2, 3) point.hasOwnProperty('x') // true point.hasOwnProperty('y') // true point.hasOwnProperty('toString') // false point.__proto__.hasOwnProperty('toString') // true /**
上面代码中,x和y都是实例对象point自身的属性(因为定义在this对象上),所以hasOwnProperty()方法
返回true,而toString()是原型对象的属性(因为定义在Point类上),所以hasOwnProperty()方法
返回false。这些都与 ES5 的行为保持一致。
*/
•类的实例共享一个原型对象
var p1 = new Point(2,3); var p2 = new Point(3,2); p1.__proto__ === p2.__proto__ //true /**
上面代码中,p1和p2都是Point的实例,它们的原型都是Point.prototype,所以__proto__属性是相等的。
*/
•我们可以通过实例的 __proto__ 属性来为类添加方法。
var p1 = new Point(2,3); var p2 = new Point(3,2); p1.__proto__.printName = function () { return 'Oops' }; p1.printName() // "Oops" p2.printName() // "Oops" var p3 = new Point(4,2); p3.printName() // "Oops" /**
上面代码在p1的原型上添加了一个printName()方法,由于p1的原型就是p2的原型,因此p2也可以调用这个
方法。而且,此后新建的实例p3也可以调用这个方法。这意味着,使用实例的__proto__属性改写原型,
必须相当谨慎,不推荐使用,因为这会改变“类”的原始定义,影响到所有实例。
*/
取值 getter 和 存值函数 setter
•与ES5 一样,在类的内部可以使用 get 和 set 关键字,对某个属性设置存值函数和取值函数,拦截改属性的
存取行为。
class MyClass { constructor() { // ... } get prop() { return 'getter'; } set prop(value) { console.log('setter: '+value); } } let inst = new MyClass(); inst.prop = 123; // setter: 123 inst.prop // 'getter' 属性表达式 let methodName = 'getArea'; class Square { constructor(length) { // ... } [methodName]() { // ... } } // 上面代码中,Square类的方法名getArea,是从表达式得到的。 Class 表达式 const MyClass = class Me { getClassName() { return Me.name; } };
•如果类的内部没有用到的话,可以写成下面的这种简写方法
const MyClass =class { /* ... */ };
•采用 class 表达式可以写出立即执行的 class。
let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }('张三'); person.sayName(); // "张三"
// 上面代码中,person是一个立即执行的类的实例。
注意点
1.严格模式:类和模快的内部,默认就是使用严格模式,所以不需要使用 use strict 指定运行模式。
2.不存在提升:类不存在变量提升(hoist),这一点与ES5完全不同
new Foo(); // ReferenceError class Foo {}
•因为ES6不允许把类的声明提升到代码头部,这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。
{ let Fo = class {}; class Bar extends Foo { } } // 因为class会被提升到代码头部,而let命令是不提升的,所以导致Bar继承Foo的时候,Foo还没有定义。
1.name 属性
•由于本质上,ES6 的类只是ES5 的构造函数的一层包装,所以函数的许多特性都被 class 继承,包括
name 属性。
class Point {} Point.name // "Point"
•name 属性总是返回紧跟在 class 关键字后面的类名。