likes
comments
collection
share

JS模块化的理解与开发

作者站长头像
站长
· 阅读数 2

(看完本篇文章您将知晓js模块化的由来,模块的引入与开发,模块化规范;结尾附带经典面试题)

1.理解

1.1 说明

在学习一个技术之前,我们最好先了解一下它的历史与由来,这样可以帮助我们理解这个技术的本质和发展过程,从而更深入地掌握它

1.2 历史

第一阶段:JavaScript语言刚刚诞生,主要用于实现页面中的一些小效果。在这个阶段,前端工作往往由后端工程师顺带完成。在1996年,NetScape将JavaScript语言提交给欧洲的一个标准制定组织ECMA。

第二阶段:随着ajax的出现,JavaScript开始和服务器之间进行交互,前端程序逐渐变得复杂。然而,前端开发还有几个大的问题没有解决,包括浏览器解释执行JS的速度太慢,用户端的电脑配置不足,以及更多的代码带来了全局变量污染、依赖关系混乱等问题。

第三阶段:2008年,谷歌的V8引擎发布,将JS的执行速度推上了一个新的台阶。2009年,Ryan Dahl发布了nodejs,它基于开源的V8引擎,对源代码作了一些修改。nodejs的诞生,便把JS中的最后一个问题放到了台前,即全局变量污染和依赖混乱问题。经过社区的激烈讨论,最终,形成了一个模块化方案,即鼎鼎大名的CommonJS。

第四阶段:CommonJS的出现打开了前端开发者的思路。于是,开始有人想办法把CommonJS运用到浏览器中。然后,AMD规范和CMD规范相继出炉。2015年,ES6发布,它提出了官方的模块化解决方案——ES6 模块化。从此以后,模块化成为了JS本身特有的性质,这门语言终于有了和其他语言较量的资本,成为了可以编写大型应用的正式语言。

在这个阶段,出现了各种前端开发框架,如Angular、React、Vue等,以及后端开发框架,如Express、Koa等。npm包管理器出现,实用第三方库变得极其方便。webpack等构建工具出现,专门用于打包和部署。此外,还出现了各种可以使用JS语言开发的桌面应用程序、移动端应用程序和小程序。

1.3 总结

模块化是一种解决问题的方法,它将复杂系统划分为多个独立、可组合、可分解和可更换的模块。在编程领域,模块化意味着遵守一定规则,将大文件拆分成多个相互依赖的小模块。每个模块封装不同的功能并提供使用接口,它们之间互不影响,按需加载。 模块化的主要好处包括: 高内聚低耦合:有利于团队协作开发。当项目复杂时,可以划分为子模块分给不同人开发,最后组合在一起。这降低了模块间的依赖关系,体现了低耦合;同时,每个模块有特定功能,体现了高内聚。 可重用性和维护性:模块具有特定功能,可以在多个项目中重复使用,避免了重复编写代码。当功能需要变更时,只需修改相应模块,方便维护。 避免命名冲突和全局变量污染:通过 exports 暴露接口,无需使用全局变量或命名空间,从而避免了命名冲突。 内置依赖管理:通过 require 引入依赖,使依赖关系内置于模块中。开发者只需关注当前模块的依赖,其他事情由模块加载器(如 Sea.js 或 Require.js)自动处理。 总的来说,模块化可以提高代码的可读性、可维护性和可重用性,降低复杂性,并促进团队协作开发。

2.开发与规范

2.1 commomJS

规范:

一个文件就是一个模块,每个模块都有自己的作用域,模块之间的变量不会相互污染 模块的导入和导出是通过require、module.exports实现的 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。

代码:

导出:

// math.js,以下二选一
// 单个导出
export function add(a, b) {
	return a + b;
}
export function subtract(a, b) {
	return a - b;
}
//统一导出
module.export = {
	add = function(){},
	subtract = function(){}
}

引入:

