1. ES6模块是什么?

ES6在语言层级上出现了“模块”的概念。

javascript中一个文件就是一个模块,如果模块中使用了ES6的语法import或者export,

这个文件就是一个ES6模块。

另外,其实在html文件还可以通过设置script脚本的类型type="module"。这个脚本也是ES6模块。

2. ES6模块的特性

ES6模块区别于一般文件模块的特性如下:

1. 默认使用严格模式

ES6模块默认使用"use strict"。代码按照严格模式运行。

所以模块中,顶级this是undefined,不允许是window。

2. 模块级作用域

普通的模块一般通过IIFE(立即执行函数表达式)来生成模块作用域。

ES6模块默认模块文件的代码处于“模块作用域”,模块内的内容外部无法访问。

外部代码只能通过import访问模块export出的内容。

需要注意的是,如果在html文件中通过type="module"的script标签外部引入js,文件之间彼此也不能访问。

// html--按照es6Module引入的文件遵循ES6Module的标准
<script src="1.js" type="module"></script>
<script src="2.js" type="module"></script>
// 1.js
let user = 'lyra';
// 2.js
alert(user); // 报错,访问不到

3. 模块代码仅在第一次导入时解析执行

即不论模块代码被import多少次,都只在第一次被import的时候解析执行;

import语句是单例模式。

然后将第一次的导出结果供给所有需要导入的文件,即所有引用文件共享一个输出结果。

所以如果导出的是对象,如果一个文件修改了对象的属性,则所有文件的引入结果都会改变。

// obj.js文件export一个对象
export var obj={
name: 'nobody'
}
// 1.js引入--假设这个文件先执行
import {obj} from ./'obj.js';
console.log(obj.name); // "nobody"
obj.name = 'lyra';
// 2.js引入
import {obj} from './obj.js';
console.log(obj.name); // "lyra"--obj的属性被修改,证明使用的一个导出结果

4. <script type="module">默认异步加载(defer属性)

普通脚本是同步加载文件,文件加载完成后执行,会产生页面阻塞。如果想异步,可以加上defer,async属性。

其中defer属性是“等页面渲染完再执行”,如果含有多个使用defer属性的标签,按照书写顺序执行。

async是“下载完就执行”,如果含有多个使用async的标签,执行顺序随机,按照下载完成的先后顺序。

ES6模块默认使用defer属性。即不管是下面的哪种方式都是等页面渲染完再执行代码。

也可以使用async属性,会覆盖defer属性。

<script>标签作为模块,有两种方式:

1. 外部脚本导入

如果src是从其他域名获取文件,且设置了type="module",如果跨域需要外部服务器设置 Access-Control-Allow-Origin

// 1. 使用了type="module", 则引入的文件代码只执行一次
<script type="module" src="./outside.js"></script> 

2.内联脚本

<script type="module" async>
import {outsideObj} from './outside.js'
// 等import执行完成就执行后续代码,async不用等渲染完成
...
</script>

3. export命令

1. export命令特征

1. export命令可以输出变量,函数,类。

❗️输出函数和类的时候,后面不要加分号(;), 因为导出的是函数和类的声明。

2. export命令只能位于块级作用域的顶层,不能位于块级或者函数作用域中,否则无法进行静态优化。

2. export的写法

因为ES6模块的模块级作用域,外部代码无法访问内部的变量或者方法等。

export命令本身就是为了给外部提供一个可以访问模块内部变量或者方法的接口。

接口可以动态的获取模块内导出的变量或者方法。

1.逐个导出(不推荐)

export var a = 5;
export function test() {} // 没有分号
export class{} // 没有分号

2. 大括号导出(推荐)

var a = 5;
function test() {} // 没有分号
class example{} // 没有分号 export { a, test, example};
// ⚠️注意千万不要export m;相当于直接输出一个常量,和模块无关,不是一个外部访问模块内部的接口,不能通过接口从模块内部获取值
// 同理,也不能直接export test;因为模块代码只执行一次,相当于直接输入一个固定的函数,如果函数声明变化,输出的值不会随之变化

3. 默认导出

一个模块中只能使用一次默认导出,相当于导出一个变量名的default的值。

// 1.js
var a = 1;
export default a;
// 相当于导出一个变量名为default的变量,值是a;所以不能写成export default a =1;
// 但是可以写成export default 1;
//  对应的导入写法
import a from './1.js'; // 没有大括号

3. export重命名--as

可以使用as对导出的变量或者函数等进行重命名

var a = 5;
function test() {} // 没有分号
class example{} // 没有分号 export {
a as aValue,
test as testFunction,
example as exampleClass
};

可以通过as在大括号导出方法中导出default

