ES6 19 Module

1.概述

ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

Module的好处:

  • 不再需要UMD模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。
  • 将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性。
  • 不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。

2.严格模式

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。

3.export命令

模块功能主要由两个命令构成:

  • export命令:用于规定模块的对外接口
  • import命令:用于输入其他模块提供的功能。

一个模块就是一个独立的文件。
该文件内部的所有变量,外部无法获取。

如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。

下面是一个 JS 文件,里面使用export命令输出变量。

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

上面代码是profile.js文件,保存了用户信息。ES6 将其视为一个模块,里面用export命令对外部输出了三个变量。

export的写法,除了像上面这样,还有另外一种。

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export { firstName, lastName, year };//大括号中指定要输出的变量

它与前一种写法(直接放置在 var 语句前)是等价的

应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。

export命令还可以输出函数或类(class)。

export function multiply(x, y) {
  return x * y;
};

export命令中,可以使用as关键字重命名。

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

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

上面代码使用as关键字,重命名了函数v1v2的对外接口。重命名后,v2可以用不同的名字输出两次。换句话说,就是使用as关键字重命名不会覆盖之前使用as关键字命名的元素,而是会让元素有两个不同的名字。

注意

  • export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
  • export命令是向外抛的值的 引用 ,所以说不能省略大括号,否则会认为你是抛出的值,而不是引用而报错
  • export语句输出的接口,与其对应的值是 动态绑定 关系,即通过该接口,可以取到模块内部实时的值。
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);

上面代码输出变量foo,值为bar,500 毫秒之后值变成baz

  • export命令,只要处于模块顶层就可以。如果处于块级作用域内,就会报错。import 命令也是如此
    这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。

4.improt命令

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

// main.js
import { firstName, lastName, year } from './profile.js';
//使用 import 命令加载 profile.js文件

function setName(element) {
  element.textContent = firstName + ' ' + lastName;// 引入后可直接当做变量使用
}

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

  • 同样,import 可以使用as关键字命名
import { lastName as surname } from './profile.js';
  • import命令具有提升效果,会提升到整个模块的头部,首先执行。(import命令在编译阶段执行)
  • import命令不允许在加载模块的脚本里面,改写接口。因为import输入的变量都是只读的,它的本质是输入接口。
import {a} from './xxx.js'

a = {}; // Syntax Error : 'a' is read-only; 

上面代码中,脚本加载了变量a,对其重新赋值就会报错。因为a是一个只读的接口。
但是,如果a是一个对象,改写a的属性是允许的。

import {a} from './xxx.js'//a 为一个对象

a.foo = 'hello'; // 合法操作

上面代码中,a的属性可以成功改写,并且其他模块也可以读到改写后的值。
建议凡是输入的变量,都当作完全只读,不要轻易改变它的属性。因为这种写法很难查错。

import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。

import {myMethod} from 'util';

上面代码中,util是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块。

  • 由于import是静态执行,所以命令中不能使用表达式和变量
  • 最后,import语句会执行所加载的模块,因此可以有下面的写法。
import 'lodash'; //仅仅执行 lodash 模块,但是不输入任何值
import 'lodash'; //重复执行同样的模块,也只会执行一次
import { foo } from 'my_module';
import { bar } from 'my_module';

// 等同于
import { foo, bar } from 'my_module'; // 对应的是同一个my_module实例。

5.模块的整体加载

除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值(通过export抛出的值)都加载在这个对象上面。

import * as circle from './circle';

console.log('圆面积:' + circle.area(4)); 
console.log('圆周长:' + circle.circumference(14));
//因为所有输出值都加载在这个用 * 号指定对象上面,所以可以通过对象的.(点)方法调用

上面的 areacircumference 是模块输出的方法,使用整体加载的方法,使circle对象接收

6.export default 命令

export default命令,可以为模块指定默认输出。

从前面的例子可以看出,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令

语法:

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

 import-default.js
import customName from './export-default'; //指定名称
customName(); // 'foo'

export-default.js是一个模块文件,使用export default默认输出一个函数
其他的模块加载该模块时,import还可以为它指定任意名字(不用带大括号),因为export default在模块中只能有一个。

  • export default命令用在非匿名函数前,也是可以的。
  • export default也可以用来输出类。
export default class { ... }
  • export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。
// modules.js
function add(x, y) {
  return x * y;
}
export {add as default};
// 等同于
// export default add;

// app.js
import { default as foo } from 'modules';
// 等同于
// import foo from 'modules';

