ECMAScript6 从入门到入坟,你敢来挑战吗???(二)

简介: ECMAScript6 从入门到入坟,你敢来挑战吗???(二)

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 一样,在类的内部可以使用 getset 关键字,对某个属性设置存值函数和取值函数,拦截改属性的
存取行为。

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 关键字后面的类名。


相关文章
|
4月前
|
算法 C++
惊爆!KPM算法背后的秘密武器:一行代码揭秘字符串最小周期的终极奥义,让你秒变编程界周期大师!
【8月更文挑战第4天】字符串最小周期问题旨在找出字符串中最短重复子串的长度。KPM(实为KMP,Knuth-Morris-Pratt)算法,虽主要用于字符串匹配,但其生成的前缀函数(next数组)也可用于求解最小周期。核心思想是构建LPS数组,记录模式串中每个位置的最长相等前后缀长度。对于长度为n的字符串S,其最小周期T可通过公式ans = n - LPS[n-1]求得。通过分析周期字符串的特性,可证明该方法的有效性。提供的C++示例代码展示了如何计算给定字符串的最小周期,体现了KPM算法在解决此类问题上的高效性。
90 0
|
缓存 JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(六)
ECMAScript6 从入门到入坟,你敢来挑战吗???(六)
|
JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(三)
ECMAScript6 从入门到入坟,你敢来挑战吗???(三)
|
JSON 前端开发 JavaScript
ECMAScript6 从入门到入坟,你敢来挑战吗???(一)
ECMAScript6 从入门到入坟,你敢来挑战吗???
|
缓存 JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(五)
ECMAScript6 从入门到入坟,你敢来挑战吗???(五)
|
JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(四)
ECMAScript6 从入门到入坟,你敢来挑战吗???(四)
程序媛才能读懂的高级情话
程序媛才能读懂的高级情话
174 0
|
前端开发 JavaScript
#yyds干货盘点# 前端歌谣的刷题之路-第二十三题-检测复杂数据类型
#yyds干货盘点# 前端歌谣的刷题之路-第二十三题-检测复杂数据类型
98 0
#yyds干货盘点# 前端歌谣的刷题之路-第二十三题-检测复杂数据类型
|
前端开发
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(一)🔥
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(一)🔥
170 0
|
存储 前端开发
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(三)🔥
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(三)🔥
112 0
下一篇
DataWorks