第三十一节: ES6 ESModule
1. Module 模块化
JavaScript 采用'共享一切'的方式加载代码, 也就是在ES6 之前, JavaScript中定义的一切都共享同一个全局作用域, 这样随着程序复杂度提升的同时,也会带来命名冲突等诸多问题. 因此ES6的一个目标就是解决作用域问题. 也为了使JavaScript 应用程序显得有序, 于是引进了模块
1.1 模块功能
模块功能主要由两个命令构成:export和import。
export命令用于规定模块的对外接口。
import命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。
2. 模块的使用
注意如果需要测试模块化需要放到服务器环境
<!-- module模块化 import导入 -->
<script src="02.js"></script> <!-- 这里只是引入了脚本 -->
<!-- 在浏览器上运行的基本上是前端模块化
在服务器上运行的基本是后端模块化
后台是给内部员工使用,用来添加数据的。前台给用户看的。都是前端
-->
<script type="module">
// module表示模块化。注意:模块化只能在服务器环境运行。服务器环境有端口号,IP地址,协议
//js的type类型通常是javascript,所以省略,里面就直接写脚本
import './01.js' // 这样导入js,只是执行了一遍该js文件,拿不到任何数据,不能使用该js中的变量和函数
// console.log(a); //此时a是在01.js中定义声明的,这里没法使用。
//导出模块:想要使用a,要在定义a的文件里写上export let a =10; 导出的是模块对象,再回到需要的地方拿到这个导出的模块对象
// import * as aa from './01.js'; //导入理解:从路径'./01.js'中导出模块* ,aa作为这个模块的别名
// console.log(aa);
// console.log('模块对象里的变量a为',aa.a);
import { a } from './01.js'//导出的简单写法,很像解构
console.log(a); //很像解构因为前面从后面是对象中拿到a
//对应01.js文件中的代码
export let a =10;
export let b=20;
2.1. export 导出语法
使用export 关键字将模块中的一部分代码暴露给其他模块, 可以将export 关键字放在任何变量, 函数,类声明之前, 将其导出
导出模块
export 东西
2.1.1 export 导出(暴露)
// 单个导出数据(变量)
export let a = 10; //导出变量
export let b = 20;
export function add(){ //导出函数
return a+b
}
let c = 'c30'
console.log('这里是01.js')
//整体导出,一次性导出多个数据
let a = 10;
let b = 20;
let c = 'c30'
function add(){
return a+b
}
add()
export { //不能按照对象的写法,只能数据的罗列,类似es6的简写方式
a,
b,
add
}
2.1.2 export`命令输出函数或类(class)
// 导出函数
export function multiply(x, y) {
return x * y;
};
// 导出类
export class Penson{
constructor(name,age){
this.name = name;
this.age = age;
}
}
2.1.3 as关键字重命名
通常情况下export 输出的变量, 函数或者类就是本来的名字,但是有时,我们可能并不希望使用它们的原始名称. 此时就可以通过as关键字修改导出元素的名称.
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
};
2.1.4 特别注意
需要特别注意的是,export命令规定的是对外的接口
// 报错
export 1;
// 报错
var m = 1;
export m;
//正确写法
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};
同样的,function的输出,也必须遵守这样的写法
// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};
动态绑定
export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
<!-- 动态数据导出,异步异出 -->
<!-- 导入文件 -->
<script type='module'>
import { a, b } from './01.js';
console.log(a, b); //a,b不是新值
</script>
<!-- 导出文件 -->
let a = 10;
let b = 20;
function add() {
return a + b
}
setTimeout(() => { //3秒后导出
a = 100;
b = 200;
}, 3000)
export { a, b, add }
<!-- 动态数据导出,异步异出 -->
<!-- 导入文件 -->
<script type='module'>
import { a, b } from './01.js';
console.log(a, b); //a,b不是新值
setTimeout(()=>{
console.log(a, b); //这里的值就是更新的了
},4000)
</script>
<!-- 导出文件 -->
let a = 10;
let b = 20;
function add() {
return a + b
}
setTimeout(() => { //3秒后导出
a = 100;
b = 200;
}, 3000)
export { a, b, add }
2.1.5 整体导出
发现导入都是需要花括号的,如果希望导入不用花括号,那就需要改变导出方式
// 导出时使用export default
let a;
export default a = 10;
// 导入时可以不用花括号
<script type="module">
import a from './modules/1.js'
// 默认导出 export default 注意一个模块只能有一个默认导出
// export default [ 'a', 'b', 'add' ] //也可以导出对象,函数等 只能有一个默认导出
//export default function aa(){
// console.log(111);
//}
导入文件
<script type='module'>
import aa from './01.js';
console.log(aa);
</script>
// 同时有默认导出 export default 和变量导出
export let a=10;
export let b =20;
export default function aa(){
console.log(111);
}
导入文件
<script type='module'>
// 导入方法一
import * as aa from './01.js';
console.log(aa);
console.log(aa.a);
console.log(aa.b);
console.log(aa.default);
// 导入方法二
import def, { a, b } from './01.js'; //大括号里面的是有名字的,外面的是默认导出的
console.log(a);
console.log(b);
console.log(def);
</script>
2.2. 导入模块
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
引入模块
import 模块路径
//正常导入
import * as js01 from './01.js'
console.log(js01); //打印导入的模块对象
console.log(js01.a); //这样导入的数据需要打点才能使用,通常不这么用
//类似解构的简单的导入方式
import {a,b,add}from './01.js'
console.log(a); //不用打点,直接使用
console.log(a,b);
console.log(add());
<script type="module">
import "./modules/1.js"; // 这种写法只是相当于引入一个文件,也叫无绑定导入
import {a} from "./modules/1.js" // 导入模块中导出的a
</script>
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
上面代码的import命令,用于加载profile.js文件,并从中输入变量。import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。
import {a,b,add}from './01.js'
a =100 //导入的数据相当于常量const a =10 ,不能随意再赋值,会报错
2.2.1 as关键字改变量名
//导出文件里的内容
let a = 10;
let b = 20;
function add(){
return a+b
}
let c = 'c30'
export {
a as aaa, //将变量a通过as改名为aaa,导入时全部使用aaa
b,
add as bdd
}
//导入文件里的内容
import {aaa,b,bdd}from './01.js'
console.log(aaa); //使用as后的变量名
console.log(b);
console.log(bdd());
import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。
2.2.2 import 提升
注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。
// 导入前引用
export let a =10;
导入文件
<script type='module'>
console.log('我在导入之前的', a * a); //导入前引用
import { a } from './01.js'; //程序会将import提升,但最好自己写在最前面
</script>
foo();
import { foo } from 'my_module';
//import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。
2.2.3 import不能使用表达式
由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
// 报错 不能拆成表达式
import { 'f' + 'oo' } from 'my_module';
// 报错 导入不能使用表达式
let module = 'my_module';
import { foo } from module;
// 报错 import只能使用在顶层
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
2.2.4 模块化整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
注意,模块整体加载所在的那个对象,不允许运行时改变。下面的写法都是不允许的。
2.2.5 import 特点总结
- 可以是相对路径也可以是绝对路径
import "./modules/1.js"; - import模块只会导入一次,无论你引入多少次。模块的导入导出有缓存机制,只有第一次导入会执行一遍并且将数据放入缓存,后面的所有的导入直接到缓存中取数据使用,但不会再执行了。
- import "./modules/1.js"; 如果这么用,就相当于引入了一个js文件
- 导入模块中的一个变量或常量
import {a,b,c} from "./modules/1.js"
5.导入时可以取别名使用
// 导入时也可以修改别名
export let a =0;
导入的文件
<script type='module'>
let a = 1234;//导入的变量与模块中的变量a重名了
console.log('a还是原来的a',a); //a已经存在,所以要改别名
import {a as aa} from './01.js';
console.log('aa是导入时修改的别名',aa);
</script>
import {a as aa,b as bb,c as cc} from "./modules/1.js"
6.导入这个模块导出的对象
import * as obj from './modules/1.js';
// 这个时候你就以使用obj这个Module对象了
console.log(obj.a);
7.import 的导入语句会进行提升,提升到最顶部,首先执行
console.log(a + b); // 能正常获取到a,b的值
import {a,b} from './1.js'
8.出去的模块内容,如果里面有定时器发生导出内容的改变,改变的内容会根据定时器自动同步缓存中的内容,所以外边导入的内容也会实时发生改变,不像Comment会缓存。
2.2.6 import() 动态引入
默认的import语法不能写在if之类的判断里的,因为是静态引入,先引入在使用
// 这种写法是错的
let a = 12;
if(a == 12){
import {a} from './1.js'
}else{
import {a} from './2.js'
}
import()是动态引入 类似于node里面的require
//动态导入模块(按需导入)import()返回promise
let flag=false;
if(flag){
import('./01.js')
.then(data=>{
console.log('data',data);
})
}else{
import('./02.js')
.then(res=>{
console.log('res',res);
})
}
//动态导入模块返回promise优化写法
let flag = true;
let url = flag ? './01.js' : './02.js'
import(url)
.then(res => {
console.log('res', res);
})
优点:
- 按需加载
- 可以写在if里面,判断加载
- 路径都可以是动态的
例子:
/ 这里是1.js
let a = 20;
let b = 30;
export {
a, b
}
// 这里是2.js
import { a, b } from './1.js';
console.log(`a的值是:${a}, b的值是:${b}`);
const sum = () => {
console.log(`我是a,b的和:${a + b}`);
return a + b;
}
const show = () => {
console.log('show 执行了')
return 1;
}
function Person(name, age) {
this.name = name;
this.age = age;
this.showName = function () {
return `我的名字是${this.name}`;
}
}
export {
sum,
show,
a,
b
}
export default { Person }
// 这里是HTML导入
import mod, { sum, show, a, b } from "./2.js"
console.log(mod);
let p = new mod.Person("wuwei", 18);
console.log(p);
console.log(p.showName());
show();
sum();
console.log(a, b);
// 所有导出只能在顶层导出
if(true){
export let a=10; //这不是顶层所以报错,不能通过条件判断和函数再导出,嵌套导出会报错
}else{
export let a=200;
}
导入文件
<script type='module'>
import {a} from './01.js'; //大括号里面的是有名字的,外面的是默认导出的
console.log(a);
</script>
// 导出只能在顶层导出,可以这样写
let a =0;
if(true){
a=10;
}else{
a=200;
}
export{a} //整体导出才可以
导入文件
<script type='module'>
import {a} from './01.js'; //大括号里面的是有名字的,外面的是默认导出的
console.log(a);
</script>