2.4.ES6 Module
ES Module和CommonJS的模块化有一些不同之处:
- 一方面它使用了import和export关键字;
- 另一方面它采用编译期的静态分析,并且也加入了动态引用的方式(import()函数)
ES Module模块采用export和import关键字来实现模块化:
- export负责将模块内的内容导出;
- import负责从其他模块导入内容;
了解:采用ES Module将自动采用严格模式:use strict
2.4.1.使用的时候可能遇到的错误
使用ES6 Module时要在script标签里添加type=“module”
来开启模块化
当打开浏览器的时候 , 如果时使用默认浏览器打开会报以下错误
这个在MDN上面有给出解释:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules
你需要注意本地测试 — 如果你通过本地加载Html 文件 (比如一个 file:// 路径的文件), 你将会遇到 CORS 错误,因 为Javascript 模块安全性需要。
你需要通过一个服务器来测试。VSCode中有一个插件:Live Server
2.4.2.export关键字
export关键字将一个模块中的变量、函数、类等导出;
导出的方式有三种:
- 在声明的前面直接加export关键字
- 将所有需要导出的标识符放在
export{}
中
- 注意:这里的 {}里面不是ES6的对象字面量的增强写法,{}也不是表示一个对象的;
- 所以: export {name: name},是错误的写法
- 导出时给标识符起别名
// 第一种导出的方式
// export const name = 'xxx';
// export const age = 18;
// export function say (value) {
// console.log('value');
// };
const name = 'xxx';
const age = 18;
function say (value) {
console.log('value');
}
// 第二种导出的方式 , 注意导出的不是es6中对象的方式 , 这里导出的是
export {
name,
age,
say
};
// 第三种 , 如果使用了起别名的方式,那么,在使用import引入的时候,必须使用别名,因为标识符的名字已经改变了
//此时导入的使用应该是 import {fname , fage , fsay} from './module/bar.js'
export {
name as fname,
age as fage,
say as fsay
};
2.4.3.import关键字
import关键字负责从另外一个模块中导入内容
导入内容的方式也有多种
- import {标识符列表} from '模块';
- :导入时给标识符起别名
- 通过 * 将模块功能放到一个模块功能对象(a module object)上
// 第一种方式
// import { name, age, say } from './module/bar.js';
// console.log(name);
// console.log(age);
// say('hello world');
// 第二种方式
// import { name as fname, age as fage, say as fsay } from './module/bar.js';
// console.log(fname);
// console.log(fage);
// fsay('hello world');
// 第三种方式
import * as info from './module/bar.js'
console.log(info.name);
console.log(info.age);
info.say('hello world');import { name, age, say } from './module/bar.js'
2.4.4.default用法
前面我们学习的导出功能都是有名字的导出(named exports):
- 在导出export时指定了名字;
- 在导入import时需要知道具体的名字;
还有一种导出叫做默认导出(default export)
- 默认导出export时可以不需要指定名字;
- 在导入时不需要使用 {},并且可以自己来指定名字;
- 它也方便我们和现有的CommonJS等规范相互操作;
在一个模块中,只能有一个默认导出(default export);
2.4.4.import()函数
当时用按需加载的时候,如果想要当满足某种条件的时候,然后再加载一个模块,如:
let flag = true;
//当flag为true的时候,才能加载这个模块
if (flag) {
import { name, age } from './module/bar.js';
}
像这样做是错误的,因为import { name, age } from './module/bar.js';
是帮助我们在解析阶段确定依赖的,而现在,直接放在了执行阶段,也就是前面的代码执了,然后再来加载这个模块,但是执行的时候并不认识这种语法。
补充:
为什么commonJS中可以在执行阶段使用require()?
因为require()是一个函数
但是es6也为我们提供了import()函数,import函数是个异步函数,
使用方法
let flag = true;
//当flag为true的时候,才能加载这个模块
if (flag) {
import('./module/bar.js').then(res => {
console.log(res.name, res.age);
res.say('hello world');
}).catch(err => {
console.log(err);
})
}
2.4.5.ES Module加载过程
ES Module加载js文件的过程是编译(解析)时加载的,并且是异步的:
- 编译时(解析)时加载,意味着import不能和运行时相关的内容放在一起使用:
- 比如from后面的路径需要动态获取;
- 比如不能将import放到if等语句的代码块中;
- 所以我们有时候也称ES Module是静态解析的,而不是动态或者运行时解析的;
异步的意味着:JS引擎在遇到import时会去获取这个js文件,但是这个获取的过程是异步的,并不会阻塞主线程继 续执行;
- 也就是说设置了 type=module 的代码,相当于在script标签上也加上了 async 属性
- 如果我们后面有普通的script标签以及对应的代码,那么ES Module对应的js文件和代码不会阻塞它们的执行;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="./index.js" type="module"></script> <script> console.log('加载了'); </script> </body> </html>
ES Module通过export导出的是变量本身的引用:
export在导出一个变量时,js引擎会解析这个语法,并且创建模块环境记录(module environment record);
- 模块环境记录会和变量进行 绑定(binding),并且这个绑定是实时的;
- 而在导入的地方,我们是可以实时的获取到绑定的最新值的;
所以,如果在导出的模块中修改了变化,那么导入的地方可以实时获取最新的变量;
注意:在导入的地方不可以修改变量,因为它只是被绑定到了这个变量上(其实是一个常量)
思考:如果bar.js中导出的是一个对象,那么main.js中是否可以修改对象中的属性呢?
答案是可以的,因为他们指向同一块内存空间;
如const name='xxx'; 不能修改name
但是const info = {name:'xxx' , age:18} , 就可以修改name和age
赋值过程
//bar.js
let name = 'xxx';
const age = 18;
function say (value) {
console.log(value);
}
setTimeout(() => {
name = 'zdd'
}, 1000);
export {
name,
age,
say
}
//main.js
import { name, age, say } from './module/bar.js';
console.log(name);
console.log(age);
say('hello world');
setTimeout(() => {
console.log(name);
}, 2000)
2.4.6.Node对ES Module的支持
在最新的Current版本(v14.13.1)中,支持es module我们需要进行如下操作:
方式一:在package.json中配置 type: module(后续学习,我们现在还没有讲到package.json文件的作用)
方式二:文件以 .mjs 结尾,表示使用的是ES Module;
这里我们暂时选择以 .mjs 结尾的方式来演练