模块

项目变大时需要把不同的业务分割成多个文件,这就是模块的思想。模块是比对象与函数更大的单元,使用模块组织程序便于维护与扩展。

  • 模块就是一个独立的文件,里面是函数或者类库
  • 虽然JS没有命名空间的概念,使用模块可以解决全局变量冲突 模块需要隐藏内部实现,只对外开发接口 模块可以避免滥用全局变量,造成代码不可控 模块可以被不同的应用使用,提高编码效率

AMD

let module = (function() {
	// 模块列表集合
	const moduleList = {}

	function define(name, modules, action) {
		modules.map((m, i) => {
			modules[i] = moduleList[m]
		})
		// 执行并保存模块
		moduleList[name] = action.apply(null, modules)
	}

	return { define }
})()

// 声明模块不依赖其他模块
module.define('mk', [], function() {
	return {
		show() {
			console.log('mk modules show')
		}
	}
})

// 声明模块依赖其他模块
module.define('mq', ['mk'], function(mk) {
	mk.show()
})

基础知识

标签使用

在浏览器中使用以下语法靠之脚本做为模块使用,这样就可以在里面使用模块的代码了。

在html文件中导入模块,需要定义属性 type="module"

<script type="module"></script>

模块路径

在浏览器中引用模块必须添加路径如./ ,但在打包工具如webpack中则不需要,因为他们有自己的存放方式。

<script type="module">
    // import { mk } from 'mk.js' // error
    import { mk } from './mk.js' // ok
</script>
export const mk = {
    name: 'mkimq'
}

延迟解析

模块总是会在所有html解析后才执行,下面的模块代码可以看到后加载的 button 按钮元素。

建议为用户提供加载动画提示,当模块运行时再去掉动画

严格模式

模块默认运行在严格模式

作用域

模块都有独立的顶级作用域

单独文件作用域也是独立的

预解析

模块在导入时只执行一次解析,之后的导入不会再执行模块代码,而使用第一次解析结果,并共享数据。

  • 可以在首次导入时完成一些初始化工作
  • 如果模块内有后台请求,也只执行一次即可

导入导出

ES6使用基于文件的模块,即一个文件一个模块。

  • 使用export 将开发的接口导出
  • 使用import 导入模块接口
  • 使用*可以导入全部模块接口
  • 导出是以引用方式导出,无论是变量还是对象,即模块内部变量发生变化将影响已经导入的变量

导出模块

export const site = 'mkimq'
export const func = function() {}
export class User {}
export default {}

具名导入

import { site, func, User} from './mk.js'

模块默认是在顶层静态导入,这是为了分析使用的模块方便打包

批量导入

如果要导入的内容比较多,可以使用 * 来批量导入。

import * as all from './mk.js'

导入建议

因为以下几点,我们更建议使用明确导入方式

  • 使用webpack 构建工具时,没有导入的功能会删除节省文件大小
  • 可以更清晰知道都使用了其他模块的哪些功能

别名使用

导入别名

可以为导入的模块重新命名

  • 有些导出的模块命名过长,起别名可以理简洁
  • 本模块与导入模块重名时,可以通过起别名防止错误
import { User as user } from './mk.js'

导出别名

模块可以对导出给外部的功能起别名

export {
    site,
    func as action,
    User as user
}

默认导出

很多时候模块只是一个类,也就是说只需要导入一个内容,这地可以使用默认导入。

使用default 定义默认导出的接口,导入时不需要使用 {}

  • 可以为默认导出自定义别名
  • 只能有一个默认导出
  • 默认导出可以没有命名

单一导出

export default class {}

// 将一个导出命名为 default 也算默认导出
export {
    User as default
}

// 导入时就不需要使用 {} 来导入了

import user from './mk.js'

默认导出的功能可以使用任意变量接收

混合导出

模块可以存在默认导出与命名导出。

export const site = ''

export default function() {}

// 可以使用一条语句导入默认接口与常规接口
import fn, { site } from './mk.js'

如果是批量导入时,使用 default 获得默认导出

使用建议

  • 不建议使用默认导出,会让开发者导入时随意命名
  • 如果使用默认导入最好以模块的文件名有关联,会使用代码更易阅读

导出合并

解决问题

可以将导入的模块重新导出使用,比如项目模块比较多如下所示,这时可以将所有模块合并到一个入口文件中。

实际使用

export * as mk from './mk.js'

export { default as fn } from './fn.js'

动态加载

使用import必须在顶层静态导入模块,而使用import()函数可以动态导入模块,它返回一个promise对象

if (true) {
    import { User } from './mk.js' // error
}

// ok
if (true) {
    const mk = import('./mk.js').then(module => {
        console.log(module.site)
    })
}

指令总结

表达式说明
export function show() {}导出函数
export const name = 'mkimq'导出变量
export class User {}导出类
export default show默认导出
const name = 'mkimq' export { name }导出已经存在变量
export { name as mk_name }别名导出
import defaultVar from 'mk.js'导入默认导出
import { name, show } from 'a.j'导入命名导出
Import { name as mkName, show } from 'mk.js'别名导入
Import * as api from 'mk.js'导入全部接口
贡献者: mankueng