// app.js
var math = require('./math');
console.log(math.add(1, 2)); // 输出 3
console.log(math.subtract(3, 1)); // 输出 2

注意:

在CommonJS中,exports和module.exports都可以用来导出模块的公共接口供其他模块使用,但它们之间存在一些区别1:exports:它是module.exports的一个引用。当你为exports添加属性时,实际上是在module.exports对象上添加属性。 使用 require() 方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准。

2.2 AMD

规范:

AMD全称是Asynchronous Module Definition,即异步模块加载机制。 是一种异步加载模块的规范,它允许异步加载 JavaScript 模块,而不阻塞浏览器。AMD 规范要求定义一个函数,这个函数就是模块的定义,并且这个函数只在定义完成后才运行。遵循AMD规范的主要指requirejs

代码:

requirejs官网链接:requirejs.org/docs/downlo…,下载后将文件导入项目即可使用

导出:

// math.js
define(['dependency'], function(dependency) {
	function add(a, b) {
		return a + b;
	}
	
	function subtract(a, b) {
		return a - b;
	}
	
	return {
		add: add,
		subtract: subtract
	};
});

引入:

// app.js
require(['./math'], function(math) {
	console.log(math.add(1, 2)); // 输出 3
	console.log(math.subtract(3, 1)); // 输出 2
});

2.3 CMD

规范:

CMD 全称是Common Module Definition,即通用模块定义。是另一种异步加载模块的规范,它类似于 AMD,但更加灵活。CMD 允许模块按需加载,并且可以异步或同步地加载依赖项。CMD 并没有强制模块必须异步加载,这是它与 AMD 的主要区别。

代码:

将sea.js导入文件后使用

导出:

// math.js
define(function(require, exports, module) {
	var dependency = require('dependency');
	function add(a, b) {
		return a + b;
	}
	function subtract(a, b) {
		return a - b;
	}
	
	exports.add = add;
	exports.subtract = subtract;
});

引入:

// app.js
define(function(require, exports, module) {
	var math = require('./math');
	
	console.log(math.add(1, 2)); // 输出 3
	console.log(math.subtract(3, 1)); // 输出 2
});

2.4 ES6模块化

规范:

ES6(ECMAScript 2015)引入了原生的模块化支持,它提供了一种更加简洁、易于理解的模块定义和导入方式。ES6 模块支持静态导入和导出,可以在编译时确定依赖关系,并且模块只会被加载一次。 导出变量或函数:使用export关键字将变量或函数导出,其他模块可以通过import语句引入。 导入模块:使用import语句引入其他模块,可以指定需要引入的变量或函数。 默认导出:可以通过export default语法来指定一个模块的默认导出,其他模块可以直接导入该默认导出。

代码:

导出

// mathFunctions.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

const multiply = (a, b) => a * b;
export default multiply;

引入

// anotherFile.js
import multiply, { add, subtract } from './mathFunctions.js';

console.log(multiply(2, 3)); // 输出 6
console.log(add(1, 2)); // 输出 3
console.log(subtract(3, 1)); // 输出 2

注意:

一个模块中export和export default可以同时存在,但一个模块中只能有一个export default。export可以有多个,用于导出多个变量或函数。 通过export方式导出,在导入时要加{},且要为导入的变量或函数起别名。通过export default方式导出,在导入时可以不加{},且可以为导入的变量或函数起任意名字。

3.经典面试题

3.1 什么是模块化?为什么需要模块化?

模块化是指将一个复杂的系统或程序划分为一系列独立、可互操作的组件或模块的过程。每个模块负责特定的功能或任务,并通过定义好的接口与其他模块进行通信。模块化的目的是提高代码的可维护性、可重用性、可测试性和可扩展性。

为什么需要模块化?

可维护性:模块化使得代码更易于理解和维护,因为每个模块的功能相对独立且清晰。当某个模块出现问题时,开发人员可以迅速定位并修复问题,而不需要对整个系统进行全面的检查。

