深入了解Node模块原理

当我们编写JavaScript代码时,我们可以申明全局变量:

var s = 'global';

在浏览器中,大量使用全局变量可不好。如果你在a.js中使用了全局变量s,那么,在b.js中也使用全局变量s,将造成冲突,b.js中对s赋值会改变a.js的运行逻辑。

也就是说,JavaScript语言本身并没有一种模块机制来保证不同模块可以使用相同的变量名。

那Node.js是如何实现这一点的?

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

请注意我们编写的hello.js代码是这样的:

var s = 'Hello';
var name = 'world'; console.log(s + ' ' + name + '!');

Node.js加载了hello.js后,它可以把代码包装一下,变成这样执行:

var s = 'Hello';
var name = 'world'; console.log(s + ' ' + name + '!');

这样一来,原来的全局变量s现在变成了匿名函数内部的局部变量。如果Node.js继续加载其他模块,这些模块中定义的“全局”变量s也互不干扰。

所以,Node利用JavaScript的函数式编程的特性,轻而易举地实现了模块的隔离。

但是,模块的输出module.exports怎么实现?

这个也很容易实现,Node可以先准备一个对象module

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

可见,变量module是Node在加载js文件前准备的一个变量,并将其传入加载函数,我们在hello.js中可以直接使用变量module原因就在于它实际上是函数的一个参数:

module.exports = greet;

通过把参数module传递给load()函数,hello.js就顺利地把一个变量传递给了Node执行环境,Node会把module变量保存到某个地方。

由于Node保存了所有导入的module,当我们用require()获取module时,Node找到对应的module,把这个moduleexports变量返回,这样,另一个模块就顺利拿到了模块的输出:

var greet = require('./hello');

以上是Node实现JavaScript模块的一个简单的原理介绍。

module.exports vs exports

很多时候,你会看到,在Node环境中,有两种方法可以在一个模块中输出变量:

方法一:对module.exports赋值:

// hello.js

function hello() {
console.log('Hello, world!');
} function greet(name) {
console.log('Hello, ' + name + '!');
} module.exports = {
hello: hello,
greet: greet
};

方法二:直接使用exports:

// hello.js

function hello() {
console.log('Hello, world!');
} function greet(name) {
console.log('Hello, ' + name + '!');
} function hello() {
console.log('Hello, world!');
} exports.hello = hello;
exports.greet = greet;

但是你不可以直接对exports赋值:

// 代码可以执行,但是模块并没有输出任何变量:
exports = {
hello: hello,
greet: greet
};

如果你对上面的写法感到十分困惑,不要着急,我们来分析Node的加载机制:

首先,Node会把整个待加载的hello.js文件放入一个包装函数load中执行。在执行这个load()函数前,Node准备好了module变量:

var module = {
id: 'hello',
exports: {}
};

load()函数最终返回module.exports

var load = function (exports, module) {
// hello.js的文件内容
...
// load函数返回:
return module.exports;
}; var exported = load(module.exports, module);

也就是说,默认情况下,Node准备的exports变量和module.exports变量实际上是同一个变量,并且初始化为空对象{},于是,我们可以写:

exports.foo = function () { return 'foo'; };
exports.bar = function () { return 'bar'; };

也可以写:

module.exports.foo = function () { return 'foo'; };
module.exports.bar = function () { return 'bar'; };

换句话说,Node默认给你准备了一个空对象{},这样你可以直接往里面加东西。

但是,如果我们要输出的是一个函数或数组,那么,只能给module.exports赋值:

module.exports = function () { return 'foo'; };

exports赋值是无效的,因为赋值后,module.exports仍然是空对象{}。

结论

如果要输出一个键值对象{},可以利用exports这个已存在的空对象{},并继续在上面添加新的键值;

如果要输出一个函数或数组,必须直接对module.exports对象赋值。

所以我们可以得出结论:直接对module.exports赋值,可以应对任何情况:

module.exports = {
foo: function () { return 'foo'; }
};

或者:

module.exports = function () { return 'foo'; };

最终,我们强烈建议使用module.exports = xxx的方式来输出模块变量,这样,你只需要记忆一种方法。

参考地址:点我跳转

