阅读 503

重学ES6基础语法(五)

本系列博客为ES6基础语法的使用及总结,如有错误,欢迎指正。 重学ES6基础语法(五)主要包括 ES6模块化ES6的继承遍历器等。

ES6模块化(ES6 Module)

历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。

在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

在过去为了支持JS模块化,可以使用类、立即执行函数或者第三方插件(RequireJS、seaJS)来实现模块化

前端模块化开发

1.AMD 和 CMD

(1) AMD

全称:Asynchronous Module Definition(异步模块定义)

  • AMD规范是require.js推广的、对模块定义的规范
  • 非同步加载模块,允许指定回调函数
  • AMD在使用模块之前将依赖模块全部加载完成

(2) CMD

全称:Common Module Definition(通用模块定义)

  • CMD规范是SeaJS推广的、对模块定义的规范
  • 就近依赖,需要时再进行加载,所以执行顺序和书写顺序一致

(3) AMD 和CMD 二者异同

  • 相同:AMD 和 CMD都是浏览器端的js模块化规范

  • 异同:

    • AMD提前执行,CMD延迟执行
    • AMD推崇依赖前置,CMD推崇依赖就近
    • require.js遵循AMD规范,SeaJS遵循CMD规范
    • AMD受网络等因素执行顺序可能和书写顺序不一样(使用模块之前将依赖模块全部加载完成),CMD执行顺序和书写顺序一致

2.CommonJS

CommonJS是服务端模块的规范,NodeJS采用了这个规范。 CommonJS规范同步加载模块,也就是:只有加载完成,才能执行后面的操作。

3.AMD和CommonJS的比较

  • AMD非同步加载模块;CommonJS规范同步加载模块
  • AMD推荐风格是通过module transport规范暴露接口,即通过返回一个对象来暴露模块接口;CommonJS的风格是通过对module.exportsexports的属性赋值来达到暴露模块对象的目的

ES6模块化

模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

export 命令

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

1.常规导出

//分开导出
export let firstName = 'Michael';
export let lastName = 'Jackson';
export let year = 1958;

//一次性导出
let firstName = 'Michael';
let lastName = 'Jackson';
let year = 1958;

export {firstName, lastName, year};
复制代码

2.通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。

变量名被修改后原有变量名自动失效

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
复制代码

export default 命令

1.export default命令,为模块指定默认输出。

export default function () {
  console.log('foo');
}
复制代码

上面代码是一个模块文件,它的默认输出是一个函数。

其他模块加载该模块时,import命令可以为该匿名函数指定任意名字,不需要知道原模块输出的函数名。

2.注意点

  • 一个模块只能使用一次默认导出, 多次无效

  • 默认导出时, 导入的名称可以和导出的名称不一致

import 命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。

1.常规导入

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

import {firstName, lastName, year} from '那个js文件的路径';
复制代码

2.如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。

import { lastName as surname } from '那个js文件的路径';
复制代码

3.注意点

  • import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js路径可以省略。
  • 接收导入变量名必须和导出变量名一致(根本上是解构赋值)
  • 其他模块加载默认模块时,import命令可以为该匿名函数指定任意名字;不需要知道原模块输出的函数名。
  • 需要注意的是,加载默认模块时import命令后面,不使用大括号。
// export-default.js
export default function () {
  console.log('foo');
}

// import-default.js
import customName from './export-default';
customName(); // 'foo'
复制代码
  • 如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。

ES6的继承

ES6类和对象

Class基本语法

1.在ES6之前通过构造函数来定义一个类

function Person(myName, myAge) {
     // 实例属性
     this.name = myName;
     this.age = myAge;

     // 实例方法
     this.say = function () {
         console.log(this.name, this.age);
     }
     // 静态属性
     Person.num = 666;
     // 静态方法
     Person.run = function () {
         console.log("run");
     }
 }
 let p = new Person("zs", 18); //创建一个Person实例
 p.say(); //调用实例方法,访问实例属性
 console.log(Person.num); //访问静态属性
 Person.run(); //调用静态方法
复制代码

实例属性/实例方法
通过实例对象访问的属性,称之为实例属性;
通过实例对象调用的方法,称之为实例方法。

静态属性/静态方法
通过构造函数访问的属性,称之为静态属性;
通过构造函数调用的方法,称之为静态方法。

2.ES6引入了Class(类)这个概念,作为对象的模板。

通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

2.1 定义类的两种方式

//方式一
class User{
    ···
}

//方式二
const List = class{
    ···
};
复制代码

2.2 类是一种特殊的函数

  • 函数存在提升,但是类不存在提升,在定义之前调用类会报错
  • typeof检查类返回function
class User{
    
}
console.log(typeof User); //function
复制代码

2.3 上面的代码用ES6的“类”改写,就是下面这样:

class Person{
    constructor(myName, myAge){
        this.name = myName;
        this.age = myAge;
    }
    // 定义实例方法
    say(){
        console.log(this.name, this.age);
    }
    // 定义静态方法
    static run() {
        console.log("run");
    }
}
let p = new Person("zs", 18); //创建一个Person实例
p.say(); //调用实例方法
Person.run(); //调用静态方法
复制代码

3.constructor方法

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

  • constructor意义:类初始化时候执行的函数
  • 作用:初始化属性, 传入所需要的参数
  • 返回值:自动返回实例对象
  • 实例属性都需要在constructor中添加

