视频5
随着JavaScript的不断发展,以及CPU、浏览器性能的提升,很多页面逻辑迁移到了客户端(表单验证等),随着Web 2.0时代的到来,Ajax技术得到广泛应用,jQuery等前端库层出不穷,前端代码日益复杂,因为JavaScript没有类的概念,以其简单的代码组织规范不足以驾驭规模越来越庞大的代码。
JavaScript一开始没有模块系统,之后出现几大类模块系统,使得代码组织和管理逐渐规范,这些模块系统可以统称为JavaScript模块系统,它实现了从文件层面上对变量、函数、类等各种JS内容的隔离封装,为这些内容划出了边界,并开放可互相沟通的入口。
模块通常是指编程语言所提供的代码组织机制,利用此机制可将程序拆解为独立且通用的代码单元。模块化主要解决代码分割、作用域隔离、模块之间的依赖管理,以及发布到生产环境时的自动化打包与处理等多个方面的问题。
有了模块系统,就能更好地归类、划分不同职责的代码。划分的原则还是以业务和非业务功能为基础,尽量将业务上相关联的代码(包括只在该业务中所使用的工具代码)组织在同一个模块中;而和业务无关的、其他模块通用的代码,可以按功能分类组织在一个或多个模块中。
可维护性。因为模块是独立的,一个设计良好的模块会使外部的代码对自己的依赖逐渐减少,这样自己就可以独立更新和改进。
命名空间。在 JavaScript 中,如果一个变量在最顶级的函数之外声明,它就直接变成全局可用。因此,常常不小心出现命名冲突的情况。使用模块化开发来封装变量,可以避免污染全局环境。
重用代码。通过模块引用的方式,来避免代码编写重复。
一个模块就是实现特定功能的文件。借助模块,可以更方便地通过加载模块来复用代码。模块开发需要遵循一定的规范,通用的 JavaScript 模块规范主要有 3 种:AMD、CMD 和CommonJS。
异步模块定义(Asynchronous Module Definition,AMD),是JavaScript在浏览器端模块化开发的规范,采用异步方式加载模块,这样在加载模块时不影响其后面语句的运行。依赖该模块的语句都定义在回调函数中,模块加载完成之后,回调函数才会运行。由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到前端模块化管理工具库——require.js,实际上AMD是require.js在推广过程中对模块定义的规范化产出。因为AMD模块化规范需要require.js文件的支持,所以必须先下载并引入require.js。
require.js文件主要解决两个问题。
(1)加载多个JS文件时,文件间可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器,require.js 能实现 JS 文件的异步加载,管理模块之间的依赖性,便于代码的编写与维护。
(2)JS 加载的时候浏览器会停止页面渲染,加载文件越多,页面响应时间越长,require.js可以避免网页失去响应。
AMD采用require()语句加载模块,它要求有两个参数:require([module], callback);。
第一个参数[module]是一个数组,里面的成员即要加载的模块,第二个参数是回调函数callback(),当前面的模块加载成功后被调用,加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。
【示例2.1】 用AMD规范定义一个计算两数相乘的模块并在页面中调用。
(1)module.js——定义一个计算两数相乘的模块
// 定义一个用于计算两数相乘的函数 define(function() { let multiply = function(x, y) { return x * y; } return{// 得出multiply()函数的运算结果 multiply }; });
定义了一个模块,实现两数相乘的函数multiply(),模块返回该函数的运行结果。
(2)callMmodule.html——调用module.js,利用模块开发计算两数的乘积
<html> <head> <meta charset="utf-8"> <title>AMD模块化开发</title> </head> <body> </body> <!-- 引入依赖文件 --> <script src="require.js"></script> <script> // 加载模块 require(['module.js'], function(obj) { alert(obj.multiply(10,20));// 结果为200 }); </script> </html>
运行结果如图2-1所示。
图2-1 运行结果
先下载并引入 require.js 文件才能获得 require.js 文件的支持。require()加载之前定义模块module.js,加载依赖函数时是异步加载的,这样浏览器不会失去响应,其指定的回调函数只在模块加载成功后才会运行。在调用函数multiply()时,必须加上function函数的参数,即模块名obj。
通用模块定义(Common Module Definition,CMD)的规范与AMD相近,使用更加方便,支持中文版。正如AMD有require.js,CMD有一个浏览器的实现sea.js。sea.js解决的问题和require.js类似,但在模块定义方式和模块加载时机上有所不同。
AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块。而CMD推崇就近依赖,只有在用到某个模块的时候才去调用 require()。AMD在加载模块后就会执行该模块,所有模块都加载、执行完后会进入require()回调函数,执行主逻辑,依赖模块的执行顺序和书写顺序不一定一致,先下载下来的依赖模块先执行,但是主逻辑一定在所有依赖模块加载完成后才执行。
CMD模块化规范需要sea.js文件的支持,所以必须下载并引入sea.js。
【示例2.2】 CMD模块相互调用。
该示例一共有7个文件,运行页面为index.html,页面文件的存放目录如图2-2所示。
模块文件引用关系如图2-3所示。
图2-2 文件目录
图2-3 模块文件引用关系
(1)主模块main.js——在HTML文件中使用seajs.use('./js/modules/main')调用
define(function (require) { var m1 = require('./module1') var m4 = require('./module4') m1.show() m4.show() })
主模块加载两个模块,分别调用module1和module4的show()函数。
(2)module1.js——用CMD规范定义一个模块
define(function (require, exports, module) { //内部变量数据 var data = 'sea.js采用的是CMD规范。'; //内部函数 function show() { console.log('模块1:' + data); } //向外暴露 exports.show = show; })
module1的show()函数向外暴露,用户在控制台输出文字“模块1:sea.js采用的是CMD规范。”。
(3)module4.js——用CMD规范定义一个模块
define(function (require, exports, module) { //引入依赖模块(同步) var module2 = require('./module2') function show() { console.log('调用模块4:' + module2.msg) } exports.show = show //引入依赖模块(异步)后执行,因为是异步的,所以主线的模块执行完才会执行引入的依赖模块 require.async('./module3', function (m3) { console.log('异步引入依赖模块3:' + module3.APl_KEY) }) })
module4以同步方式引入模块module2,调用module2中暴露的变量msg,值为“中慧教育”,module4的show()函数向外暴露,用户在控制台输出文字“调用模块4:中慧教育”。
module4以异步方式引入module3,调用其暴露的变量APl_KEY,值为“zhonghui123456”。因为是异步的,所以主线的模块执行完才会执行引入的依赖模块,控制台输出“异步引入依赖模块3:zhonghui123456”。
(4)module2.js——用CMD规范定义一个模块
define(function (require, exports, module) { module.exports = { msg: '中慧教育' } })
module2向外暴露变量msg,值为“中慧教育”。
(5)module3.js——用CMD规范定义一个模块
define(function (require, exports, module) { const APl_KEY = 'zhonghui123456' exports.APl_KEY = APl_KEY })
module3向外暴露变量APl_KEY,值为“zhonghui123456”。
(6)index.html——调用main.js的页面
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>02-模块化规范-CMD</title> </head> <body> </body> <script src="js/libs/sea.js"></script> <script> seajs.use('./js/modules/main') </script> </html>
运行结果如图2-4所示。
图2-4 运行结果
首先下载并引入模块sea.js,通过main模块调用其他模块,在浏览器控制台输出3行语句。
若网页端没有采用模块化编程,页面的JavaScript逻辑会十分复杂,但在服务器端通常要采用模块化编程,用于与操作系统和其他应用程序互动。
CommonJS 始于 Mozilla 的工程师2009年开始的一个项目,旨在让浏览器之外的JavaScript(比如服务器端或桌面端)能够通过模块化的方式来开发和协作。需要注意的是,CommonJS 规范的主要适用场景是服务器端编程。Node.js 的模块系统就是参照 CommonJS规范实现的。
在CommonJS规范中,每个JavaScript 文件就是一个独立的模块,在模块中默认创建的属性都是私有的。也就是说,在一个文件中定义的变量(还包括函数和类)对其他文件是不可见的。该模块实现方案允许某个模块对外暴露部分接口并且由其他模块导入使用。
CommonJS 定义的模块关键字有模块标识(module)、模块导出(exports)和模块引用(require)。模块只有一个出口是module.exports对象,可把模块中希望输出的函数或变量放入该对象中导出。可以使用require加载模块,读取一个文件并执行,返回文件内部的module.exports对象。
【示例2.3】 CommonJS规范定义一个模块并进行调用。
(1)moduleA.js——CommonJS规范定义一个模块
module.exports = function(value) { return value * 2; }
使用module.exports导出一个函数,函数有形参value。return语句返回函数值,根据给定值计算2倍值输出。
(2)moduleB.js——调用模块moduleA.js
let multiplyBy2 = require('./moduleA'); let result = multiplyBy2(4); console.log(result);
运行显示为8。
引入当前目录下的模块文件 moduleA.js,命名为 multiplyBy2。调用模块中定义的函数进行计算,将返回值赋值给变量result,值为8。当引入用户自定义模块时,如果是同级目录,则“. /”不能省略。
CommonJS属于后台模块化规范,要用node或者nodemon运行moduleB.js文件。
任选一种模块化开发方式在控制台输出九九乘法口诀表。
(1)multiplication_formula.js——用来模块化实现输出九九乘法口诀表
// 输出九九乘法口诀表 let m_f=(function(){// 定义llFE(自执行函数表达式) let rs = ''; for (let i = 9; i >= 1; i--) { for (let j = 1; j <= i; j++) { rs += j + '*' + i + '=' + i * j + ' '; } rs += '\n'; } return rs; })(); module.exports = m_f;
以上代码定义一个函数m_f()并通过module.exports向外导出。函数内使用双重循环,实现九九乘法并在一个字符串rs中拼接输出,函数的返回值为rs。
(2)result.js——调用上述模块,输出九九乘法表
let m_f = require('./multiplication_formula'); console.log(m_f);
运行结果如图2-5所示。
图2-5 运行结果
以上代码引入同名目录下的模块文件multiplication_formula,使用nodemon命令运行,结果将函数返回值打印在控制台。