引子
欢迎来到 JavaScript 的模块化大战——这是一场被各种不同规范规定的模块横亘在我们面前带来的混乱战斗。然而,拿起你的剑吧,让我们一起跨过这段困难的旅程!
在ES6(ECMAScript 2015)之前的规范
JavaScript社区存在多种模块化规范和实现
- CommonJS ⭐⭐⭐⭐⭐
- AMD(Asynchronous Module Definition)
- UMD(Universal Module Definition)
- IIFE(Immediately Invoked Function Expression)
CommonJS
在令人舒适的ES6(也就是 ECMAScript 2015)之前,我们的JavaScript社区里各种模块化规范和实现让人眼花缭乱。比如说CommonJS,这是一个专门针对服务器端JavaScript的模块化规范。要是你是个Node.js 的粉丝,你一定熟悉这个。它有两个特别简单术语——“require”和 “module.exports” 偶尔还会有个"exports",用这两个英勇的小家伙,你就可以加载和导出你的模块啦!
自创模块的导入导出
我们来看看他的第一件装备:module.exports
我们有两个模块,一个是math.js,用于进行数学运算,另一个是app.js,用于调用math.js中的函数。
首先,创建一个名为math.js的文件,并在其中定义一些数学函数:
math.js
它有 add 和 subtract 这两个超能力 加法函数 function add(a, b) { return a + b } 减法函数 function subtract(a, b) { return a - b } 然后我们把这些超能力暴露出去,让别人也可以得到这些超能力 module.exports = { add: add, subtract: subtract }
app.js
引入math.js模块 有了这个,我们其他模块也可以有这个超能力 var math = require('./math') 看看我学会了新能力 使用加法函数 var result1 = math.add(10, 5) console.log(result1) // 输出: 15 使用减法函数 var result2 = math.subtract(10, 5) console.log(result2) // 输出: 5
看吧,是不是很神奇?只要你有心,你也可以!
而且这个英雄如此好学,他还多学会了一种叫做“exports
”的装备,别小看它,它并不逊色于"module.exports
"!
utils.js
我们有个模块叫做 utils.js,它有 sum 和 reverseString 这两个超能力 求和函数 function sum(numbers) { return numbers.reduce((acc, curr) => acc + curr, 0) } 反转字符串函数 function reverseString(str) { return str.split('').reverse().join('') } 然后我们用 exports 这个武器,暴露出去这些超能力 exports.sum = sum exports.reverseString = reverseString
app.js
这样,其他模块想要这些超能力,只要去借就好了 var utils = require('./utils') 定义一组数字 var numbers = [1, 2, 3, 4, 5] 使用sum函数求和 var total = utils.sum(numbers) console.log(total) // 输出: 15 使用reverseString函数反转字符串 var str = 'Hello World!' var reversed = utils.reverseString(str) console.log(reversed) // 输出: '!dlroW olleH'
是不是很厉害,就像是开了挂,来来去去就那么几行代码,动不动就能加载和导出任何模块,令人惊叹,这就是开挂的生活,我太羡慕了!
导入外部模块或第三方模块
等等,别急着去模仿英雄,除了自创超能力外,还有另一个技能——就是借用别人的超能力。没错,他叫做 CommonJS,他可以引入别人的超能力,把优秀的进行繁衍和传承,让强者更强。
相对路径引入
var http = require('http')
绝对路径引入
var myModule = require('/path/to/myModule')
使用模块的名称引入(需要先使用 npm 安装)
var moment = require('moment')
AMD(Asynchronous Module Definition)
在浏览器方面,我们逃不过另一个规范 —— 那就是 AMD (Asynchronous Module Definition)。这是专门针对浏览器端设计的,跟它名字一样,可以异步加载你的模块。它手里有两个武器 “define” 和 “require”,听起来跟CommonJS差不多,可实则不然,这娃子可是异步加载的,不像CommonJS那样会阻塞,性格活跃开朗,给它个大大的赞!
UMD(Universal Module Definition)
在上面这两个标准横行霸道的同时,出现了一位充满和平属性的 “UMD” (Universal Module Definition) 模块标准——综合了CommonJS和AMD, 还能在全局的JavaScript环境中工作。想想就觉得他好厉害,能在这几个环境中灵巧的折腾。但是他不出名,可能因为名字不好听吧。嘿嘿~
IIFE(Immediately Invoked Function Expression)
除此之外, IIFE(Immediately Invoked Function Expression ,立即调用的函数表达式),在 ES6 之前广泛用于模拟模块化。它不是一种明确定义的模块化规范,但却占据了 JavaScript 世界的大半江山。它的特点就是形成一块私有空间,变量不会污染全局。这一点,给它点个赞!
ES6的模块化语法
然后呢,那些派系斗争终于在ES6出现后告一段落。ES6的模块化标准走入了我们的视线。ES6的规范采用了一种更现代化、更强大的模块化特性,听到这你是不是心里一震,同时又对未知感到一丝恐惧呢?别怕,我来给你解释。
ES6的模块化功能主要由 export 和 import 这两个命令构成。 export 是英雄,用于定义模块的对外接口,而import就是小跟班,负责把其他模块的功能都统统引用过来。在在在在在在在,这里超级重要——“export default”,简单来说,它只能定义一个主角,不能搞出一堆 “皆大欢喜” 的场面出来。
export
命令用于规定模块的对外接口import
命令用于输入其它模块提供的功能
暴露方式
命名导出
- 分别导出
export A export B export C export D
- 统一导出
export {A, B, C, D}
首先,创建一个名为api.js的模块文件,用于封装使用axios进行网络请求的函数:
// api.js import axios from 'axios'; // 获取用户列表 export function getUsers() { return axios.get('/api/users'); } // 创建用户 export function createUser(user) { return axios.post('/api/users', user); } // 更新用户信息 export function updateUser(id, user) { return axios.put(`/api/users/${id}`, user); } // 删除用户 export function deleteUser(id) { return axios.delete(`/api/users/${id}`); }
然后,在另一个文件中,比如main.js中,可以导入并使用这些命名导出的函数:
// main.js import { getUsers, createUser, updateUser, deleteUser } from './api'; // 调用获取用户列表的函数 getUsers() .then(response => { console.log('用户列表:', response.data); }) .catch(error => { console.error('获取用户列表失败:', error); }); // 创建新用户 const newUser = { name: 'John Doe', age: 30 }; createUser(newUser) .then(response => { console.log('创建用户成功:', response.data); }) .catch(error => { console.error('创建用户失败:', error); }); // 更新用户信息 const userId = 123; const updatedUser = { name: 'Jane Smith', age: 25 }; updateUser(userId, updatedUser) .then(response => { console.log('更新用户信息成功:', response.data); }) .catch(error => { console.error('更新用户信息失败:', error); }); // 删除用户 const userIdToDelete = 456; deleteUser(userIdToDelete) .then(response => { console.log('删除用户成功:', response.data); }) .catch(error => { console.error('删除用户失败:', error); });
默认导出
export default
继续使用上面结合axios的例子
api.js
import axios from 'axios'; function getUsers() { return axios.get('/api/users'); } function createUser(user) { return axios.post('/api/users', user); } function updateUser(id, user) { return axios.put(`/api/users/${id}`, user); } function deleteUser(id) { return axios.delete(`/api/users/${id}`); } export default { getUsers, createUser, updateUser, deleteUser };
我们使用export default关键字将一个对象作为默认导出。注意,默认导出只能有一个。
然后,在另一个文件中,可以使用import api的形式来导入默认导出的模块:
import api from './api'; // 调用获取用户列表的函数 api.getUsers() .then(response => { console.log('用户列表:', response.data); }) .catch(error => { console.error('获取用户列表失败:', error); }); // 创建新用户 const newUser = { name: 'John Doe', age: 30 }; api.createUser(newUser) .then(response => { console.log('创建用户成功:', response.data); }) .catch(error => { console.error('创建用户失败:', error); }); // 更新用户信息 const userId = 123; const updatedUser = { name: 'Jane Smith', age: 25 }; api.updateUser(userId, updatedUser) .then(response => { console.log('更新用户信息成功:', response.data); }) .catch(error => { console.error('更新用户信息失败:', error); }); // 删除用户 const userIdToDelete = 456; api.deleteUser(userIdToDelete) .then(response => { console.log('删除用户成功:', response.data); }) .catch(error => { console.error('删除用户失败:', error); });
我们使用import api的语法导入了默认导出的模块,并将其命名为api。这样就可以直接使用api.getUsers、api.createUser等函数。
默认导出的好处是,在导入时不需要指定导出的名称,而是直接使用一个变量来引用整个导出模块。
引入方式
处女座可能会问,这多样性的导出方式,引入的时候会不会很痛苦啊?别怕,ES6的引入方式是非常人性化滴。
来来来,准备 m1
m2
m3
这三个模块
m1.js
对其分别暴露
export let school = '清华大学' export function teacher() { console.log("开发 5 G") }
m2.js
对其统一暴露
let school = '电子科技大学' function fgo() { console.log("China") } export {school, go}
m3.js
对其默认暴露
export default { school: '北京大学', go: function() { console.log("去往北京大学") } }
- 通用的导入方式
通用的导入方式:就类似你去货架上挑选商品,例如:
import * as m1 from './src/js/m1.js'
,这里的*
星号,代表着m1.js
导出的所有模块,都通通归放到名为m1
的这个变量中。
// 1. 引入 m1.js 模块内容 import * as m1 from './src/js/m1.js' // 2. 引入 m2.js 模块内容 import * as m2 from './src/js/m2.js' // 3. 引入 m3.js 模块内容 import * as m3 from './src/js/m3.js'
- 解构赋值形式
解构赋值形式:我就说嘛,JavaScript ES6 引入了这么现代化的特性,其中就肯定包括解构赋值啦。就比如:import {beauty} from './src/js/m4.js' ,这里的 {taech} 对你来说就是于从学校中精选你最喜欢的美女啦。
import {school, taech} from './src/js/m3.js' import {school as otherSchool, teacher} from './src/js/m2.js' import {default as m3} from './src/js/m3.js'
- 简写形式 【注意:只针对默认暴露】
简写形式:只针对默认暴露,听我说,这就类似于,作为一个孤胆英雄的模块,我就独自上场吧,那你作为引入方,直接用 import 声明一个变量,相应地引入我就行了。例如:import m3 from ‘./src/js/m3.js’ ,这种方式,简单粗暴,一目了然。
import m3 from './src/js/m3.js'
小结
嘿嘿,现在你有信心来驾驭这个 JavaScript 的模块化规范战场了吗?