深入浅出之模块

faichou.zh@gmail.com

2016.10.05

Node 模块原理

CommonJS 规范

Node应用由模块组成,采用CommonJS模块规范。CommonJS 规范是一种模块加载机制,在此规范下,每个.js文件都是一个模块,有自己的作用域,在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。例如,hello.jsmain.js都申明了全局变量 var name = 'FaiChou', 但互不影响。

一个模块想要对外暴露变量(包括函数),可以用 module.exports = variable;,一个模块要引用其他模块暴露的变量,用 var ref = require('module');就拿到了引用模块的变量。

深入了解模块原理

我们都知道C语言中全局变量的链接性,在两个 .c 文件中同时声明 int a,那么在链接过程中会产生重复定义的错误。相同的,在编写 js 代码时候也会有类似的问题,在浏览器中,大量使用全局变量是愚蠢的,我们尽量避免。JavaScript语言本身并没有一种模块机制来保证不同模块可以使用相同的变量名。C语言是通过 static 关键字来避免此效应的,那么 Node.js 是如何实现这一点的呢?

其实要实现“模块”这个功能,并不需要语法层面的支持。Node.js也并不会增加任何JavaScript语法。实现“模块”功能的奥妙就在于JavaScript是一种函数式编程语言,它支持闭包。如果我们把一段JavaScript代码用一个函数包装起来,这段代码的所有“全局”变量就变成了函数内部的局部变量。

var name = 'FaiChou';

经过 Node.js 包装过后执行是这样的


(function() {
    var name = 'FaiChou';
})();

通过这样的方式,本来全局变量 name 则变成了局部变量。


module.exports的实现

Node内部提供一个Module构建函数。所有模块都是Module的实例。


function Module(id, parent) {
    this.id = id;
    this.exports = {};
    this.parent = parent;
// ...

每个模块内部,都有一个module对象,代表当前模块。
下面是一个简化的示例


// 准备module对象:
var module = {
    id: 'hello',
    exports: {}
};
var load = function (module) {
    /** 读取的hello.js代码 */
    var name = 'FaiChou';
    module.exports = name;
    /** hello.js代码结束 */
    return module.exports;
};
var exported = load(module);
// 保存module:
save(module, exported);

变量 module 是Node在加载js文件前准备的一个变量,并将其传入加载函数,我们在 hello.js 中可以直接使用变量 module 原因就在于它实际上是函数的一个参数。Node保存了所有导入的 module,当我们用require()获取 module 时,Node找到对应的 module,把这个 module 的 exports 变量返回,这样,另一个模块就顺利拿到了模块的输出var name = require('./hello');

在开发中经常看到 import React from 'react';import { Component } from 'react';, 另外还有 const hello = require('./Header').hello;这样的,其实都是对模块的导入,没有加大括号的(import React …)只是在 export 时候用的是 export default

参考链接

[1] 阮一峰,CommonJS规范

[2] 廖雪峰,JavaScript教程

发表评论

电子邮件地址不会被公开。 必填项已用*标注