因此,它后面不能跟变量声明语句。

// 正确
export var a = 1;

// 正确
var a = 1;
export default a;

// 错误
export default var a = 1; //语法会理解为 export {var a = 1 as default},导致报错

又因为export default命令的本质是将后面的值,赋给default变量,所以可以直接将一个值写在export default之后。

// 正确
export default 42; //指定了对外的接口,为 default

// 报错
export 42;//未指定对外的接口

export 与 export default 的区别

  1. exportexport default均可用于导出常量、函数、文件、模块、类等
  2. 在一个文件或模块中,exportimport可以有多个,export default仅有一个
  3. 使用export default导出,再导入时不需要 { }

7.export 与 import 的复合写法

如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。

export { foo, bar } from 'my_module';

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

这样写相当于转发了这个接口,导致当前的模块不能使用foobar

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

//具名接口改为默认接口的写法
export { es6 as default } from './someModule';

//默认接口改名为具名接口
export { default as es6 } from './someModule';

// 整体输出
export * from 'my_module';

上面是各个方式的写法

8.模块的继承

模块之间可以继承。

// circleplus.js

export * from 'circle'; // 表示再输出 circle 中的所有属性和方法,不包括 default 方法,因为export *命令会忽略 default 方法
export var e = 2.71828182846; 
export default function(x) {
  return Math.exp(x);
}

这时,也可以将circle的属性或方法,改名后再输出。

// circleplus.js

export { area as circleArea } from 'circle'; 
//可简单理解为
import {area} from 'circle'
export {area as circleArea};	

加载上面模块的写法如下。

// main.js

import * as math from 'circleplus'; //将模块输出的整体改名为 math
import exp from 'circleplus'; //此处 exp 表示,将 circleplus 模块的默认方法(default)加载为 exp 方法
console.log(exp(math.e));

9.跨模块常量

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

//text1.js
import * as constants from './constants'
console.log(constants.A); // 1
console.log(constants.B); // 3

//text2.js
imports {A,B}  from './constants'
console.log(A); // 1
console.log(B); // 3

如果要使用的常量非常多,可以建一个专门的constants目录,将各种常量写在不同的文件里面,保存在该目录下。

// constants/db.js
export const db = {
  url: 'http://my.couchdbserver.local:5984',
  admin_username: 'admin',
  admin_password: 'admin password'
};

// constants/user.js
export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];

然后,将这些文件输出的常量,合并在index.js里面。使用的时候,直接加载index.js就可以了。

// constants/index.js
export {db} from './db';
export {users} from './users';

// script.js
import {db, users} from './constants/index';

10.import()

import()函数是一个提案,可实现动态加载

11.浏览器的加载方式

HTML 网页中,浏览器通过<script>标签加载 JavaScript 脚本。 默认情况下,JavaScript
脚本为同步加载,遇到脚本时,等到执行完脚本,再继续向下渲染。
如果是外部脚本,还必须加入脚本下载的时间。

浏览器允许脚本异步加载,下面就是两种异步加载的语法。

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

上面代码中,<script>标签打开deferasync属性,脚本就会异步加载。
渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。

deferasync的区别:

defer

  • 等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行
  • 若有多个defer脚本,会按照它们在页面出现的顺序加载。

async

  • async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
  • 若有多个async脚本,不能保证它的加载顺序。

简单来说,defer是“渲染完再执行”(下载完毕,等待渲染),async是“下载完就执行”。

12.ES6模块加载规则

浏览器加载 ES6 模块,也使用<script>标签,但是要加入type="module"属性。让浏览器知道这是一个 ES6 模块。

带有type="module"<script>,都是异步加载,等同于打开了<script>标签的defer属性。

<script type="module" src="./foo.js"></script>
<!-- 等同于 -->
<script type="module" src="./foo.js" defer></script>

也可以自己输入 async属性,加载后就可以执行(会中断渲染,执行完成再恢复渲染)

<script type="module" src="./foo.js" async></script>

ES6 模块也允许内嵌在网页中,语法行为与加载外部脚本完全一致。

<script type="module">
  import utils from "./utils.js";

  // other code
</script>

对于外部的模块脚本(上例是foo.js),有几点需要注意:

  • 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
  • 模块脚本自动采用严格模式,不管有没有声明use strict
  • 模块之中,可以使用import命令加载其他模块(.js后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用export命令输出对外接口。
  • 模块之中,顶层的this关键字返回undefined,而不是指向window
  • 同一个模块如果加载多次,将只执行一次。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值