es6笔记(2)

180 阅读21分钟

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」。

Reflect

概述

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。

  • Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法

  • 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

    // 老写法
    try {
      Object.defineProperty(target, property, attributes);
      // success
    } catch (e) {
      // failure
    }
    
    // 新写法
    if (Reflect.defineProperty(target, property, attributes)) {
      // success
    } else {
      // failure
    }
    
  • Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

    // 老写法
    "assign" in Object; // true
    
    // 新写法
    Reflect.has(Object, "assign"); // true
    

静态方法

  • Reflect.get

    • Reflect.get(target, name, receiver),Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined

      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
      
    • 如果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
      
    • 如果第一个参数不是对象,Reflect.get方法会报错。

      Reflect.get(1, "foo"); // 报错
      Reflect.get(false, "foo"); // 报错
      
  • Reflect.set

    • Reflect.set(target, name, value, receiver),Reflect.set方法设置target对象的name属性等于value

      var myObject = {
        foo: 1,
      };
      
      myObject.foo; // 1
      
      Reflect.set(myObject, "foo", 2);
      myObject.foo; // 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
      
  • Reflect.has

    • Reflect.has(obj, name),Reflect.has方法对应name in obj里面的in运算符

      var myObject = {
        foo: 1,
      };
      
      // 旧写法
      "foo" in myObject; // true
      
      // 新写法
      Reflect.has(myObject, "foo"); // true
      

      如果Reflect.has()方法的第一个参数不是对象,会报错。

  • Reflect.deleteProperty

    • Reflect.deleteProperty(obj, name),Reflect.deleteProperty方法等同于delete obj[name],用于删除对象的属性

      const myObj = { foo: "bar" };
      
      // 旧写法
      delete myObj.foo;
      
      // 新写法
      Reflect.deleteProperty(myObj, "foo");
      

      该方法返回一个布尔值。如果删除成功,或者被删除的属性不存在,返回true;删除失败,被删除的属性依然存在,返回false。如果Reflect.deleteProperty()方法的第一个参数不是对象,会报错.

Promise

概述

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

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

promise 特点

Promise对象有以下两个特点。

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

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

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

promise 使用方法

  • ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

  • Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

    var p = new Promise(function (resolve, reject) {
      if (true) {
        resolve(data);
      } else {
        reject(data);
      }
    });
    

    resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

  • Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

    p.then(
      function (value) {
        // success业务处理
      },
      function (error) {
        // failure
      }
    );
    

    then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

    • 看一个简单的例子

      function time(ms) {
        return new Promise((resolve, reject) => {
          setTimeout(resolve, ms);
        });
      }
      time(1000).then((value) => {
        console.log(value);
      });
      
  • Promise 新建后就会立即执行。

    let promise = new Promise(function (resolve, reject) {
      console.log("Promise");
      resolve();
    });
    
    promise.then(function () {
      console.log("resolved.");
    });
    
    console.log("Hi!");
    //Promise
    //Hi
    //resolved
    

class

概述

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return "(" + this.x + ", " + this.y + ")";
  }
}

基本介绍

  • constructor()

    • constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
  • 类的实例

    • 生成类的实例的写法,与 ES5 完全一样,也是使用new命令。前面说过,如果忘记加上new,像函数那样调用Class,将会报错。

      class Point {
        // ...
      }
      // 报错
      var point = Point(2, 3);
      // 正确
      var point = new Point(2, 3);
      
    • 与 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
      
    • 与 ES5 一样,类的所有实例共享一个原型对象

      var p1 = new Point(2, 3);
      var p2 = new Point(3, 2);
      
      p1.__proto__ === p2.__proto__;
      //true
      
  • 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,是从表达式得到的

