ES6基础6

  1. Promise对象
    1. Promise的含义
      1. 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。

        Promise对象有以下两个特点。

        1. 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

        2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

          有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

          Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

          如果某些事件不断地反复发生,一般来说,使用stream模式是比部署Promise更好的选择。

    2. 基本用法

      let promise = new Promise(function(resolve, reject) {
        console.log('Promise');
        resolve();
      });
      
      promise.then(function() {
        console.log('Resolved.');
      });
      
      console.log('Hi!');
      
      // Promise
      // Hi!
      // Resolved
    3. Promise.prototype.then()

      getJSON("/post/1.json").then(function(post) {
        return getJSON(post.commentURL);
      }).then(function funcA(comments) {
        console.log("Resolved: ", comments);
      }, function funcB(err){
        console.log("Rejected: ", err);
      });
    4. Promise.prototype.catch()

      p.then((val) => console.log("fulfilled:", val))
        .catch((err) => console.log("rejected:", err));
      
      // 等同于
      p.then((val) => console.log("fulfilled:", val))
        .then(null, (err) => console.log("rejected:", err));
    5. Promise.all()

      1. Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。

        var p = Promise.all([p1, p2, p3]);

        上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。)

        p的状态由p1、p2、p3决定,分成两种情况。

        1. 只有p1、p2、p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1、p2、p3返回组成一个数组,传递给p的回调函数。

        2. 只要p1、p2、p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

          // 生成一个Promise对象的数组
          var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
            return getJSON("/post/" + id + ".json");
          });
          
          Promise.all(promises).then(function (posts) {
            // ...
          }).catch(function(reason){
            // ...
          });
    6. Promise.race()

      1. Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。

        var p = Promise.race([p1, p2, p3]);

        上面代码中,只要p1、p2、p3之中有一个实例率先改变状态p的状态跟着改变。那个率先改变的 Promise 实例返回值,就传递给p的回调函数。

        Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。

        下面是一个例子,如果指定时间内没有获得结果,就将Promise的状态变为reject,否则变为resolve。

        var p = Promise.race([
          fetch('/resource-that-may-take-a-while'),
          new Promise(function (resolve, reject) {
            setTimeout(() => reject(new Error('request timeout')), 5000)
          })
        ])
        p.then(response => console.log(response))
        p.catch(error => console.log(error))

        上面代码中,如果5秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。

    7. Promise.resolve()

      1. 有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。

        Promise.resolve('foo')
        // 等价于
        new Promise(resolve => resolve('foo'))
    8. Promise.reject()

      1. Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。它的参数用法与Promise.resolve方法完全一致。

        var p = Promise.reject('出错了');
        // 等同于
        var p = new Promise((resolve, reject) => reject('出错了'))
        
        p.then(null, function (s){
          console.log(s)
        });
        // 出错了
    9. 两个有用的附加方法

      1. done()

        1. Promise对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。

          asyncFunc()
            .then(f1)
            .catch(r1)
            .then(f2)
            .done();
      2. finally()

        1. finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

          下面是一个例子,服务器使用Promise处理请求,然后使用finally方法关掉服务器。

          server.listen(0)
            .then(function () {
              // run test
            })
            .finally(server.stop);
  2. Async函数
    1. ES7提供了async函数,使得异步操作变得更加方便。
    2. 语法
      1. async函数返回一个Promise对象。
        1. async函数内部return语句返回的值,会成为then方法回调函数的参数。
          async function f() {
            return 'hello world';
          }
          
          f().then(v => console.log(v))
          // "hello world"
        2. async函数内部抛出错误,会导致返回的Promise对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
          async function f() {
            throw new Error('出错了');
          }
          
          f().then(
            v => console.log(v),
            e => console.log(e)
          )
          // Error: 出错了
      2. async函数返回的Promise对象,必须等到内部所有await命令的Promise对象执行完,才会发生状态改变。也就是说,只有async函数内部的异步操作执行完才会执行then方法指定的回调函数
      3. 正常情况下,await命令后面是一个Promise对象。如果不是,会被转成一个立即resolve的Promise对象。
        async function f() {
          return await 123;
        }
        
        f().then(v => console.log(v))
        // 123
      4. 如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject。
        async function f() {
          try {
            await new Promise(function (resolve, reject) {
              throw new Error('出错了');
            });
          } catch(e) {
          }
          return await('hello world');
        }
    3. 注意点
      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);
          };
        }

      2. 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
        let foo = await getFoo();
        let bar = await getBar();

        上面代码中,getFoo和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都是同时触发,这样就会缩短程序的执行时间

      3. await命令只能用在async函数之中,如果用在普通函数,就会报错。
        async function dbFuc(db) {
          let docs = [{}, {}, {}];
        
          // 报错
          docs.forEach(function (doc) {
            await db.post(doc);
          });
        }

        上面代码会报错,因为await用在普通函数之中了。但是,如果将forEach方法的参数改成async函数,也有问题。

        async function dbFuc(db) {
          let docs = [{}, {}, {}];
        
          // 可能得到错误结果
          docs.forEach(async function (doc) {
            await db.post(doc);
          });
        }

        上面代码可能不会正常工作,原因是这时三个db.post操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。

        async function dbFuc(db) {
          let docs = [{}, {}, {}];
        
          for (let doc of docs) {
            await db.post(doc);
          }
        }

  3. Class基本语法
    1. 概述
      1. 示例
        //定义类
        class Point {
          constructor(x, y) {
            this.x = x;
            this.y = y;
          }
        
          toString() {
            return '(' + this.x + ', ' + this.y + ')';
          }
        }

        此外,类的数据类型就是函数,类本身就指向构造函数。

        class Point {
          // ...
        }
        
        typeof Point // "function"
        Point === Point.prototype.constructor // true

        构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

        class Point {
          constructor(){
            // ...
          }
        
          toString(){
            // ...
          }
        
          toValue(){
            // ...
          }
        }
        
        // 等同于
        
        Point.prototype = {
          toString(){},
          toValue(){}
        };

        在类的实例上面调用方法,其实就是调用原型上的方法。

        class B {}
        let b = new B();
        
        b.constructor === B.prototype.constructor // true

        另外,在ES6中,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。

        class Point {
          constructor(x, y) {
            // ...
          }
        
          toString() {
            // ...
          }
        }
        
        Object.keys(Point.prototype)
        // []
        Object.getOwnPropertyNames(Point.prototype)
        // ["constructor","toString"]
    2. constructor方法
      1. constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
        constructor() {}

        constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。下面代码中,constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。

        class Foo {
          constructor() {
            return Object.create(null);
          }
        }
        
        new Foo() instanceof Foo
        // false
    3. 类的实例对象
      1. 不存在变量提升,这点与ES5不同
    4. Class表达式
      1. 与函数一样,类也可以使用表达式的形式定义。
        const MyClass = class Me {
          getClassName() {
            return Me.name;
          }
        };

        上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是MyClass而不是MeMe只在Class的内部代码可用,指代当前类。

        
        const MyClass = class Me {
          getClassName() {
            return Me.name;
          }
        }
        let mc = new MyClass()
        console.log(mc.getClassName())  //Me
    5. 私有方法
      1. 私有方法是常见需求,但ES6不提供,只能通过变通方法模拟实现。一种做法是在命名上加以区别。

        class Widget {
        
          // 公有方法
          foo (baz) {
            this._bar(baz);
          }
        
          // 私有方法
          _bar(baz) {
            return this.snaf = baz;
          }
        
          // ...
        }

        上面代码中,_bar方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。

        另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。

        class Widget {
          foo (baz) {
            bar.call(this, baz);
          }
        
          // ...
        }
        
        function bar(baz) {
          return this.snaf = baz;
        }

        上面代码中,foo是公有方法,内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的私有方法。

        还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。

        const bar = Symbol('bar');
        const snaf = Symbol('snaf');
        
        export default class myClass{
        
          // 公有方法
          foo(baz) {
            this[bar](baz);
          }
        
          // 私有方法
          [bar](baz) {
            return this[snaf] = baz;
          }
        
          // ...
        };

        上面代码中,bar和snaf都是Symbol值,导致第三方无法获取到它们,因此达到了私有方法和私有属性的效果。

    6. this的指向
      1. 类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。下面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境,因为找不到print方法而导致报错。
        class Logger {
          printName(name = 'there') {
            this.print(`Hello ${name}`);
          }
        
          print(text) {
            console.log(text);
          }
        }
        
        const logger = new Logger();
        const { printName } = logger;
        printName(); // TypeError: Cannot read property 'print' of undefined

        一种有效的解决方法是使用箭头函数。

        class Logger{
            printName = (name = 'wang')=>{
                return this.print(`hello,${name}`)
            }
            print(text){
                console.log(text)
            }
        }
        let log = new Logger()
        log.printName()  //hello,wang
        

    7. name属性
      1. 由于本质上,ES6的类只是ES5的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。name属性总是返回紧跟在class关键字后面的类名。
        class Point {}
        Point.name // "Point"
    8. Class的继承
      1. Class之间可以通过extends关键字实现继承
        class ColorPoint extends Point {
          constructor(x, y, color) {
            super(x, y); // 调用父类的constructor(x, y)
            this.color = color;
          }
        
          toString() {
            return this.color + ' ' + super.toString(); // 调用父类的toString()
          }
        }
      2. 类的prototype属性和__proto__属性
        1. 子类的__proto__属性,表示构造函数的继承,总是指向父类。
        2. 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
          class A {
          }
          
          class B extends A {
          }
          
          B.__proto__ === A // true
          B.prototype.__proto__ === A.prototype // true

    9. Extends 的继承目标
      1. extends关键字后面可以跟多种类型的值。
        1. 第一种特殊情况,子类继承Object类。
          class A extends Object {
          }
          
          A.__proto__ === Object // true
          A.prototype.__proto__ === Object.prototype // true
        2. 第二种特殊情况,不存在任何继承。
          class A {
          }
          
          A.__proto__ === Function.prototype // true
          A.prototype.__proto__ === Object.prototype // true

          这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Funciton.prototype。但是,A调用后返回一个空对象(即Object实例),所以A.prototype.__proto__指向构造函数(Object)的prototype属性。

        3. 第三种特殊情况,子类继承null。
          class A extends null {
          }
          
          A.__proto__ === Function.prototype // true
          A.prototype.__proto__ === undefined // true
    10. Object.getPrototypeOf()
      1. Object.getPrototypeOf方法可以用来从子类上获取父类。因此,可以使用这个方法判断,一个类是否继承了另一个类。
        Object.getPrototypeOf(ColorPoint) === Point
        // true
    11. super 关键字
    12. 实例的__proto__属性
      1. 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。
        var p1 = new Point(2, 3);
        var p2 = new ColorPoint(2, 3, 'red');
        
        p2.__proto__ === p1.__proto__ // false
        p2.__proto__.__proto__ === p1.__proto__ // true

        上面代码中,ColorPoint继承了Point,导致前者原型的原型是后者的原型。

        p2.__proto__.__proto__.printName = function () {
          console.log('Ha');
        };
        
        p1.printName() // "Ha"

        因此,通过子类实例的__proto__.__proto__属性,可以修改父类实例的行为。

    13. Class的取值函数(getter)和存值函数(setter)

      1. 在Class内部可以使用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'

    14. Class的静态方法

      1. 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

        class Foo {
          static classMethod() {
            return 'hello';
          }
        }
        
        Foo.classMethod() // 'hello'
        
        var foo = new Foo();
        foo.classMethod()
        // TypeError: foo.classMethod is not a function

        上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。

        父类的静态方法,可以被子类继承。

        class Foo {
          static classMethod() {
            return 'hello';
          }
        }
        
        class Bar extends Foo {
        }
        
        Bar.classMethod(); // 'hello'

        上面代码中,父类Foo有一个静态方法,子类Bar可以调用这个方法。静态方法也是可以从super对象上调用的。

        
        class Foo {
          static classMethod() {
            return 'hello';
          }
        }
        
        class Bar extends Foo {
          static classMethod() {
            return super.classMethod() + ', too';
          }
        }
        
        console.log(Bar.classMethod()) //hello, too
        
    15. Class的静态属性和实例属性
      1. 静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。
        class Foo {
        }
        
        Foo.prop = 1;
        Foo.prop // 1
      2. ES7有一个静态属性的提案,目前Babel转码器支持。这个提案对实例属性和静态属性,都规定了新的写法。

        1. 类的实例属性。类的实例属性可以用等式,写入类的定义之中。

          class MyClass {
            myProp = 42;
          
            constructor() {
              console.log(this.myProp); // 42
            }
          }
        2. 类的静态属性。类的静态属性只要在上面的实例属性写法前面,加上static关键字就可以了。

          class MyClass {
            static myStaticProp = 42;
          
            constructor() {
              console.log(MyClass.myStaticProp); // 42
            }
          }
          let mc = new MyClass()