深入了解Node模块原理的更多相关文章

  1. node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理

    一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...

  2. node模块加载层级优化

    模块加载痛点 大家也或多或少的了解node模块的加载机制,最为粗浅的表述就是依次从当前目录向上级查询node_modules目录,若发现依赖则加载.但是随着应用规模的加大,目录层级越来越深,若是在某个 ...

  3. node模块系统常用命令

    node模块系统常用命令 命令 示例 备注 安装模块 npm install commander 最新版本 npm install commander@1.0.0 指定版本 npm install c ...

  4. Commonjs规范及Node模块实现

    前面的话 Node在实现中并非完全按照CommonJS规范实现,而是对模块规范进行了一定的取舍,同时也增加了少许自身需要的特性.本文将详细介绍NodeJS的模块实现 引入 nodejs是区别于java ...

  5. 模块机制 之commonJs、node模块 、AMD、CMD

    在其他高级语言中,都有模块中这个概念,比如java的类文件,PHP有include何require机制,JS一开始就没有模块这个概念,起初,js通过<script>标签引入代码的方式显得杂 ...

  6. NW.js安装原生node模块node-printer控制打印机

    1.安装原生node模块 #全局安装nw-gyp npm install -g nw-gyp #设置目标NW.js版本 set npm_config_target=0.31.4 #设置构建架构,ia3 ...

  7. 【转】Commonjs规范及Node模块实现

    前言: Node在实现中并非完全按照CommonJS规范实现,而是对模块规范进行了一定的取舍,同时也增加了少许自身需要的特性.本文将详细介绍NodeJS的模块实现 引入 nodejs是区别于javas ...

  8. [转]模块化——Common规范及Node模块实现

    Node在实现中并非完全按照CommonJS规范实现,而是对模块规范进行了一定的取舍,同时也增加了少许自身需要的特性.本文将详细介绍NodeJS的模块实现 引入 nodejs是区别于javascrip ...

  9. node 模块正确暴露方法

    一个node模块,为了能够服用,就需要将其暴露,那么如何正确写呢?(参考:https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Expr ...

随机推荐

  1. Nexus centos 安装

    目录 1.安装nexus 2.启动nexus 2.1启动服务器 2.2以后台进程启动: 2.3web访问 3.搭建私服 3.1 界面元素介绍 3.2 仓库集合的界面 3.3 通过网页方式将jar包上传 ...

  2. Entwurfsmuster

    1 Entwurfsmuster 1.1 Das Begriff Entwurfsmuster (englisch design patterns) sind bewährte Lösungsscha ...

  3. Java泛型的协变

    在上篇<Java泛型的基本使用>这篇文章中遗留以下问题,即将子类型也能添加到父类型的泛型中,要实现这种功能必须借助于协变. 实验准备 现在在上篇文章展示的Decorator类型的基础上,增 ...

  4. CSS小技巧(一)

    左右布局 将内部的子元素加浮动,父元素清除浮动即可. 代码: <!DOCTYPE html> <html> <head> <title>test< ...

  5. [转]How to add a script in a partial view in MVC4?

    本文转自:https://stackoverflow.com/questions/14114084/how-to-add-a-script-in-a-partial-view-in-mvc4 问题: ...

  6. jQuery几个易混淆之处(参考《众妙之门》及相关博客)

    parent() && parents() && closest() 这三个方法都与沿着DOM向上导航有关,在由选择器返回的元素上方,匹配父元素或之前的祖先元素,但是每 ...

  7. golang爬取免费代理IP

    golang爬取免费的代理IP,并验证代理IP是否可用 这里选择爬取西刺的免费代理Ip,并且只爬取了一页,爬取的时候不设置useAgent西刺不会给你数据,西刺也做反爬虫处理了,所以小心你的IP被封掉 ...

  8. Java学习--Calendar 类的应用

    Calendar 类的应用 Date 类最主要的作用就是获得当前时间,同时这个类里面也具有设置时间以及一些其他的功能,但是由于本身设计的问题,这些方法却遭到众多批评,不建议使用,更推荐使用 Calen ...

  9. Web开发 学习积累20161018

    项目 一.项目做的是什么 业务逻辑 -> 增删改查 二.什么是面向对象编程,它有哪些好处 oop:object oriented programming <>核心思想:使用人类思考问 ...

  10. Java中名词的解释

    在上一篇中说到了Java的四大特性,里面出现了很多名次,包括以后学习Java中也会出现很多常用到的名次,对初学者来说可能不知道是什么意思,或者是对这些刺耳的理解不是特别透彻,这里我就我自己的理解来解释 ...