本书所指的模块,都是代码模块,而非业务功能模块。
我们在前面学习了很多方法,用于拆分代码的规模。例如把一段逻辑封装为函数,把一些实例提炼成为类。
我们在用很多方式把一个大的问题,化解为小的问题。
模块化,就是在这种思路下提炼出来的工程化解决方案。因此,我们可以在一定的规范之下,引入别人已经封装好的模块用于解决自己的问题,而不需要自己重复封装。
模块最核心的思想是隔离。要有自己的内部属性,内部方法,以及自己来决定哪些属性与方法能够被其他模块访问。但是 JavaScript 在最初并没有模块相关的支持语法。因此我们只能利用许多别的类似的,具备隔离属性的方式来模拟模块。
了解模块发展历程有助于增加我们的知识厚度。目前在实践中,我们仅使用最新的模块化语法。
1、函数自执行
因为没有模块化语法,我们常常用自执行函数来模拟模块。
10// 自执行函数模拟模块化2030// Person 模块40(() => {50// 实例个数,模块内部变量,外部无法直接访问,60let number = 070function Person(name, age) {80number ++90this.name = name10this.age = age11}1213Person.prototype.getName = function() {14return this.name15}1617Person.getInstanceNumber = function() {18return number19}2021// 对外抛出接口22window.Person = Person23})();242526// main 模块27(() => {28// 引入模块29const Person = window.Person3031const p1 = new Person('Tom', 20)32const p2 = new Person('Jake', 20)33const p3 = new Person('Alex', 20)3435p1.getName()3637console.log('实例化个数', Person.getInstanceNumber())38})()
Node 应用的模块,就是采用 CommonJS 规范来实现。
在 nodejs 中,每一个文件就是一个模块,有自己的作用域。因此,在该文件中,定义的变量、函数、类都是私有的。
每个模块内部,module
用于代表当前模块。
并且使用 module.exports
对外暴露接口。
在其他模块,可以使用 require
加载该模块,加载的结果,就是 module.exports
的合集。
10// person 模块20// person.js30let number = 040function Person(name, age) {50number++60this.name = name70this.age = age80}9010// 对外暴露接口11Person.prototype.getName = function () {12return this.name13}1415// 对外暴露接口16module.exports.getInstanceNumber = function () {17return number18}19module.exports.Person = Person
10// main.js20// 引入模块30const person = require('./person.js')4050const {Person, getInstanceNumber} = person6070const p1 = new Person('Tom', 20)80const p2 = new Person('Jake', 20)90const p3 = new Person('Alex', 20)1011p1.getName()12p2.getName()13p3.getName()1415console.log('实例化个数', getInstanceNumber())16
AMD 是适用于浏览器环境的异步加载模块规范,它是一种依赖前置的规范。
依赖前置:所有的 require 都会提前执行
require.js
与 curl.js
实现了该规范。该规范使用 define
定义一个模块
10// person.js20define(function() {30let number = 040function Person(name, age) {50number++60this.name = name70this.age = age80}9010// 对外暴露接口11Person.prototype.getName = function () {12return this.name13}1415function getInstanceNumber() {16return number17}1819// 对外暴露接口20return {21getInstanceNumber,22Person23}24})
10// main.js20// 引入模块30define(['./person.js'], function(person) {40const { Person, getInstanceNumber } = person5060const p1 = new Person('Tom', 20)70const p2 = new Person('Jake', 20)80const p3 = new Person('Alex', 20)9010p1.getName()11p2.getName()12p3.getName()1314console.log('实例化个数', getInstanceNumber())15})16
CMD 规范是模仿 CommonJS,由阿里玉伯提出,sea.js
实现了该规范,这是一种就近依赖的规范
就近依赖:下载完之后,并不执行加载,回调函数中遇到 require 时才执行
10// person.js20define(function(require, exports, module) {30let number = 040function Person(name, age) {50number++60this.name = name70this.age = age80}9010// 对外暴露接口11Person.prototype.getName = function () {12return this.name13}1415// 对外暴露接口16module.exports.getInstanceNumber = function () {17return number18}19module.exports.Person = Person20})
1020// mian.js30define(function(require) {40const person = require('./person.js')50const { Person, getInstanceNumber } = person6070const p1 = new Person('Tom', 20)80const p2 = new Person('Jake', 20)90const p3 = new Person('Alex', 20)1011p1.getName()12p2.getName()13p3.getName()1415console.log('实例化个数', getInstanceNumber())16})
UMD 是一个兼容写法,一个开源模块可能会提供给 CommonJS 标准的项目中实现,也可能提供给 AMD 标准的项目使用。UMD 应运而生。
10(function(root, factory) {20if (typeof define === 'function' && define.amd) { // AMD30define(['person'], factory)40} else if (typeof define === 'function' && define.cmd) { // CMD50define(function(require, exports, module) {60module.exports = factory()70})80} else if (typeof exports === 'object') { // CommonJS90module.exports = factory()10} else { // global11root.person = factory()12}13})(this, function() {14let number = 015function Person(name, age) {16number++17this.name = name18this.age = age19}2021// 对外暴露接口22Person.prototype.getName = function () {23return this.name24}2526function getInstanceNumber () {27return number28}2930return {31Person,32getInstanceNumber33}34})
很多开源模块都会采用这种兼容性的写法。
ES6 提出了新的模块化语法规范。
这也是目前我们在实践开发中,使用得最多的规范。
10// person.js20let number = 030export function Person(name, age) { // 暴露接口40number++50this.name = name60this.age = age70}8090// 对外暴露接口10Person.prototype.getName = function () {11return this.name12}1314// 对外暴露接口15export const getInstanceNumber = function () {16return number17}
10// main.js20// 引入模块30import {Person, getInstanceNumber} from './person.js'4050const p1 = new Person('Tom', 20)60const p2 = new Person('Jake', 20)70const p3 = new Person('Alex', 20)8090p1.getName()10p2.getName()11p3.getName()1213console.log('实例化个数', getInstanceNumber())