ES5及以下版本JS通过在html中<script></script>定义和<script src=”XXX.js”></script>方式引入的JS代码,都是全部以全局作用域共享方式存于内存中至页面退出而销毁,即造成内存浪费,存在命名冲突和安全问题,也不利用程序阅读调试。ES6引入了JS模块化封装,将模块化中所有变量(方法)进行私有封闭,要想在全局作用域使用某模块中的变量(方法),一是必须在模块中进行声明,二是在全局作用域引入模块文件时进行变量(方法)导入绑定。如此,就大大减少全局作用域的总驻代码量,模块只将声明了的变量(方法)导出给全局作用域,从而让程序清晰明了、结构简化,更是易于调试,有利用程序稳定运行。
当然,一个模块也可导入绑定另一个模块的变量(方法)。
一、模块导出声明
方法是通过export关键字将一部分已发布的代码暴露给全局作用域或其他模块。
//example.js文件中定义如下内容
1.导出数据
export var color="red";
export let name="张小玉";
export const magicNumber=7;
2.导出函数
export function sum(num1,num2){
return num1+num2;
}
3.导出类
export class Rectangle{
constructor(length,width){
this.length=length;
this.width=width;
}
}
4.下面这个函数没用export声明,其是模块私有化的
function subtract(num1,num2){
return num1-num2;
}
5.也可以先定义函数,后用export导出函数名
function multiply(num1,num2){
return num1*num2;
}
export multiply;
6.导出时重命名
function sum(num1,num2){
return num1+num2;
}
export {sum as add}; //导出时将函数名从sum改为add
import {add} form “./example.js”;//由于导出时改了名,导入时必须使用add这外名称
二、全局作用域(或模块域)导入绑定
从模块导出的功能可通过import关键字在全局作用域或另一个模块中访问。语法基本形式是:
Import {identifiler1,identifiler2…} from “./example.js”;
大括号里表示从给定模块中导入的绑定(binding),当从模块中导入一个绑定时,它就好象使用const定义一样。结果是你无法定义另一个同名变量名(包括导入另一个同名绑定),另外绑定的变量名,也无法赋值改变。
下面,以导入上述模块文件example.js为例,进行举例:
1.导入单绑定
Import {sum} form “./example.js”;//导入sum函数
console.log(sum(1,2));//输出结果:3
sum=1; //将抛出一个错误
2.导入多个绑定
Improt {sum,multiply,magicNumber} from “./example.js”;//导入2个函数和一个变量
console.log(sum(1,magicNumber));//输出结果:8
console.log(multiply(1,2));//输出结果:2
3.导入整个模块
import * as example from "./example.js";//导入模块中的所有绑定被加载到一个example对象中
console.log(example.sum(1,example.magicNumber));//输出结果:8
console.log(example.multiplay(1,2));//输出结果:2
4.不管被import多少次,实际example.js只被绑定导入一次
Import {sum} form “./example.js”;
Improt { multiply} from “./example.js”;
Improt { magicNumber} from “./example.js”;
5.模块语法的限制
export和import的一个重要限制是,它们必须在其他语句和函数之外使用。例如,下面代码会给出一个错误:
if(flag){
export flag;//抛出错误
}
同样,export和import不能动态的导入和导出绑定,它们必须是静态的,在程序运行前就必须存在,不能在程序运行中给与定义。
6.导入时重命名
function sum(num1,num2){
return num1+num2;
}
export {sum};
import {sum as add} form “./example.js”;
console.log(typeof,sum);//输出结果:undefined
console.log(sum(1,2));//输出结果:3
//由于导入时改了名,导入时必须使用add这外名称
三、模块的默认值导出导入
这种方法是大多数程序员常用的,其意思是指,在导出时用默认名(default),不去指定其名称(只能导出一个变量或方法),在导入时指定一个名称代替,以达到简化代码目的。
1.导出默认值
//在XXX.js中只能有一个导出变量或方法
export default function(num1,num2){
return num1+num2;
}
也可以如下定义导出方式
Function sum(num1,num2){
return num1+num2;
}
export default sum;或者export {sum as default};
2.导入默认值
//导入默认值
import anyName from "./example.js"//anyName可以是任何名
console.log(anyName(1,2));//输出结果:3
四、在html中如何定义使用模块
<script>元素的默认行为是将JS文件作为脚本加载,而非作为模块加载。当type属性缺失或包含一个内容类型(如text/javascript)时就会只当作脚本。而当type属性值为“module”时,就会当作模块加载。
<!--在html中加载一个javaScript模块文件-->
<script type="module" src="example.js"></script>
<!--内联引入一个模块-->
<script>
import {sum} from "./example.js";
let result=sum(1,2);//输出结果:3
</script>
五、web浏览器中的模块加载顺序
三个原则:
第一,按需加载,不管是<script type=”module”>显示引入还是import隐式导入,都是需要时才加载;
第二,同步加载时,按照html中从上到下的顺序进行按需加载,总是等上一个模块执行完毕后才执行下一个模块;
第三,当异步加载不同模块时,不按先后顺序,谁先下载完成,先执行谁。如下,
<!--无法保证这两个哪个先执行-->
<script type="module" src="module1.js"></script>
<script type="module" src="module2.js"></script>
在这个实例中,两个模块文件被异步加载,如果module1.js首先完成下载(包括其所有的支援资源)它将先执行,如果module2.js首先完成,则module2.js将先执行。