ES6的模块
ES6-Import
Module模块的特性
ES6的module模块,可以把复杂的JS拆分为不同的模块,实现模块化封装。
一个模块就是一个独立的文件,一个脚本就是一个模块。模块可以相互引用,
但必须通过exprot
、import
关键字来申明导出的接口、引入的接口。
因为一个模块(文件)内部是一个独立的作用域,与外界无关。
-
静态编译:模块的
exprot
、import
都是在编译时执行,会优先执行。 属于静态加载,不是运行态的。
-
严格模式:模块始终是严格模式
use strict
,无需申明。- 变量必须先申明再使用,没了变量提升。
-
this
为undefined
,模块的顶级this
指向undefined
, 而不是我们熟悉的全局window
对象。
-
模块作用域,模块的作用域只在模块内,外面无法访问。
包括一个
<script type="module">
内部JS代码块也是如此。-
只有通过
exprot
、import
申明的才可以外部访问,这有很好的保护作用。
-
只有通过
-
延迟执行:模块脚本是延迟的,效果类似
defer
,同其他资源一样并行加载。 等HTML解析完成才会(顺序)执行,所以需注意顺序。支持async
, 异步脚本准备好后会立即执行。
在HTML中使用模块,需要<script type="module">
来申明,
告诉浏览器这里要被当做模块来对待。包括内联代码、外部引用。
<script type="module" src="./foo.js"></script> <script type="module"> console.log(1) // 比下面的晚输出 </script> <script async type="module"> console.log(2)// 比上面的先输出 </script>
例子:study/javascript/es6-module/
export导出
export申明导出可以给外部访问的接口,可以是任意类型的变量、方法、类, 支持任意多个。
export const max = 100;。
-
export
的本质是通过接口名与模块内部变量之间,建立了一一对应的关系。 -
export
可以在任何位置使用,一般推荐在最后的地方统一导出,比较清晰。 注意由于模块是静态编译执行,只能放到顶级位置,不能在块级作用域里面。 -
as
重命名,导出时给变量取一个艺名。export { sayHello as Hi }
; -
导入、导出合并使用,导出另一模块导入的接口,做一个中间商,
实现了类似继承的效果。
export ... from '*.js'
// export let userInfo = { name: 'sam', age: 100, } let book = { id: '001', price: 100, } function sayHi(name) { console.log('Hi!', name); } export const MAX = 100; // 统一导出,as重命名 export { book, sayHi as sayHello } // 导入、导出合并使用,把导入的转手导出 export { max, book } from './js/m-user.js' export * from './js/m-user.js'
import导入
import从一个模块加载(导入)变量、函数、类:
import { max } from './js/m-user.js'
-
{接收}
:用一个花括号包裹,接收的变量名称必须和导出时一致。 -
可以用
as
重命名:import { sayHello as Hi }
-
url
文件:导入的文件url支持相对路径、绝对路径。也可以只是模块文件名, 这时需要配置模块查找方式了。- Singleton模式:import模块的代码只会执行一次。 同一个url文件只会第一次导入时执行代码, 后续任何地方import都不会执行模块代码了。 也就是说,import语句是Singleton模式的。
- 只读-共享:模块导入的接口的是只读的,不能修改。 当然引用对象的属性值是可以修改的,不建议这么干,注意模块是共享的, 导出的是一个引用,修改后其他方也会生效。
- import具有提升效果,可以先消费,后导入,因为import是编译阶段执行的。
-
*
一次性整体加载模块中所有接口,到一个as
指定的变量上,此时没有{花括号}
。 建议按需导出,不用*
,这样有利于构建工具的优化,剔除没用到的代码。
<script type="module" src="../js/f2.js"></script> <script type="module"> import { MAX } from './js/m-user.js'; import { book, sayHello as sayHi } from './js/m-user.js'; // 整体加载,`*`一次全部导入到一个引用上 import * as uall from './js/m-user.js'; console.log(MAX, book); sayHi(); </script>
export default
import加载的时候必须指定模块中的名字,有一种不需要指定名字的方法——设置默认导出的变量:
export default MAX;
- 导入时可以随意命名,此时不需要花括号。
-
default
只能用一次。 -
本质上就是输出一个叫做
default
的接口,把一个变量赋值给default
。 所以也可以显示的使用default
。
// 设置默认导出接口 const MAX = 1000; export default MAX; // 只能用一次,下面这一行会报错 export { sayHi as default }
// 导出默认接口 import M from './js/m-user.js'; import {default as MX} from './js/m-user.js';
import(url)
函数
import()
用于运行时动态加载一个模块,也支持加载非模块的脚本,可以用在任何地方。
他是异步的,返回一个Promise对象,可以接then()
,或者用await
命令。
功能类似Node.js中的require()
,区别就是require()
是同步的。
<!-- 动态加载不需要设置type="module" --> <script> import('./js/m-user.js').then(m => { //获得module,所有导出的接口都在m上 console.log(m.book) m.sayHello("baby"); }); async function dosth() { let obj = await import('./js/m-user.js'); let { book } = await import('./js/m-user.js'); console.log("book:", obj.book, book); } dosth(); </script>
require
和import
的区别
require是 CommonJS 模块的语法,CommonJS是Node.js 的模块机制,简称CJS。 CommonJS 与ES6 的模块并不兼容,两者的作用类似,但用法还是有些差异的。
比较 | CommonJS | ES6 模块 |
---|---|---|
运行环境 | Node.js | 浏览器 |
导出语法 |
module.exports = { obj }
|
export { obj }
|
加载语法 |
require() :运行时动态加载
|
import :编译时静态引入
|
import(url) :运行时动态加载
|
||
异步加载? | 同步加载 | 异步加载 |
输出的什么? | 导入的是一个值的拷贝,独享 | 导入的是一个引用,有福同享 |
常见错误
模块外异常
SyntaxError: Cannot use import statement outside a module
import
要在模块内使用,如果不在模块内会报异常。
分两种场景,一种是在浏览器内,一个是在nodejs应用中。
浏览器中
js里使用了import
:
import string from './css.js'
使用时就要在模块里,不然会报错:
<!-- SyntaxError: Cannot use import statement outside a module 错误, 因为不在模块内 --> <script src="main.js"></script>
改成指定为模块就可以了:
<script type="module" src="main.js"></script>
nodejs中
因为NodeJS环境默认使用的是CommonJS规范,要用require
语句进行导入。
与ES6规范中的import
不兼容。
例:
import * as fs from 'fs';
这时候如果使用node命令直接执行会报错:
$ node my-function.js D:\workspace\test-app\src\my-function.ts:2 import * as fs from 'fs'; ^^^^^^ SyntaxError: Cannot use import statement outside a module at Object.compileFunction (node:vm:352:18) at wrapSafe (node:internal/modules/cjs/loader:1033:15) at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10) at Module.load (node:internal/modules/cjs/loader:981:32) at Function.Module._load (node:internal/modules/cjs/loader:822:12) at Module.require (node:internal/modules/cjs/loader:1005:19) at require (node:internal/modules/cjs/helpers:102:18) at Array.forEach (<anonymous>)
解决方法:
-
一种方法:在
package.json
中指定type
为module
,然后用npm start
命令执行。 但是这样混用CommonJS和ES6可能会有问题。 -
另一个方法:使用
.mjs
扩展名。因为NodeJS版本v13.2开始已经可以支持ES6模块支持。 但要用.mjs
扩展名。
第一种方法的例子:
{ "name":"test-app", "scripts": { "start": "node src/my-function.js" }, "type":"module", "dependencies": { "express":"~2.6.1" } }
异常ERR_UNKNOW_FILE_EXTENSION
但是这样混用CommonJS和ES6可能会有问题,可能会遇到ERR_UNKNOW_FILE_EXTENSION
。
这样可能要改回CommonJS中的require
方式。
异常ERR_REQUIRE_ESM
还有一种可能是你引入的外部库版本比较新,
只支持ES6的import
不支持CommonJS的require
。
这种情况下可以试试把引用的外部库版本降低到旧一些的版本。