var a = 5;
export { a as default } ;
// 相当于export default a;

4. import命令

1. import命令特征

1. import命令会执行所加载的模块

所以写代码时如果没有用到的模块,就不要引入,因为会执行。

// 1.js
function test(){console.log('hehe')}
test();
export var a = 1;
//2.js
import {a} from './1.js'; // 会打印,因为import执行模块代码,test执行

2. import一个模块,不管多少次,都只执行一次

3.❗️import是静态执行(编译阶段执行),所以后面不能跟变量或者表达式。

// 报错
import { 'f' + 'oo' } from 'my_module';

4. import除了导入默认的名称可以随机,其他的名称必须和export导出的接口名称一致

5. import 导入的接口是只读属性,特例是接口如果是对象,可以更改属性(不建议)

6. import只能位于模块的顶层,不能位于块级或者函数作用域,否则无法静态分析。

7. import命令可以变量提升

foo();//可以正常执行
import { foo } from 'xxxx'

8.from后面原则上只能跟绝对或者相对路径,但使用webpack解析,可以不是路径形式。

2. import 写法

1. 大括号引入普通接口

import { foo } from 'xxxx'

2.引入默认接口

import anyName from 'xxxx'; // 不用大括号;名字可以是任意名称
// 还可以使用as将默认接口转成其他名称
import { default as foo } from 'xxxx';

3.普通接口和默认接口同时

import defaultName, { x1, x2 } from 'xxx';
// 注意只能默认值在前

4.整体加载*

import * as obj from 'xxx';
// 可以通过obj获取所有的输出接口
obj.default; // 默认输出
obj.xxx; // 其他变量或方法

3.import重命名

1. 普通转

import {x  as y} from 'xxxx'

2.默认转

import { default as foo } from 'xxxx';

5. import和export复合写法

其实相当于在一个模块中转发另一个模块的接口。语法如下:

export xxxx from 'xxxx';

意义是可以将多个文件,用一个入口文件管理。

1. 全部转发的写法(*)

其实相当于模块的继承,转发的模块继承了被转发的模块。

// 2.js --- 相当于继承1.js的接口
export * from '1.js';// 注意,不会导出1.js中的默认接口,默认接口被忽略
export {default as foo} from '1.js'; // 转发默认接口
// 两句合在一起才完成输出1.js的接口

2. 非全部转发的写法({})

这种写法需要注意的是: 只是转发,test.js中是无法使用foo的

// test.js
export { foo } from 'xxxx';

3. 转发的同时定义别名

// 普通
export {foo as bar} from 'xxx';
// 默认转别名
export {default as bar } from 'xxx';
// 别名为默认
export {foo as default} from 'xxx';// 相当于输出默认值

4. 三种特殊形式复合写法的提案

export * as someIdentifier from "someModule";
export someIdentifier from "someModule";
export someIdentifier, { namedIdentifier } from "someModule";

6. 应用--跨模块常量或者方法

1.公共模块

实际开发中,项目中经常会有需要多个模块都使用的常量,或者通用的方法,组件等。

一般将常量和方法归类为公共模块,统一放到common文件夹下。

common下设constants文件夹放置常量文件,文件名可以根据类型区分,然后设一个index.js作为统一出口。

common下设utils文件夹放置公共方法文件,文件名可以根据功能区分,然后设一个index.js作为统一出口。

设置统一的出口,可以不考虑具体的文件。

2. 统一出口

// add.js
export function() {
//add函数
}
// sum.js
export function() {
//求和函数
}
//index.js
export {add } from './add.js';
export {sum } from './sum.js';

7. 动态导入--import(modulePath)函数

1.import()函数特性

1. import(模块位置)函数可以在任何地方(import和export只能在顶层)调用,返回一个Promise对象,解析结果是模块对象。

语法如下:

import('./' + fileName).then(moduleObj => {// 可以使用参数解构.then({exp1, exp2} => {
// 导入成功
}).then(err => {
// 导入失败
})

如果想要同时加载多个模块

Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
])
.then(([module1, module2, module3]) => {
···
});

2. import()函数是在运行时加载

3.import(path)中的参数可以是变量或者表达式。不同的运行结果加载不同的模块。

2. 使用场合

1. 动态加载

动态加载就是参数是变量或者表达式,可以在运行时根据不同的结果动态加载内容。

import(`./${filename}`).then().then()

2. 按需加载

如在监听函数的回调函数中使用,那么只有当触发监听函数才会执行。

button.addEventListener('click', event=> {
import('./').then(module=>{}).then(err=>{})//
})

3. 条件加载

if(...) {
import('./....').then().then()
}else {
import('./....').then().then()
}

4. 用在async函数中