4.在类里面定义方法注意点

  • 定义“类”的方法的时候,不需要加上function关键字,直接把函数定义放进去。
  • 方法之间不需要逗号分隔,加了会报错。
  • static关键字表示定义静态方法;ES6明确规定,Class内部只有静态方法,没有静态属性。
  • 实例方法写在constructor外面,默认是添加到原型上面的
    class Point {
       constructor(){
         // ...
       }
    
       toString(){
         // ...
       }
    
       toValue(){
         // ...
       }
    }
    
    // 等同于
    Point.prototype = {
       toString(){},
       toValue(){}
    };
    复制代码

(以上上面代码为例打印p这个对象,可以发现say()方法存在在原型上)

  • 类的内部所有定义的方法,都是不可枚举的(non-enumerable)。

ES6继承

ES6之前的继承

1.两个关键步骤

  • 在子类中通过call/apply方法借助父类的构造函数
  • 将子类的原型对象设置为父类的实例对象

2.三句关键代码

function Person(myName, myAge) {
   this.name = myName;
   this.age = myAge;
}
Person.prototype.say =  function () {
   console.log(this.name, this.age);
};

function Student(myName, myAge, myScore) {
   // 1.在子类中通过call/apply方法借助父类的构造函数
   Person.call(this, myName, myAge);
   this.score = myScore;
   this.study = function () {
     console.log("day day up");
   }
}
// 2.将子类的原型对象设置为父类的实例对象
Student.prototype = new Person();
Student.prototype.constructor = Student;

let stu = new Student("zs", 18, 99);
stu.say();
复制代码

ES6继承

1.Class之间可以通过extends关键字实现继承

  • 格式:class b extends a
  • 意思:定义了一个b类,该类通过extends关键字,继承了a类的所有属性和方法。

2.super关键字

  • 表示父类的构造函数,用来新建父类的this对象。
  • 子类必须在constructor方法中调用super方法,否则新建实例时会报错。
  • 注意,super虽然代表了父类a的构造函数,但是返回的是子类b的实例,即super内部的this指的是b

3.ES6实现继承

class Person{
   constructor(myName, myAge){
      this.name = myName; 
      this.age = myAge; 
   }
   say(){
      console.log(this.name, this.age);
   }
}
// 以下代码的含义: 告诉浏览器将来Student这个类需要继承于Person这个类
class Student extends Person{
   constructor(myName, myAge, myScore){
      super(myName, myAge);
      this.score = myScore;
   }
   study(){
       console.log("day day up");
   }
}
let stu = new Student("zs", 18, 98);
stu.say();
复制代码

4.小结

ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

Object.getPrototypeOf()

Object.getPrototypeOf方法可以用来从子类上获取父类。

在ES6中可以使用这个方法判断,一个类是否继承了另一个类。

console.log(Object.getPrototypeOf(Student) === Person); //true
复制代码

遍历器Iterator

1.什么是遍历器

遍历器是一个对象,该对象里面有一个next方法会返回给我们需要的数据

可遍历对象就是部署了[Symbol.iterator]属性的对象

2.[Symbol.iterator]

  • 可遍历对象都有一个叫做[Symbol.iterator]的属性
const arr = ['Ann','Bob','Charlie','Dolly'];
console.log(arr);
复制代码

  • [Symbol.iterator]的属性会返回一个函数
  • [Symbol.iterator]返回的函数执行之后会返回一个新对象Array Iterator {},该对象中又一个名称叫做next的方法
const iterator = arr[Symbol.iterator]();
console.log(iterator);
复制代码

  • next方法每次执行都会返回一个对象;该对象中存储了当前取出的数据和是否取完了的标记
let res = iterator.next();
console.log(res);
let res2 = iterator.next();
console.log(res2);
let res3 = iterator.next();
console.log(res3);
let res4 = iterator.next();
console.log(res4);
复制代码

  • 数据遍历完之后,随后再调用一直返回undefinedtrue
let res5 = iterator.next();
console.log(res5);
let res6 = iterator.next();
console.log(res6);
复制代码

3.一点区别

第三篇说到Array.prototype.entries();[Symbol.iterator]();返回的都是新的Array Iterator对象,二者等价

console.log(arr.entries());
console.log(arr[Symbol.iterator]());
复制代码

二者的返回的都是新的Array Iterator{}对象,细微的区别在于:

  • 使用.entries()返回的Array Iterator{}对象,再调用next()时,返回给我们的数据中的value是以数组(即包含索引)的形式

  • 使用[Symbol.iterator]();返回的Array Iterator{}对象,再调用next()时,value返回的就是那个值

4.其他方法返回Array Iterator{}

4.1 .keys方法

顾名思义,它的Array Iterator{}next()方法返回的是索引

let iterator = arr.keys();
console.log(iterator);
let res = iterator.next();
console.log(res);

let res2 = iterator.next();
console.log(res2);

let res3 = iterator.next();
console.log(res3);
复制代码

4.2 .values()方法 顾名思义,它的Array Iterator{}next()方法返回的是值

let iterator = arr.values();
console.log(iterator); //Array Iterator {}
let res = iterator.next();
console.log(res);

let res2 = iterator.next();
console.log(res2);

let res3 = iterator.next();
console.log(res3);
复制代码

5.实现一个遍历器

(抄的,这个代码太优雅了,必须分享)

Array.prototype.myIterator = function () {
    let i = 0;
    let items = this;
    return {
        next(){
            const done = i >= items.length;
            const value = done ? undefined : items[i++];
            return {
                value,
                done
            }
        }
    }
};
复制代码

本博客部分内容ES6模块化和ES6继承参考了这些:

caibaojian.com/es6/module.…

caibaojian.com/es6/class.h…