Javascript程序本来很小,在早期的时候用来执行独立的脚本任务。在web上提供交互的时候提供交互,所以不需要多大的脚本,但是随着时间发展现在JavaScript可以执行复杂程序。
但是其它用户发现这个问题,比如Node和 CommonJS等却开始提供一种将JavaScript程序拆分可以按需要导入的单独模块机制。(如果学过Java的话,可以简单理解为不同的逻辑写入不同js,然后需要的时候再引入。)比如node支持是因为V8引擎,现在浏览器也慢慢的支持模块功能了,所以ES6也可以有了modules(模块)。而
使用 JavaScript 模块依赖于
import
和
export
,当然不是所有的浏览器都支持,所以通过Babel让其编译成ES5可运行的代码。不过目前浏览器新版本都支持,而IE这个兼容性的坑也被微软放弃变成Edge了。
- export: 规定模块的对外接口
- import: 规定输入其它模块提供的功能
其实模块这个的注意事项,简单的说就是遵守ES6制定的规范,而在使用中注意其使用的一些细节,理解其作为新特性的意义。
而本篇说的是ES6格式和要求,具体node等几个模块化的写法到时候再聊。
在模块概念中将独立的js文件视为一个模块(module)
。而这个文件可以在其它js文件中引用,也可以在html中引用。
对于模块 js 文件,其实业界或官方约定俗成称为
.mjs
文件格式。
一来能够和普通 JavaScript 文件(.js后缀)进行区分,一看就知道是模块文件;二来 Node.js 中 ES6 的模块化特性只支持 .mjs 后缀的脚本,能够和 Node.js 保持一致。固然,开发者直接使用.js做为模块 js 文件的后缀也是能够的。
若是在浏览器使用
.mjs
格式的文件引入模块时,是须要在服务器配置
mime type
类型的。由于浏览器对模块 JS 文件的 mime type 要求很是严格。
演示前提
通过编程软件调用
比如我用的pycharm其内部通过图标调用
这个可以看出url地址都不是显示存储的硬盘的地址,不像是直接通过浏览器打开这个文件,而是如下:
这是一种服务器的调用方式。
如果
是vscode 就需要按照live server插件。
都需一个服务器才可以调用,不然会报错。就是nodejs也是依托于跨时代的v8引擎。
在html中引入模块演示
第一步:通过export 将某js模块中的数据
暴露
出去
export var test='宫崎老贼';
export function createGame(){
console.log('你懂魂系?');
}
第二步:在html中引入:
<script type="module" >
</script>
第三步:通过import将数据引入
<script type="module" >
// 引入模块 这个*和as 后面具体聊
import * as t from './test.js'
console.log(t);
console.log(t.test);
t.createGame();
</script>
看一下结果
不过说实话,如果引用的文件模块文件很多,那就是会直接将所有的引用放入一个js文件中,然后页面直接调用这个js而统一引用。
规范
export和import
说一下export的格式:
// 第一种格式: 依次映射出去
export 变量名,[....]/方法等数据;
//例子1
export var test[,test1];// 可以在这里直接赋值有可以在后面中赋值。 如果是变量就需要前面添加 var/const/let
// 例子2 如果 是换行定义就需要添加一个 {} 不然会报错
export {test,[....]};
var test=....;// 如果是变量也是需要前面有var/const/let 不然也会提示未定义 因为是严格模式
import数据也是有格式:
import {变量名} from '路径';
注意:
-
如果是通配符 * 就不需要{},但是需要跟着 as。 还有一种默认暴露的数据。也就是通过
export default
暴露的就不需要花括号了。
-
模块的路径的地址,必须是
(/) 或者 (./) 或者(../)
.哪怕是在同级目录下,不然会报错。其导入模块的后缀( .js ),不同的环境有不同的要求可以省略,但是一般浏览器必须带。个人建议带着后缀,在哪里也可以运行。
export default
默认导出:每个模块包含一个;
这个和普通的暴露数据不同,下面演示一下:
// 第一种格式: 依次映射出去
export default 方法或者calss; // 不能var这种变量赋值哪怕赋值的是一个方法。
//例子1
export function test(){..};// 可以在这里直接赋值有可以在后面中赋值。 如果是变量就需要前面添加 var/const/let
// 例子2 这样暴露的话也需要花括号
export default test ;// 这里只能一个而不能多个
function test(){};//如果这样写var test=....;会报错
// 第二种
export { test as default };
function test(){};//如果这样写var test=....;会报错
导入默认暴露
import 变量名 from '路径'; // 不需要花括号了
//或者如下第二种
import {default as 变量名 from '路径';
为什么有第二种?本质export default 就是输出一个叫做default的变量。所以看一下默认的暴露是什么:
// 在test1.js
export default {test1};
function test1() {
console.log('test1')
}
//在test.js 中调用
import * as t from './test1.js'
console.log(t);
注意:
默认的暴露如果使用通配符是无法调用的,比如下面:
// 在test1.js
export default {test1};
function test1() {
console.log('test1')
}
//在test.js 中调用
import * as t from './test1.js'
// 如果调用 默认暴露
// import test1 from "./test1.js";
//使用
//test1()
// 这样调用会报错
t.test1() ;
关键字 as
暴露数据或导入的时候可以用as,这个只要学过SQL的化应该很容易理解,就是新别名。例子:
// 第一种 不用as
// 暴露为test
export var test;
// 导入的时候就需要使用
import { test,[....] }
// 第二种 不用as
// 暴露为test
export var test as te;// 或者 export {test as te}
// 导入的时候就需要使用
import { te,[....] }
当然import也可以使用as 因为意义一样:
// 第一种 不用as
import { test,[....] }
console.log(test)
// 第二种 不用as
// 暴露为test
import {test as te,[.....]} ;
// 引用的时候就需要用别名了
console.log(te)
通配符 *
这个在导入数据时候,一般会和as一起用, ( * )号的意思就是引入的文件所有的数据,因为数据是一个。所以需要用别名操作,不然*号只是一个泛指,如果有对象名呢?
其实模块加载时一个异步加载,如下操作
// 在一个 test1.js文件内容
export var t_date=Date.now()
//将test.js 文件内容改为
import * as test1 from
引入数据不可修改
引入数据不可以修改的,以及不同的模块js文件不共享变量名。
下面我演示一下:
// test1.js
export var t_name1='test1_1';
export var t_name2='test1_2';
// test.js
import * as t from './test1.js'
console.log(t.t_name1);
//console.log(t_name2); Uncaught ReferenceError: t_name2 is not defined 这里看出导入文件变量名属于导入模块的,而在这里引用也需要带着模块名下
t.t_name1='test';
报错:
module 的中转
这个其实在前面聊html如果加载很多的模块,可以通过一个js写入所有导入,然后被html引用,其实也就是中转的一种方式,下面简单演示一想;
比如test不需要test1中的数据,而其它文件需要test和tes1的数据。可以在test.js中如下写:
// test.js
export * as t from './test1.js'
export var str='test';
这种方式也被称之为继承,不过我感觉还是叫做中转比较贴切。
动态加载
还是老规矩摘抄官网解释,然后再代码演示。
标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入。下面的是你可能会需要动态导入的场景:
- 当静态导入的模块很明显的降低了代码的加载速度且被使用的可能性很低,或者并不需要马上使用它。
- 当静态导入的模块很明显的占用了大量系统内存且被使用的可能性很低。
- 当被导入的模块,在加载时并不存在,需要异步获取。
- 当导入模块的说明符,需要动态构建。(静态导入只能使用静态说明符)
- 当被导入的模块有副作用(这里说的副作用,可以理解为模块中会直接运行的代码),这些副作用只有在触发了某些条件才被需要时。(原则上来说,模块不能有副作用,但是很多时候,你无法控制你所依赖的模块的内容)
请不要滥用动态导入(只有在必要情况下采用)。静态框架能更好的初始化依赖,
而且更有利于静态分析工具和
tree shaking
发挥作用。
Tree shaking
是一个通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code) 行为的术语。它依赖于ES2015中的
import
和
export
语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用。
关键字 import 可以像调用函数一样来动态的导入模块。以这种方式调用,将返回一个
promise
。
演示看一下:
// test1.js 来一个类的,演示一下还可以暴露一个类
export {test1};
class test1 {
constructor() {
this.name='test1';
}
t_print(){
console.log(this.name)
}
}
// test.js
//这个前面可以用if(条件) 来判断是否调用这个模块
var test1= import('./test1.js')
console.log(test1)
ps:其实动态都需要一个类似 if 的判断语句的,但是为了方便演示我直接不带这个条件逻辑了。
所以可以通过
then
和
await
调用promise关键字而调用。(如果不了解promise,可以看另一篇文章
传送门
,如果不了解await可以另一篇文章:
传送门
)
现在来一个使用的演示:
// test1.js 来一个类的,演示一下还可以暴露一个类
export {test1};
class test1 {
constructor() {
this.name='test1';
}
t_print(){
console.log(this.name)
}
}
// test.js
//这个前面可以用if(条件) 来判断是否调用这个模块
var test1= import('./test1.js')
// 这个成功状态下的value 可以理解为通配符import * as value from...
test1.then(value =>{
var test1_obj= new value.test1();
test1_obj.t_print()
})
如果是await的话如下:
// test.js
//这个前面可以用if(条件) 来判断是否调用这个模块
// 这个成功状态下的value 可以理解为通配符import * as value from...
var value= await import('./test1.js');
var test1_obj= new value.test1();
test1_obj.t_print()
结果如上图,不再重复。