ES6---Module

111 阅读7分钟

概述

在ES6之前,javascript一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小程序。

不过在此之前社区,社区制定了一些模块加载方案,最主要的是CommonJS和AMD两种,CommonJs用于服务器, AMD用于浏览器。

在ES6中,实现了模块功能,而且实现的也很简单,完全可以取代CommonJs和AMD,成为浏览器和服务器通用的模块解决方案。

设计思想:尽量静态化,使其在编译时就能确定模块的依赖关系,以及输出和输入变量。

CommonJs和AMD模块都只能在运行时确定这些东西。输入时必须查找对象的属性。

例如:
  // CommonJS模块
  let { stat, exists, readfile } = require('fs');

  // 等同于
  let _fs = require('fs');
  let stat = _fs.stat;
  let exists = _fs.exists;
  let readfile = _fs.readfile;
  
  这里实质是整体加载fs模块,生成一个对象,然后再从这个对象上读取这三个方法。
  这种称作运行时加载,因为只有运行时才能获取这三个方法。

ES6模块不是对象,而是通过export命令显示指定输入代码,再通过import命令输入。

例如:
	// ES6模块
	import { stat, exists, readFile } from 'fs';
    这里实质上是从fs模块加载3个方法,其他方法不加载。这种加载称为编译时加载。
    (即ES6编译时就完成模块加载)
    
    除了静态加载带来的好处,ES6还有以下好处
    1:不再需要UMD格式了。
    2:将来浏览器的新API就能通过模块格式提供,不再必须变成全局或者navigator对象属性
    3:不再需要对象作为命名空间(比如Math对象),这些功能都可以通过模块提供。

注意点:模块中自动采用严格模式

模块功能主要由两个命令构成 export和import

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

一个模块就是一个独立的文件,文件里面的变量外面无法获取,如果我们希望外部能够读取模块内部的某个变量,就必须使用export,输出该变量

以下几种方式都可以对外输出变量
	1:export var firstName = 'Michael'
    // 优先考虑下面这种方法,可以写在脚本尾部,导出什么变量,清晰可见
    2: var firstName = 'Michael' export {firstName}
    
除了输出变量,还可以输出函数或类
	export function fun(){}
    
输出时,还可以对变量进行重命名:
	// 这里通过as关键字,重命名了函数fun的对外接口为myFun
	export{ fun as myFun}
    
这里值得注意的是,export命令规定的是对外接口。必须与模块内部的变量建立一一对应关系。
而不是一个值。
	// 这种就不行,因为不能直接export一个值
	export 1
    // 这种也不行,虽然通过变量m,可是还是export一个值
    var m = 1; export m
    
    那么如何建立对应的意义关系呢?
    可以理解成,export后面的是一份名单,或者是这份名单上的某个成员,
    我们需要通过名单上面的名字,找到当前模块的对应的成员。
    (即export后面是成员的名字,而不是成员,外部需要通过这个名字,找到这个模块对应的成员)
    	// 写法一:名字是m
        export var m = 1;
        // 写法二:名字也是m
        var m = 1;
        export {m};
        // 写法三:名字还是m
        var n = 1;
        export {n as m};

export语句输出的接口,与其对应的值是动态绑定关系,可以获取模块内部实时的值

export可以出现在模块顶层的任何地方,因为处于块级作用域,就无法做到静态了

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

当我们有一个module对外export接口时,我们在这个模块的外部就可以通过import,读取或使用该接口对应的值或函数,类了。

   import {} from './profile.js'
   Tips:
   	1:import 接受一对大括号,里面指定要从其他模块导入的变量名,
    	变量名必须与被导入模块的接口名相同
    2:也可以在变量名后使用as进行重命名
    3:不允许在加载模块的脚本,改写接口,如果加载的是一个对象,则可以改写对象的属性
    4from后面指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略
    5import命令具有提升效果,会提升到整个模块的头部,首先执行
    6:不许使用表达式和变量,这些只有在运行时才能得到的结果的语法结构
    7import会执行所加载的模块,如果多次重复执行一句语句,也只会执行一次
    

export default命令--模块的默认输出

有时在我们不需要其他使用我们编写的模块的时候,可以通过输出默认值,给使用者使用

export defualt就是用来指定一个模块的默认输出的

例如:
	// export-default.js
    export default function () {
      console.log('foo');
    }
    // 这个时候其他模块加载该模块时,import命令可以为该匿名函数指定任意名字
    // 并且import命令后面,不使用大括号
    // import-default.js
    import customName from './export-default';
    customName(); // 'foo'
    
    // export default命令也可以写在非匿名函数之前
    function foo() {
      console.log('foo');
    }
	// 这个时候,foo这个接口在模块外部是无效,加载的时候,视作为匿名函数加载
    // 并且export default命令只能使用一次
    export default foo;

如上面的例子,foo这个接口在模块外是无效的,主要原因是:

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

default实际上就是我们对外接口,因此default后面不能跟变量声明语句

// 正确
export var a = 1
// 正确
var a = 1
export default a
//错误 此处的变量已经是default了,所以default后面应该跟着a对应的值,而不是a
export default var a = 1

如果想在一个import语句中,同时输出默认方法和其他接口,可以写成

import _,{each,forEach} from 'loash'

export default 也可以用来输出类

export 与 import的复合写法

在一个模块中,先输入,后输出同一个模块,import和export语句写在一起。

// 这里export和import语句结合在一起,写成一行。
// 不过,写成一行以后,foo,bar实际上没有导入到当前模块,只是相当于对外转发这两个接口
// 所以当前模块不能直接使用foo,bar
export { foo, bar } from 'my_module';

// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };

// 默认接口的写法如下。
export { default } from 'foo';

// ES2020 之前,有一种import语句,没有对应的复合写法。
import * as someIdentifier from "someModule";
// ES2020补上了这个写法。
export * as ns from "mod";
// 等同于
import * as ns from "mod";
export {ns};

模块的继承:

// 假设现在编写一个circleplus模块,继承circle模块
export * from 'circle.js'

export var e = 2.71828;
// 因为export * 会忽略circle模块的default方法,所以可以在这里编写输出方法
// 当然import * 也会忽略,所以使用import *导入方法时,需要再import一次,导入模块默认导出值
export default function(x){return Math.exp(x)}

跨模块常量

const声明的常量,只在当前代码块有效,如果想设置跨模块常量,可以按照下面的写法

// constants.js 模块
   export const A = 1;
   export const B = 3;
   export const C = 4;

import() --使ES6的模块动态化

在ES2020提案中,引入import()函数,支持动态加载模块。

// specifier,指定所要加载的模块的位置
// import也可以运用在async函数中
import(specifier)
import()模块返回一个Promise对象
因此,我们可以在then方法的参数中,获取这个模块对应的对象
与Noderequire不同的是,import()是异步加载,Noderequire是同步加载