前端入门22-讲讲模块化 (3)

上面只是简单介绍了下模块化的发展历程,而且中间略过一些阶段,想看详细的可以去原文阅读。下面就具体介绍下,目前四个比较稳定的模块技术:

CommonJS规范

由于 CommonJS 是针对服务端设计的模块化规范,对于 Node.js 来说,一个 JS 文件就是一个模块,所以并不需要类似 AMD 或 CMD 中的 define 来声明一个模块。这是我的理解。

exports

既然在 Node.js 中,每个 JS 文件就是一个模块,那么这个模块文件内的变量都是对外隐藏的,外部无权访问,只能访问 exports 对外暴露的接口,如:

//module.js var name = "dasu"; var wx = "dasuAndroidTv"; var getBlog = function() { return "http://www.cnblogs.com/dasusu/"; } //以上变量和函数,外部都无权访问,外部只能访问到下面通过 exports 暴露的接口 module.exports.name = name; exports.getWx = () => wx;

模块内,可以通过对 module.exports 或 exports 添加属性来达到对外暴露指定接口的目的,当然,从程序上来说,你可以直接改变 module.exports 的指向,让它指向一个新对象而不是在原本对象上添加属性,这个就类似于对构造函数 prototype 属性的修改。

但建议使用方式还是尽可能在 exports 对象上添加属性。

如果有想探究它的原理的话,可以尝试利用 Browserify 来转换这段模块代码,看看最后生成的是什么:

function(require,module,exports){ //module.js var name = "dasu"; var wx = "dasuAndroidTv"; var getBlog = function() { return "http://www.cnblogs.com/dasusu/"; } //以上变量和函数,外部都无权访问,外部只能访问到下面通过 exports 暴露的接口 module.exports.name = name; exports.getWx = () => wx; }

虽然,对于 Node.js 来说,它其实对待每个 JS 文件,本质上,会将文件内的代码都放于一个函数内,如果该模块首次被其他模块引用了,那么就会去执行这个函数,也就是执行模块内的代码,由于函数本身有三个参数,其中有两个分别是:module 和 exports,这也是内部为什么可以直接通过 module.exports 或 exports 来操作的原因。

Module 对象是 Node.js 会为每个模块文件创建的一个对象,模块之间的通信,其实就是通过访问每个 Module 对象的属性来实现。

所以,说白了,CommonJS 模块化技术的本质,其实就是利用了函数的局部作用域的特性来实现模块作用域,然后结合一个对象作为命名空间的方式,来保存模块内部需要对外暴露的信息方式。最后,通过 require 命令来组织各模块之间的依赖关系,解决以前方案没有管理者角色的局限,解决谁先加载谁后加载的问题。

require

每个 JS 文件其实都被当做一个模块处理,也就是文件内的代码都会被放入到一个函数内,那这个函数什么时候执行呢?也就是说,模块什么时候应该被加载呢?

这就是由 require 命令决定,当某个模块内使用了 require 命令去加载其他模块,那么这时候,被加载的模块如果是首次被调用,它是没有对应的 Module 对象的,所以会去调用它的函数,执行模块内代码,这个过程是同步的,这期间会完善这个模块的 Module 对象信息。之后,其他模块如果也引用了这个模块,因为模块内代码已经被执行过了,已经存在对应的 Module 对象,所以此时就不会再重复去加载这个模块了,直接返回 Module 对象。

以上,基本也就是模块间依赖的加载处理过程。而 require 命令用法很简单:

//main.js var module1 = require("./module"); module1.name; //输出=> dasu module1.getWx(); //输出 => dasuAndroidTv //module1.getBlog(); //没有权限访问

其实,Node.js 对 main.js 的处理也是认为它是个模块,所以文件内的代码也都放入一个函数内,还记得函数的第一个参数就是 require 么,这也就是为什么模块内可以直接使用 require() 的原因,require 其实本质上是一个函数,具体的实现是 Node.js 的一个内置方法:Module._load(),主要工作上一节有介绍过了。

说得稍微严谨点,Node.js 其实才是作为各模块之间的管理者,由它来管控着哪个模块先加载,哪个后加载,维护着各模块对外暴露的信息。

到这里再来理解,有些文章中对 Module 对象的介绍:

module.id 模块的识别符,通常是带有绝对路径的模块文件名。

module.filename 模块的文件名,带有绝对路径。

module.loaded 返回一个布尔值,表示模块是否已经完成加载。

module.parent 返回一个对象,表示调用该模块的模块。

module.children 返回一个数组,表示该模块要用到的其他模块。

module.exports 表示模块对外输出的值。

这时,对于 Module 对象内的各属性用途,理解应该会比较容易点了。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyzgpj.html