async function test() {
let module = await import('./1.js');
}

8. ES6模块和CommonJS模块

1. 输出的内容形式不同

CommonJS输出的是值的拷贝,ES6输出的是值的引用(接口)。

1. CommonJS如果输出一个原始类型的值,模块内变化不会影响到这个值。

因为值会被缓存。

// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter, // 如果想要获取动态的值,可以使用取值函数
incCounter: incCounter,
};
// main.js
var mod = require('./lib'); console.log(mod.counter); //
mod.incCounter();
console.log(mod.counter); // 3,

2. ES6输出的是值的引用,运行时会到模块内实时取值。值不会被缓存。

变量总是绑定其所在的模块。

上面的示例如果用ES6实现,最后会打印出4。

2. 加载时期不同

CommonJS是运行时加载,ES6是编译时加载

3. 顶层this指向不同

CommonJS中顶层this执行当前模块;

ES6中顶层this指向undefined;

4.ES6模块加载CommonJS模块

CommonJS模块的输出形式如下:

var a = 5;
function test(){}
module.exports = {
a:a,
test: test
}

如果使用import加载CommonJS模块,会将输出解析成

var a = 5;
function test(){}
export default {
a:a,
test:test
}

注意: import不能使用{}加载CommonJS模块,如 "fs"模块

import {readFile} from 'fs'; //❌
// CommonJS模块运行时加载,import编辑时执行,取不到模块内容

5. CommonJS模块加载ES6模块

不能使用require方法加载,只能使用import()方法

6. 处理循环加载(彼此依赖)的方式不同

1. CommonJS的循环加载只会输出已经执行的部分

require的返回结果其实就是exports的值。

// a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');
// b.js
exports.done = false;
var a = require('./a.js'); // a.js加载该模块时,只执行到export.done=false,返回{done: false},继续执行
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');
// main.js
var a = require('./a.js');
var b = require('./b.js'); // 运行到这里,因为a.js中已经运行过require(b.js),所以不再执行,直接返回缓存的结果
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);

运行结果如下:

$ node main.js

在 b.js 之中,a.done = false
b.js 执行完毕
在 a.js 之中,b.done = true
a.js 执行完毕
在 main.js 之中, a.done=true, b.done=true

2. ES6会默认加载的接口的已经存在

// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar);
export let foo = 'foo';
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo);
export let bar = 'bar';

运行结果如下:

$ node node --experimental-modules a.mjs // 注意这是个试验性方案

b.mjs
ReferenceError: foo is not defined

如果想要有正确的运行结果

// a.js修改如下
import {bar} from './b';
console.log('a.mjs');
console.log(bar);
var foo = 'foo'; //变量提升,只提升变量声明,值不会
export {foo};
// b.js保持不变 //运行结果如下:
b.mjs
undefined
a.mjs
bar

9. ES6模块用于Node

node中本身有CommonJS,ES6和其不兼容,各自处理各自的加载。

1. 使用.mjs文件名

node要求ES6模块的文件使用.mjs后缀名,只能被import命令加载。

运行该类型文件语法如下:

$ node --experimental-modules my-app.mjs

node中import命令只能加载本地文件(file:协议)。语法如下

import XXXXX

1.如果模块不含路径,去node_modules下寻找模块

import 'foo/bar'; //前面不含路径,./ 或者/

2.如果是路径,根据路径寻找

import './foo/bar.js';

3.如果是路径,并且省略了后缀

import './foo/bar'

1)先按照后缀.mjs->.js->.json->.node寻找,否则继续

2)按照./foo/bar/package.json中的main字段,否则继续

3)按照./foo/bar/index.mjs->index.js->index.json->index.node,否则报错

2. ES6模块内不能使用CommonJS的特有变量

arguments,require,module,exports,__dirname,__filename

10. ES6模块的转码

为了实现兼容,需要将ES6转为ES5,一般都使用babel进行转码。

将ES6转为AMD或者CommonJS模块,还有两种方式:

1. es6-module-transpiler转码器

1)安装

npm install -g es6-module-transpiler

2)转化

compile-modules convert file1.js file2.js

2. SystemJS垫片库

1)引入

<script src="system.js"></script>

2)使用

<script>
System.import('app/es6-file').then(function(m) {
console.log(new m.q().es6); // hello
});
</script>

