在测试两个esmodule文件时候,需要将文件后缀改为mjs,如果就想使用js后缀名,需要确保node在V12之后,然后在 package.json 中添加 type 字段为 module,将默认模块系统修改为ES module,此时就不需要修改文件扩展名.mjs了
可以这样理解esmodule与commonjs区别:commonjs适用于服务端,esmodule适用于前端浏览器。因此无法在html内使用require导入commonjs的模块(浏览器的console会报错require is undefined),但可以在html内通过import...from方式导入esmodule模块。
一、ES Module基本特性
从代码中去理解4个特性:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ES Module - 模块的特性</title>
</head>
<body>
<!-- 通过给 script 添加 type = module 的属性,就可以以 ES Module 的标准执行其中的 JS 代码了 -->
<script type="module">
console.log('this is es module') // this is es module
</script>
<!-- 特性1. ESM 自动采用严格模式 -->
<script type="module">
console.log(this) //undefined。因为每个semodule都是私有作用域,this无法指向全局对象
</script>
<script>
console.log(this) //window
</script>
<!-- 特性2. 每个 ES Module 都是运行在单独的私有作用域中 -->
<script type="module">
var foo = 100
console.log(foo) //100
</script>
<script type="module">
console.log(foo) //Uncaught ReferenceError:foo is not undefined
</script>
<!-- 特性3. ESM 是通过 CORS 的方式请求外部 JS 模块的 -->
<script type="module" src="https://libs.baidu.com/jquery/2.0.0/jquery.min.js">
//跨域请求错误:Access to script at 'https://libs.baidu.com/jquery/2.0.0/jquery.min.js' from origin 'http://localhost:5000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
//请求地址失败:GET https://libs.baidu.com/jquery/2.0.0/jquery.min.js net::ERR_FAILED
</script>
<script type="module" src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js">
//换了一个支持CORS请求的链接就不会报错了
</script>
<!-- 特性4. ESM 的 script 标签会延迟执行脚本 -->
<script defer src="demo.js"></script>
<p>需要显示的内容</p> <!-- 被延迟执行的脚本内容:先alert再加载p标签下内容 -->
</body>
</html>
二、ES Modules导出
1、esmodule导入与导出
导出:module.js
var name = 'foo module'
function hello () {
console.log('hello')
}
class Person {}
export { name, hello, Person }
导入:app.js
import { name, hello, Person } from './module.js'
console.log(name, hello, Person)
需要注意的是,离线打开浏览器是会报错的:
解决方案是打开服务器:
2、module导出时给变量名重命名
导出:module.js
var name = 'foo module'
export {
// name as default,
name as fooHello
}
导入:app.js,导入时就得根据导出的名字进行导入
import { fooHello } from './module.js';
console.log(fooHello);
3、给命名设置default关键词
导出:module.js
var name = 'foo module'
export {
name as default,
}
导入,app.js。导入时候需要用default as 【你想取的名字】
import { default as name2 } from './module.js'
console.log(name2)
三、ES Modules导入导出的注意事项
1、对象后面的花括号和export后的花括号是不同的概念:一个是【对象】,一个是导出【引用】。
比如,这两个花括号是不一样的:
var obj = { name, age } // 这个花括号是个对象
export { name, age } // 这个花括号是个引用
错误:
export name // 错误的导出用法
export 'foo' // 同样错误的导出用法
可以这样导出变量名
export default name
2、导出导入的是引用的内存地址
导出:module.js:
var name = 'jack'
var age = 18
export { name, age }
setTimeout(function () {
name = 'ben'
}, 1000)
导入:app.js:
import { name, age } from './module.js'
console.log(name, age)
// 导入成员并不是复制一个副本,
// 而是直接导入模块成员的引用地址,
// 也就是说 import 得到的变量与 export 导入的变量在内存中是同一块空间。
// 一旦模块中成员修改了,这里也会同时修改,
setTimeout(function () {
console.log(name, age)
}, 1500)
3、导入的变量只读不能修改
import { name, age } from './module.js'
name=1 //Uncaught TypeError: Assignment to constant variable.=
再看一个
const a = 1;
a =2 //报错:Uncaught TypeError: Assignment to constant variable.
观察:从模块导出的变量,修改后报错的信息是与修改常量的报错信息是一致的,也就是说导入的变量的类似用了const声明。
四、ES Module更多认识
1、导入方法常识
导出:module.js
var name = 'jack'
var age = 18
export { name, age }
console.log('module action')
export default 'default export'
导入:app.js
// 一、文件导入的方法
import { name } from './module' // 1.不能省略.js,这点跟ComminJs是不一样的
import { name } from './module.js'
console.log(name)
import { lowercase } from './utils' // 2.不能只导入目录就可以找到index.js,这跟CommonJS是不一样的
import { lowercase } from './utils/index.js'
console.log(lowercase('HHH'))
import { name } from 'module.js' //3.不能以字母开头,因为ES Module就以为在加载第三方模块,而不是本地路径模块了,这点和CommonJS是相同的
import { name } from './module.js'
import { name } from '/04-import/module.js' // 4.可以用绝对路线省略/前的• → 根目录就是当前index.html目录
import { name } from 'http://localhost:3000/04-import/module.js'
console.log(name)
// 二、仅仅导入模块
import {} from './module.js' // 两种导入方式是相同的,仅仅导入模块有啥作用呢?又不能使用里面的变量,没啥用,实用性为0。
import './module.js'
// 三、把模块下所有的export属性都导出来,可以用 *
import * as mod from './module.js' //
console.log(mod)
// 四、2个不能 → 解决方案
var modulePath = './module.js' //1.不能将导入模块赋值给一个变量
import { name } from modulePath // 这才是正确导入写法
console.log(name)
if (true) {
import { name } from './module.js' // 2.不能嵌套在一个if、while语句中,只能在最顶层就导出模块
}
import('./module.js').then(function (module) { //3.解决方案:全局动态的导入方案,通过then拿到这个模块。
console.log(module)
})
// 五、提取默认成员
import { name, age, default as title } from './module.js' //1.复杂:默认成员导出方式
import abc, { name, age } from './module.js' //2.简写:逗号左边用来提取默认成员,逗号右边用来提取具名成员。
console.log(name, age, abc)
2、要是多个导入模块怎么办?
场景:如果模块多了怎么办?还这样一个个导入吗?
解决:把他们都导入一个模块require,js里,然后再从index.js导入这个汇总所有模块的模块。比如:
import { Button } from './button.js'
import { Avatar } from './avatar.js'
export { Button, Avatar }
如果存在有误地方可以帮忙指正哦。