相关推荐

  1. ES6基础语法

    2024-03-13 11:42:04       40 阅读
  2. ES6基础3

    2024-03-13 11:42:04       23 阅读
  3. ES6基础1

    2024-03-13 11:42:04       18 阅读
  4. ES6基础4

    2024-03-13 11:42:04       17 阅读
  5. ES6基础5

    2024-03-13 11:42:04       18 阅读
  6. es6基础语法

    2024-03-13 11:42:04       8 阅读
  7. ES6

    2024-03-13 11:42:04       17 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-13 11:42:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-13 11:42:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-13 11:42:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-13 11:42:04       18 阅读

热门阅读

  1. 运维工程师面试题

    2024-03-13 11:42:04       19 阅读
  2. 在vue中什么是虚拟DOM?

    2024-03-13 11:42:04       20 阅读
  3. 计算机网络面经八股-HTTP1.0和HTTP1.1的区别?

    2024-03-13 11:42:04       18 阅读
  4. CSS进阶空间转换和 less

    2024-03-13 11:42:04       15 阅读
  5. php7.3.4连接sqlserver(linux平台)

    2024-03-13 11:42:04       18 阅读
  6. 【Docker】APISIX Dashboard 容器化部署

    2024-03-13 11:42:04       18 阅读
  7. ubuntu 更换国内镜像源

    2024-03-13 11:42:04       20 阅读
  8. [element-ui] el-table组件滚动条的宽度设置

    2024-03-13 11:42:04       19 阅读