模块化:将一个整体的文件根据功能和逻辑拆分成几个互相依赖的独立文件(模块)。这样做的好处,就是让代码和逻辑能根据不同模块进行解耦,减少每个模块之间的影响,方便我们在复杂项目中对代码的管理和维护。
概念理解如下图:
在ES6之前,是由社区指定的模块加载方案:
-
CJS(CommonJS) 服务器
-
AMD 浏览器
在ES6的标准中,实现了模块功能
-
ESM(ES module)
ES6模块中主要的两个功能:
-
export:对外导出当前模块的数据
-
import:在当前模块中导入另一个模块的数据
让我们来了解两个命令的具体使用。
export
export指令通常也被称为命名导出,用于导出模块内的函数与变量,我们可以看导出时的不同做法:
//module1.js
export var a = 1;
export var b = 'fun';
export function fun(){}
像上述代码中,使用export命令导出了a、b两个变量及函数fun,ES6将module1.js文件视为一个模块,这种写法等价于:
//module1.js
var a = 1;
var b = 'fun';
function fun(){}
export {a, b, fun}
比较前者,这种写法可以明确分辨整个模块具体暴露了哪些内容。
同时我们可以用as关键字进行暴露内容的重命名(这种做法也被称为别名导出),例如:
//module1.js
var a = 1;
var b = 'fun';
function fun(){}
export {
a as a1,
b as b1,
fun as fun1
}
import
import指令用于导入其他模块的函数与变量,我们可以看下基本用法:
//main.js
import { a } from "./module1.js";
console.log(a) // 1
像上述代码中,引入了module1.js文件中暴露的a变量,括号内的变量名必须与module1.js模块中导出的变量名相同。
同时,引入的变量是只读的,不可修改。
//main.js
import { a } from "./module1.js";
a = 2 // Uncaught TypeError: Assignment to constant variable.
如果引入在module1.js中未暴露过对应变量,或者变量名不相同,则会报错:
//main.js
import { a1 } from "./module1.js";
//Uncaught SyntaxError: The requested module './module1.js' does not provide an export named 'b'
既然如此,如果有对应的场景,需要将引入的变量进行重命名(别名导入),import也可以通过as关键字达到目的:
//main.js
import { a as a1 } from "./module1.js";
console.log(a1) // 1
上述代码中,将引入的a变量重命名为a1。
还有更具体的场景,例如导入两个模块,并且这两个模块都导出了同一个命名的变量,那重命名就派上用场:
//main.js
import { a as a1 } from "./module1.js";
import { a as a2 } from "./module2.js";
虽然as解决了同名的场景,但如果导入的两个模块,有大量变量同名,那命名的工作量就变大了:
//main.js
import {
a as a1,
b as b1,
fun as fun1
} from "./module1.js";
import {
a as a2,
b as b2,
fun as fun2
} from "./module2.js";
这种情景,会让导入变量的过程格外麻烦(尤其是命名)。
那我们可以使用*将目标模块中导出的所有变量存放在一个对象上,模块的整体导入(命名空间导入),极大地简化上述情景中的导入过程:
//main.js
import * as module1 from './module1.js'
import * as module2 from './module2.js'
console.log(module1.a)
console.log(module2.a)
注意,上述代码中的module1对象不可扩展,且已有的属性是只读的,不可修改:
//main.js
import * as module1 from './module1.js'
module1.xxx = 2 // TypeError: Cannot add property b, object is not extensible
module1.a = 2 // TypeError: Cannot assign to read only property 'a' of object '[object Module]'
export default
export default(默认导出)的特点如下:
1.导出时不需要命名,一个模块内只能默认导出一次。
2.导入时需要命名,且不用大括号。
我们来看下基本用法:
//exportModule.js
export default function(){
console.log(1)
}
//main.js
import fn form './exportModule.js'
fn() //1
像上述代码,导入时可以随意命名。
但在导出时模块只能有一个默认导出,否则会报错:
//exportModule.js
export default function(){
console.log(1)
}
export default function(){
console.log(2)
}
//Duplicate export of 'default'
Duplicate export of 'default(‘default’被重复导出)
通过这个报错语句我们可以推测下默认导出的本质:”将目标值赋值给default变量“,具体我们看下面这段代码:
//exportModule.js
export default function(){
console.log(1)
}
//main.js
import * as exportModule from './exportModule.js'
console.log(exportModule) // { default: [Function: default] }
exportModule对象中只有一个属性default,存放的就是exportModule模块中默认导出的函数。
同时要避免一些错误的写法,例如:
//错误写法
export default var a = 1
export 1
//正确写法
var a = 1
export default a
//等价于
export default 1
既然了解默认导出的本质,那应该明白错误写法的问题,我们无法将一句声明变量的语句赋值给default变量。
而且,命名导出和默认导出时可以同时使用:
//module.js
export var a = 1
export default function fn(){
console.log(1)
}
//main.js
import * as module from './module.js'
module.default() //1
console.log(module.a) //1
//等价于
import fn,{a} as module from './module.js'
fn() // 1
console.log(a) //1
总结
模块化对大型项目十分重要,利于代码的维护和优化。掌握ES6的模块化语法,对应不同场景进行准确使用,类似文本提及的‘不同模块过多相同命名变量的冲突’需要懂得使用空间命名导入,否则即使有模块化概念,仍然会写出多余而冗长的代码。