关于前端模块化中CommonJS和ES6模块的理解
/ / 点击 / 阅读耗时 9 分钟在前端模块化中,目前主流的两种规范是CommonJS规范以及ES6的module规范。虽然平时项目经常使用到这些规范,但是对于这两者的区别以及更深层的理解还是不够,所以自己在网上查找资料补下这块的知识盲点。
首先先从ES6模块说起
ES6 module
ES6 module模块主要分为以下几个命令:
- import: 用于输入其他模块提供的功能,也可以说是用于模块的导入
- export:用于规定模块的对外接口,也可以说是用于模块的导出
- export default: 用于规定模块的默认导出
export
export 命令主要用于导出模块接口,使用方法如下:
export const name = 'zs' //输出变量
export function add(x,y){ //输出函数
return x + y;
}
function t1(){...}
export {
t1 as test1
}
//可以使用as关键字重新定义输入函数变量名
import
import 指令主要是用于导入其他接口,主要用法如下
import { firstName, lastName } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
除此之外,我还总结了import指令的其他知识点:
- import多次导出同一个接口只会调用一次
- import as 可以将导入的变量重命名
- import * 可以指定一个对象整体加载引入的方法或变量
- import引入的变量都是只读,除非引入的是对象
//如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次
import 'lodash'
import 'lodash'
//import as 可以重命名引入的变量 ,以下就是引入的变量n改写为变量m
import { n as m } from './profile.js'
//import * 可以指定一个变量接受引入的方法或变量
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
//常规引入接口提供的方法
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
//import * 整体加载
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
export default
export default 用于接口的默认导出
//默认导出一个匿名函数
export default function () {
console.log('foo');
}
//下面import可以使用任意变量名定义export输出的方法
import customName from './export-default';
customName();
export default 和export的区别
- export default 命令用于指定模块默认导出,一个模块只能有一个默认输出,因此export default只能有一次
- export default 对应的 import不需要使用大括号, export 对应的 import需要使用大括号
CommonJS module
CommonJS规范中,每个文件就是一个模块,有自己的作用域。一个文件里面定义的类、函数、变量都是私有的,对其他文件不可见。CommonJS主要有以下几个关键对象:
- exports:Node内置对象,用于模块中的变量、函数等导出。实际指向module.exports。
- module.exports: 表示当前模块对外输出的接口,其他文件加载模块,实际就是读取module.exports变量。
- require: 读取并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有则报错。
exports
exports 其实就是指向module.exports
var exports = module.exports
所以如果将 exports 变量指向一个值的时候,其实就是像module.exports赋值。所以最终导致可能会导出接口错误等问题,如下:
exports = function(){}
上面这种写法改变了默认exports的指向,导出的时候module对象中的exports属性则为空对象。
const name = 'zs'
const fn = function(){console.log('ls')}
exports.name = name;
exports.fn = fn;
module.exports = 'hello world'
像上面这种写法也不行,首先exports导出了一个变量和一个函数,其实是在module.exports对象里面添加了两个对象,最后module.exports = ‘hello world’重置了module.exports对象。最终只能返回一个字符串。
module exports
module.exports其实是module对象的一个属性,module对象其实还包括其他一些属性,比如:
module.id 模块的识别符,通常是带有绝对路径的模块文件名。
module.filename 模块的文件名,带有绝对路径。
module.loaded 返回一个布尔值,表示模块是否已经完成加载。
module.parent 返回一个对象,表示调用该模块的模块。
module.children 返回一个数组,表示该模块要用到的其他模块。
module.exports 表示模块对外输出的值。
举个例子:
const name = 'zs'
const fn = function (a,b) {
return a + b;
}
exports.name = name;
exports.fn = fn;
console.log(module)
打印module得出:
Module {
id: '.',
exports: { name: 'zs', fn: [Function: fn] },
parent: null,
filename: 'D:\\demo\\JS\\t2.js',
loaded: false,
children: [],
paths:
[ 'D:\\demo\\JS\\node_modules',
'D:\\demo\\node_modules',
'D:\\node_modules'
]
}
require
require指令是读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。
// example.js
exports.message = "hi";
exports.say = function () {
console.log(message);
}
通过require引入可以输出exports对象
var example = require('./example.js');
example
// {
// message: "hi",
// say: [Function]
// }
CommonJS模块和ES6模块的区别
- CommonJS可以动态引入文件,如
require(${path}/main)
,而ES6模块是编译时就确定好的。 - CommonJS模块的加载机制是,输入的是被输出的值的拷贝,模块内部的变化不会影响输出的值。而ES6模块输出的是值的引用,当模块内的值发生改变,引用也会改变。