模块简介
一些模块历史,如:
- AMD,代表是
require.js
,实现前端代码的模块化 - CommonJs,主要是
node
使用 - UMD,兼容AMD和CommonJs
ES6开始有了JS模块,通过关键字 export import
分别来导出和导入模块资源。
现代浏览器比如chrome已经能支持ES的原生模块语法,但是大部分情况下我们还是会用打包工具来在浏览器端实现模块化。
// a.js
export function hi() {}
// main.js
import {hi} from './a.js'
hi()
//index.html
// 浏览器原生支持使用type=module标注这是一个模块脚本
<sciprt type="module">
import {hi} from './a.js'
hi()
</script>
核心功能
使用 use strict
模式
模块有自己的顶级作用域
<script type="module">
// 变量仅在这个 module script 内可见
let user = "John";
</script>
<script type="module">
alert(user); // Error: user is not defined
</script>
模块代码仅仅在第一次导入时被解析(运行)
// a.js
alert('hello')
// 1.js
import 'a.js' // 仅仅这里弹框一次
// 2.js
import 'a.js'
模块是引用传递
// admin.js
export let admin = {name: 'john'}
// 1.js
import {admin} from './admin'
admin.name = 'pete'
// 2.js
import {admin} from './admin'
console.log(admin.name) // pete
// 这里1.js 和 2.js 导入的是模块admin的引用,而不是复制一份admin
模块中this为undefind
<script>
alert(this); // window
</script>
<script type="module">
alert(this); // undefined
</script>
模块脚本是延迟加载的,与 defer
特性对外部脚本和内部脚本的影响相同。也就是说:
<script type="module" src="...">
不会阻塞HTML的解析,与其他资源并行加载。- 等到HTML文档就绪后(DOM解析完毕,在DomContentLoaded前)执行。因此可以访问到位于模块脚本后面的DOM元素 ```javascript
相较于下面这个常规脚本:
- 保证脚本的相对顺序,文档中排在前面的先执行
<a name="Nm74s"></a>
### Async适用于内联脚本
对于非模块脚本(即没有 `type=module` )仅适用于引入外部脚本 `src=xxx` 。而模块脚本在内联脚本中也可用,同 `async` 的特性,异步加载,加载完就执行,不会等待任何东西。适用于广告,统计等等逻辑。
<a name="C9kfl"></a>
### 外部脚本
- 加载多次,只执行一次,同模块引入。
- 不同源外部脚本,需要有跨域响应头设置 `Access-Control-Allow-Origin`
<a name="0LMGn"></a>
### 不能裸模块
即模块引入的路径需要是相对路径,或者是绝对路径。
```javascript
import _ from 'lodash'
// Error了,不借助webpack等,直接在浏览器中这么使用,属于引用了裸模块
// 需要使用绝对路径
import _ from 'https://cdn/lodash.js'
导入和导出
在声明前导出
声明变量之前导出
export const a = 'a'
export function b() {}
export class c {}
导出和声明分开
const a = 'a'
function b() {}
export {a, b}
import * 导入所有
import * as Mod from './mod.js'
// 通过
Mod.xx 来使用
导入所有看起来方便,但是列明需要导入的项,可以方便现代构建工具做 tree shaking
。
更建议这样 import {a,b} from './mod.js'
import as
导入并重命名一个新的变量名称
// mod.js
export function a() {}
// index.js
import {a as say} from './mod.js'
say()
export as
以新变量名重新导出
// mod.js
export {a as say};
// index.js,这里引入就要用新变量名了
import {say} from './mod'
export default
有两种模块形式,一种是导出很多模块,一种是只导出一个模块,这种通常用默认导出
// user.js
export default class User {}
// index.js
import User from 'user'
如果只有一个默认导出,可以没有类名,函数名
export default class {}
export default function() {}
//没有这个
export default const a = 1
default是一个名称,可以单独使用
function a() {}
export {a as default}
同时导出默认和命名模块
// mod.js
export default class User {}
export function sayHi() {}
// index.js
import {default as User, sayHi} from 'mod'
// 或者全部导进来
import * as mod from 'mod'
mod.default // class User
mod.sayHi()
重新导出
说白了就是导入和导出合并
export {say} from 'mod' // 从mod模块中引入并导出出去
export {default as User} from 'mod' // 从mod模块中引入默认并重命名为User导出
通常这么做,有一种情况,编写了大量模块,有些功能是导出外部的,有些模块是内部自己使用,可以提供一个index.js入口文件,只暴露出对外使用的方法
重新导出默认的导出
export * from 'mod'
// 只会重新导出命名的导出部分,忽略default
// 所以也需要导出默认
export {default} from 'user'
动态导入
import()函数,返回一个promise。
import()
只是一种语法,不是函数,不能当做一等功能传递,使用apply/call