可重用性:通过模块化,可以将通用的功能封装成独立的模块,并在多个项目中重复使用。这减少了代码冗余,提高了开发效率。

可测试性:模块化使得代码更易于测试。开发人员可以针对每个模块编写测试用例,以确保模块的功能正确性。这有助于在开发过程中及时发现和修复问题。

可扩展性:模块化架构使得系统更易于扩展。当需要添加新功能或修改现有功能时,只需修改相应的模块,而不会影响整个系统的稳定性。

3.2. AMD、CMD、CommonJS与ES6模块化的区别是什么?

AMD和CMD是两种在浏览器中实现模块化的规范,而CommonJS是Node.js环境中使用的模块化规范。ES6模块化是ECMAScript 2015标准中引入的模块化系统。

AMD:AMD规范采用异步方式加载模块,模块加载不影响它后面语句的运行。它依赖一个名为requirejs的JavaScript库来运行。AMD推崇依赖前置,即在定义模块的时候就要声明其依赖的模块。

CMD:CMD规范也是针对浏览器端的模块化规范,由国内的玉伯提出。CMD推崇依赖就近,只有在需要的时候才require。CMD模块系统更加适合在浏览器环境中异步加载模块。

CommonJS:CommonJS是Node.js的模块化规范,采用同步加载模块的方式,即在加载模块时,如果模块不存在,则抛出异常。CommonJS规范定义了一个全局性的方法require(),用来加载模块,以及一个全局性的对象module,用来表示当前模块。

ES6模块化:ES6模块化是JavaScript语言层面的规范,它使用export和import关键字来实现模块的导出和导入。ES6模块是静态的,编译时就能确定模块的依赖关系,因此更加适合在打包工具中使用,如Webpack、Rollup等。

3.3. export是什么?module.exports、exports、export default有什么区别?

export:用于导出模块中的变量、函数或类。其他模块可以通过import关键字来导入这些导出的内容。export可以导出多个项目,并且在导入时要使用花括号{}来指定要导入的具体内容。

module.exports:这是CommonJS模块化规范中的语法,用于导出模块的内容。module.exports可以导出任何类型的值,包括函数、对象、字符串等。在其他模块中,通过require()函数来加载这个模块,并获取module.exports导出的内容。

exports:exports是module.exports的一个快捷方式,它们指向同一个对象。通常情况下,我们可以直接使用exports来导出模块内容,而不需要显式地使用module.exports。但是,如果直接给exports赋一个新的值(例如exports = someValue),那么它就不再指向module.exports,这通常是不推荐的做法。

export default:export default用于导出模块的默认内容。每个模块只能有一个默认导出。在导入时,可以为默认导出的内容起任意名字,因为它不依赖于导出时的名称。export default通常用于导出一个模块的主要功能或对象,这样其他模块在导入时可以更灵活地使用它。

3.4. 在大型项目中,如何管理和维护模块之间的关系?

明确模块职责:每个模块应该只负责一个特定的功能或任务,并且应该清晰地定义其职责和接口。这有助于减少模块之间的耦合度,提高系统的可维护性。

依赖管理:使用依赖管理工具(如npm、yarn等)来管理项目中的依赖关系。这些工具可以帮助我们安装、更新和删除依赖模块,确保项目中的依赖关系一致且正确。

模块化架构:采用分层或分块的模块化架构来组织代码。例如,可以将项目划分为不同的层级

3.5. require和import引入的区别

所属规范不同:require是CommonJS的语法,import是ES6的语法标准。

加载方式不同:require是动态加载,在运行时加载模块里的所有方法。import是静态加载,编译的时候调用,不管在哪里引用都会提升到代码顶部。

导出方式不同:require导出是值的拷贝,引入的是整个模块里面的对象。import导出的是值的引用,可以按需引入模块里面的对象。

用途不同:require主要用于加载CommonJS模块、AMD模块、UMD模块以及Node.js内置模块等多种类型的模块。import主要用于加载ES6模块。