ES6 module

Posted Jmytea

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ES6 module相关的知识,希望对你有一定的参考价值。

概述

历史上,javascript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。

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

设置浏览器启用es6语法功能:(Chrome 61+,Firefox 54+)

1.在浏览器的url中输入:chrome://flags/

2.搜索 JavaScript 关键字

3.设置选项 Experimental JavaScript 为 Enable

4.重启浏览器后生效

script 标签里面要加 type="module", 这样浏览器才会把相关的代码当作ES6的module 来对待

不能写“裸”路径,即使是同一层级下面的文件,也要加上 './name.js'

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

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

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

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

export 关键字

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

以下代码放在 module1.js 文件中

// 不推荐

export var userName = 'xiaoming';

export var userSex = '男';

export var userAge = 27;

// 推荐

var userName = 'xiaoming';

var userSex = '男';

var userAge = 27;

export userName, userSex, userAge ;

export命令除了输出变量,还可以输出对象、函数或类(class)。

export function multiply(x, y)

  return x * y;

上面代码对外输出一个函数multiply。

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

function f1() ...

function f2() ...

export

  f1 as test1,day23-2

  f2 as test2,

  f2 as test3

;

上面代码使用as关键字,重命名了函数f1和f2的对外接口。重命名后,f2可以用不同的名字输出两次。

错误的写法:

// 报错

export 1;

// 报错

var m = 1;

export m;

// 报错

function f()

export f;

正确的写法:

// 正确

export var m = 1;

// 正确

var m = 1;

export m;

// 正确

var n = 1;

export n as m;

// 正确

export function f() ;

// 正确

function f()

export f;

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

export var abc = '123';

setTimeout(() => abc = '456', 500);

上面代码输出变量abc,值为'123',500 毫秒之后变成'456'。

import 关键字

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

以下代码放在 test1.js 文件中

import userName, userSex, userAge from './module1.js';

console.log(userName);

console.log(userSex);

console.log(userAge);

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

import userName as xm from './module1.js';

import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。

import a from './xxx.js'

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

如果a是一个对象,改写a的属性是允许的。

import a from './xxx.js'

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

注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。

show();

import show from './module1.js';

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

import * as obj from './module1.js';

console.log(obj.userName);

console.log(obj.userSex);

console.log(obj.userAge);

export default 命令

使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。

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

下面比较一下默认输出和正常输出:

// 第一组

export default function test()  // 输出

  // ...

import test from './test.js'; // 输入

// 第二组

export function test()  // 输出

  // ...

;

import  test  from './test.js'; // 输入

第一组是使用export default时,对应的import语句不需要使用大括号;

第二组是不使用export default时,对应的import语句需要使用大括号。

一个模块只能有一个默认输出,因此export default命令只能使用一次。

因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。

// 正确

export var a = 1;

// 正确

var a = 1;

export default a;

// 错误

export default var a = 1;

// 正确

export default 42;

// 报错

export 42;

跨模块常量

如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。

// constants.js 模块

export const A = 1;

export const B = 3;

export const C = 4;

// test1.js 模块

import * as constants from './constants';

console.log(constants.A); // 1

console.log(constants.B); // 3

// test2.js 模块

import A, B from './constants';

console.log(A); // 1

console.log(B); // 3

浏览器加载规则

默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到<script>标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。

如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。

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

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

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

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

defer与async的区别是:

defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;

async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。

一句话,defer是“渲染完再执行”,async是“下载完就执行”。

如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

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

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

上面代码在网页中插入一个模块foo.js,由于type属性设为module,所以浏览器知道这是一个 ES6 模块。

浏览器对于带有type="module"的<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。

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

<!-- 等同于 -->

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

如果网页有多个<script type="module">,它们会按照在页面出现的顺序依次执行。

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

<script type="module">

    import utils from "./utils.js";

    // other code

    // ...

</script>

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

代码是在模块作用域之中运行,而不是在全局作用域运行。

模块内部的顶层变量,外部不可见。

模块脚本自动采用严格模式,不管有没有声明use strict。

模块之中,可以使用import命令加载其他模块,也可以使用export命令输出对外接口。

模块之中,顶层的this关键字返回undefined,而不是指向window。

同一个模块如果加载多次,将只执行一次。

以上是关于ES6 module的主要内容,如果未能解决你的问题,请参考以下文章

ES6 Module

ES6 module模块

ES6入门十二:Module(模块化)

ES6中的Module与Interator

ES6新特性6:模块Module

ES6语法总结-Module的语法