ES6模块的更多相关文章

  1. ES6模块import细节

    写在前面,目前浏览器对ES6的import支持还不是很好,需要用bable转译. ES6引入外部模块分两种情况: 1.导入外部的变量或函数等: import {firstName, lastName, ...

  2. ES6模块的import和export用法总结

    ES6之前以前出现了js模块加载的方案,最主要的是CommonJS和AMD规范.commonjs前者主要应用于服务器,实现同步加载,如nodejs.AMD规范应用于浏览器,如requirejs,为异步 ...

  3. commonjs模块和es6模块的区别

    commonjs模块与es6模块的区别 到目前为止,已经实习了3个月的时间了.最近在面试,在面试题里面有题目涉及到模块循环加载的知识.趁着这个机会,将commonjs模块与es6模块之间一些重要的的区 ...

  4. ES6模块之export和import详解

    ES6中的模块即使一个包含JS代码的文件,在这个模块中所有的变量都是对其他模块不可见的,除非我们导出它.ES6的模块系统大致分为导出(export)和导入(import)两个模块. 1.模块导出(ex ...

  5. Webpack4教程:第一部分,入口、输入和ES6模块

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文出处:https://wanago.io/2018/07/16/webpack-4-course-par ...

  6. ES6 模块机制

    ES6 实现了模块功能 将文件当作独立的模块,一个文件一个模块 每个模块可以导出自己的API成员,也可以导入其他模块或者模块中特定的API ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模 ...

  7. Es6模块语法笔记

    /** * Created by Administrator on 2017/4/15. */ /*---------------------export命令--------------------- ...

  8. ES6 模块与 CommonJS 模块的差异

    ES6 模块与 CommonJS 模块完全不同.它们有两个重大差异 CommonJS 输出是值的拷贝,即原来模块中的值改变不会影响已经加载的该值,ES6静态分析,动态引用,输出的是值的引用,值改变,引 ...

  9. commonjs模块和es6模块的区别?

    commonjs模块和es6模块最主要的区别:commonjs模块是拷贝,es6模块是引用,但理解这些,先得理解对象复制的问题,在回过头来理解这两模块的区别. 一.基本数据类型的模块 ./a1.js ...

  10. ES6模块的import和export用法

    ES6之前已经出现了js模块加载的方案,最主要的是CommonJS和AMD规范.commonjs主要应用于服务器,实现同步加载,如nodejs.AMD规范应用于浏览器,如requirejs,为异步加载 ...

随机推荐

  1. MEAN: AngularJS + NodeJS的REST API开发教程

    Node.JS https://www.jdon.com/idea/nodejs/web-app-with-angularjs-and-rest-api-with-node.html Mean是一个热 ...

  2. WUSOJ 1293: Fibonacci数列(Java)

    题目:

  3. 第五章:标准I/O库

    本章用于解析C语言标准I/O库,之所以在UNIX类系统的编程中会介绍C语言标准库,主要是因为UNIX和C之间具有密不可分的关系. 标准I/O库相比于操作系统的I/O库,具有更高的效率和可移植性,前者是 ...

  4. (转)从0移植uboot(六) _实现网络功能

    ref:https://www.cnblogs.com/xiaojiang1025/p/6500532.html 为uboot添加网卡功能可以让uboot通过tftp下载内核, 方便我们的开发, 对于 ...

  5. C# 延迟初始化 Lazy<T>

    概念:延时初始化重点是延时,用时加载,意思是对象在使用的时候创建而不是在实例化的的时候才创建.   延时加载主要应用的场景: 数据层(ADO.NET或Entity Framework等ORM,Java ...

  6. MVC5项目转.Net Core 2.2学习与填坑记录(1)

    流程都是自己摸索,错误地方随便指正... 老项目过于臃肿,并且所有请求都是提交到一个api中,这样当api挂掉的时候,基本所有的项目都瘫痪掉了. 在4月底的时候,下决心将项目用微服务进行重写,刚开始的 ...

  7. 十、es6之扩展运算符 三个点(...)

    对象的扩展运算符 对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中 let bar = { a: 1, b: 2 }; let baz = { ...bar }; / ...

  8. 微软发布云端基因服务:推动AI驱动的精准医疗

    微软发布云端基因服务:推动AI驱动的精准医疗 2018年03月07日 00:00:00 微软研究院AI头条 阅读数:117    版权声明:本文为博主原创文章,未经博主允许不得转载. https:// ...

  9. [学习笔记]pb_ds库

    前言 其实我很早开始就用pb_ds库了,用起来确实方便.但最近感觉还是对这个了解颇少,还是来补一下 话说有人会忘记头文件,其实这有个伎俩,找到电脑上的g++文件夹.Ubuntu应该在etc中,Wind ...

  10. C++ ifstream ofstream 注意事项

    很久没写C++,已经完全不会写了... 在使用ifstream读取一个二进制文件时,发现读取的内容和源文件不相同,导致数据解析失败,于是尝试把用ifstream读取的内容用ofstream写入另一个文 ...