static

  • 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上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类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。

  • 注意,如果静态方法包含this关键字,这个this指的是类,而不是实例

    class Foo {
      static bar() {
        this.baz();
      }
      static baz() {
        console.log("hello");
      }
      baz() {
        console.log("world");
      }
    }
    
    Foo.bar(); // hello
    

    解说:上面代码中,静态方法bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。

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

    class Foo {
      static classMethod() {
        return "hello";
      }
    }
    
    class Bar extends Foo {}
    
    Bar.classMethod(); // 'hello'
    
  • 静态属性

    静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性

    ES6 明确规定,Class 内部只有静态方法,没有静态属性。现在有一个提案提供了类的静态属性,写法是在实例属性的前面,加上static关键字。

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

继承

  • 简介

    • Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

      class Point {}
      
      class ColorPoint extends Point {}
      

      解说:上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point

    • 子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

      class Point {
        /* ... */
      }
      
      class ColorPoint extends Point {
        constructor() {}
      }
      
      let cp = new ColorPoint(); // ReferenceError
      
    • 在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。

      class Point {
        constructor(x, y) {
          this.x = x;
          this.y = y;
        }
      }
      
      class ColorPoint extends Point {
        constructor(x, y, color) {
          this.color = color; // ReferenceError
          super(x, y);
          this.color = color; // 正确
        }
      }
      
  • super

    • super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同

      • 第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。

        class A {}
        
        class B extends A {
          constructor() {
            super();
          }
        }
        

        解说:上面代码中,子类B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)`

      • 第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类

        class A {
          p() {
            return 2;
          }
        }
        
        class B extends A {
          constructor() {
            super();
            console.log(super.p()); // 2
          }
        }
        
        let b = new B();
        
      • 由于super指向父类的原型对象(prototype),所以定义在父类实例上的方法或属性,是无法通过super调用的

        class A {
          constructor() {
            this.p = 2;
          }
        }
        
        class B extends A {
          get m() {
            return super.p;
          }
        }
        
        let b = new B();
        b.m; // undefined
        

Set

基本用法

  • ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

  • Set本身是一个构造函数,用来生成 Set 数据结构。

  • Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

    const set = new Set([1, 2, 3, 4, 4]);
    console.log(set); //[1,2,3,4]
    
    • 数组去重

      [...new Set([1, 2, 3, 2, 4, 5])]; //[1,2,3,4,5]
      
    • 字符串去重

      [...new Set("ababbc")].join(""); //"abc"
      

属性和方法

  • Set 结构的实例有以下属性。

    • Set.prototype.constructor:构造函数,默认就是Set函数。
    • Set.prototype.size:返回Set实例的成员总数。
  • Set 实例的方法分为两大类

    • 操作方法(用于操作数据)

      • Set.prototype.add(value):添加某个值,返回 Set 结构本身

        const items = new Set([]);
        items.add(1).add(2).add(3);
        console.dir(items); //[1,2,3]
        
      • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功

        const items = new Set([12, 23, 34]);
        var b = items.delete(12);
        console.log(b); //true
        console.log(items); //Set(2) {23, 34}
        
      • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。

      • Set.prototype.clear():清除所有成员,没有返回值

        const items = new Set([12, 23, 34]);
        var b = items.clear(12);
        console.log(b); //undefined
        console.log(items); //Set(0) {}
        
    • 遍历方法(用于遍历成员)

      • Set.prototype.keys():返回键名的遍历器

      • Set.prototype.values():返回键值的遍历器

      • Set.prototype.entries()

        let set = new Set(["red", "green", "blue"]);
        for (let item of set.keys()) {
          console.log(item);
        }
        // red
        // green
        // blue
        for (let item of set.values()) {
          console.log(item);
        }
        // red
        // green
        // blue
        for (let item of set.entries()) {
          console.log(item);
        }
        // ["red", "red"]
        // ["green", "green"]
        // ["blue", "blue"]
        
      • Set.prototype.forEach()

        let set = new Set([1, 4, 9]);
        set.forEach((value, key) => console.log(key + " : " + value));
        // 1 : 1
        // 4 : 4
        // 9 : 9
        

Map

概述

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

基本用法

  • 作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组

    const map = new Map([
      ["name", "张三"],
      ["title", "Author"],
    ]);
    
    map.size; // 2
    map.has("name"); // true
    map.get("name"); // "张三"
    map.has("title"); // true
    map.get("title"); // "Author"
    

属性和方法

  • size 属性,size属性返回 Map 结构的成员总数。

    const map = new Map();
    map.set("foo", true);
    map.set("bar", false);
    
    map.size; // 2
    
  • Map.prototype.set(key, value) set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键, set()方法返回的是 set 对象可以采用链式写法

    const m = new Map();
    
    m.set("edition", 6); // 键是字符串
    m.set(262, "standard"); // 键是数值
    m.set(undefined, "nah"); // 键是 undefined
    
  • Map.prototype.get(key) get方法读取key对应的键值,如果找不到key,返回undefined

    const m = new Map();
    
    const hello = function () {
      console.log("hello");
    };
    m.set(hello, "Hello ES6!"); // 键是函数
    
    m.get(hello); // Hello ES6!
    
  • Map.prototype.has(key)has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中

    const m = new Map();
    
    m.set("edition", 6);
    m.set(262, "standard");
    m.set(undefined, "nah");
    
    m.has("edition"); // true
    m.has("years"); // false
    m.has(262); // true
    m.has(undefined); // true
    
  • Map.prototype.delete(key)delete方法删除某个键,返回true。如果删除失败,返回false

    const m = new Map();
    m.set(undefined, "nah");
    m.has(undefined); // true
    
    m.delete(undefined);
    m.has(undefined); // false
    
  • Map.prototype.clear()clear方法清除所有成员,没有返回值

    let map = new Map();
    map.set("foo", true);
    map.set("bar", false);
    
    map.size; // 2
    map.clear();
    map.size; // 0
    

遍历

  • Map.prototype.keys():返回键名的遍历器。

  • Map.prototype.values():返回键值的遍历器

  • Map.prototype.entries():返回所有成员的遍历器

  • Map.prototype.forEach():遍历 Map 的所有成员

    const map = new Map([
      ["F", "no"],
      ["T", "yes"],
    ]);
    
    for (let key of map.keys()) {
      console.log(key);
    }
    // "F"
    // "T"
    
    for (let value of map.values()) {
      console.log(value);
    }
    // "no"
    // "yes"
    
    for (let item of map.entries()) {
      console.log(item[0], item[1]);
    }
    // "F" "no"
    // "T" "yes"
    
    // 或者
    for (let [key, value] of map.entries()) {
      console.log(key, value);
    }
    // "F" "no"
    // "T" "yes"
    map.forEach(function (value, key, map) {
      console.log("Key: %s, Value: %s", key, value);
    });
    

    forEach方法还可以接受第二个参数,用来绑定this

Symbol

概述

ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefinednull、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

var obj = {
  say: "lagou",
};
var say = Symbol(); //say 是symbol类型
obj[say] = "web";
console.log(obj); //{say: "lagou", Symbol(): "web"}

语法

  • Symbol函数前不能使用new命令,否则会报错

  • Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分

  • 每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性

    var a = Symbol();
    var b = Symbol();
    console.log(a === b); //false
    var a = Symbol("a");
    var b = Symbol("b");
    console.log(a === b); //false
    

    注意:Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的

    var a = Symbol("a");
    var b = Symbol("a");
    console.log(a === b); //false
    
  • Symbol 值不能与其他类型的值进行运算,会报错

    let sym = Symbol("My symbol");
    
    "your symbol is " +
      sym // TypeError: can't convert symbol to string
      `your symbol is ${sym}`;
    // TypeError: can't convert symbol to string
    
  • Symbol 值作为对象属性名时,不能用点运算符

    const mySymbol = Symbol();
    const a = {};
    a.mySymbol = "Hello!";
    console.log(a[mySymbol]); //undefined
    console.log(a["mySymbol"]); //hello
    
  • Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

    const obj = {};
    let a = Symbol("a");
    let b = Symbol("b");
    obj[a] = "Hello";
    obj[b] = "World";
    const objectSymbols = Object.getOwnPropertySymbols(obj);
    console.log(objectSymbols); //[Symbol(a), Symbol(b)]
    
  • 有时,我们希望重新使用同一个 Symbol 值,Symbol.for()方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。

    let s1 = Symbol.for("foo");
    let s2 = Symbol.for("foo");
    
    s1 === s2; // true
    

    Symbol.for()Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值

    Symbol.for("bar") === Symbol.for("bar");
    // true
    
    Symbol("bar") === Symbol("bar");
    

    由于Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。Symbol.for()为 Symbol 值登记的名字,是全局环境的,不管有没有在全局环境运行

    function foo() {
      return Symbol.for("bar");
    }
    
    const x = foo();
    const y = Symbol.for("bar");
    console.log(x === y); // true
    

可迭代接口

Iterater 的概念

  • 简单介绍

    JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了MapSet。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是MapMap的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。

    遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

    Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

  • Iterator 的遍历过程

    • 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

    • 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员

    • 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员

    • 不断调用指针对象的next方法,直到它指向数据结构的结束位置

      每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含valuedone两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

      简单的Iterator遍历器的实现;
      var it = easyIterator(["a", "b"]);
      
      it.next(); // { value: "a", done: false }
      it.next(); // { value: "b", done: false }
      it.next(); // { value: undefined, done: true }
      
      function easyIterator(array) {
        var nextIndex = 0;
        return {
          next: function () {
            return nextIndex < array.length
              ? { value: array[nextIndex++], done: false }
              : { value: undefined, done: true };
          },
        };
      }
      

Iterater 接口

  • 字符串 数组 set map arguments 都有 iterater 接口,nodelist 集合,都可以用 for of 遍历

    var st = "lagou";
    for (i of st) {
      console.log(i); // l a g o u
    }
    var arr = [1, 2];
    for (v of arr) {
      console.log(v); //1 2
    }
    function fn(a, b, c) {
      for (i of arguments) {
        console.log(i); //1 2 3
      }
    }
    fn(1, 2, 3);
    

Modules

概述

  • JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

语法

  • export

    • export命令用于规定模块的对外接口

    • 一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量

      //demo.js
      export var firstName = "Michael";
      export var lastName = "Jackson";
      export var year = 1958;
      //或者
      var firstName = "Michael";
      var lastName = "Jackson";
      var year = 1958;
      
      export { firstName, lastName, year };
      
  • import

    • import命令用于输入其他模块提供的功能

    • import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同

      // main.js
      import { firstName, lastName, year } from "./profile.js";
      
      function setName(element) {
        element.textContent = firstName + " " + lastName;
      }
      
  • export default

    • 为了给用户提供方便,就要用到export default命令,为模块指定默认输出

    • 本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字

    • import命令后面,不再使用大括号

    • export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次

      // export-default.js
      export default function () {
        console.log("foo");
      }
      
      // import-default.js
      import customName from "./export-default";
      customName(); // 'foo'
      

浏览器端加载实现

  • 浏览器加载 ES6 模块,也使用标签,但是要加入type="module"属性

    // 01.js
    export var a = 123;
    
    //demo.html
    <script type="module">import {a} from "./01.js"; console.log(a)//123</script>
    
  • 脚本异步加载

    <script src="path/to/myModule.js" defer></script>
    <script src="path/to/myModule.js" async></script>
    

    解说:上面代码中,标签打开deferasync属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。defer 与 async 的区别是:defer 要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;async 一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer 是“渲染完再执行”,async 是“下载完就执行”。另外,如果有多个 defer 脚本,会按照它们在页面出现的顺序加载,而多个 async 脚本是不能保证加载顺序的。