• nodejs模块语法与开闭原则
  • nodejs模块的底层实现

一、nodejs模块语法与开闭原则

关于nodejs模块我在之前的两篇博客中都有涉及,但都没有对nodejs模块的底层做做任何探讨,但是为了使相关内容更方便查看比对理解,这里还是先引入一下之前两篇博客的连接:

js模块化入门与commonjs解析与应用

ES6入门十二:Module(模块化)

1.1 exports、module.exports、require()实现模块导出导入:

 //示例一:导出原始值数据
//a.js--用于导出数据
let a = 123;
module.exports.a=a;
//inde.js--用于导入a模块的数据
let aModule = require('./a.js');
console.log(aModule.a); // //示例二:导出引用值数据
//a.js--同上
function foo(val){
console.log(val);
}
module.exports.foo = foo;
//index.js--同上
let aModule = require('./a.js');
let str = "this is 'index' module"
aModule.foo(str); //this is 'index' module //示例三:导出混合数据
a.js--同上
let a = 123;
function foo(val){
console.log(val);
}
module.exports = {
a:a,
foo:foo
}
//inde.js--同上
let aModule = require('./a.js');
let str = "this is 'index' module"
console.log(aModule.a);//
aModule.foo(str); //this is 'index' module

在上面这些示例中,没有演示exports的导出,暂时可以把它看作与同等于module.exports,例如:

 //a.js -- 导出模块
let a = 123;
function foo(val){
console.log(val);
}
exports.a = a;
exports.foo = foo; //inde.js -- 引用模块a
let aModule = require('./a.js');
let str = "this is 'index' module"
console.log(aModule.a);//
aModule.foo(str); //this is 'index' module

但是使用exports导出模块不能这么写:

 //a.js
let a = 123;
function foo(val){
console.log(val);
}
exports = {
a:a,
foo:foo
} //index.js
let aModule = require('./a.js');
let str = "this is 'index' module"
console.log(aModule);// {} -- 一个空对象

至于为什么不能这么写,暂时不在这里阐述,下一节关于nodejs模块底层实现会具体的分析介绍,这里先来介绍nodejs模块的一个设计思想。

1.2 nodejs模块的开闭原则设计实现

 //a.js -- 导出模块
let num = 123;
let str = "this is module 'a'";
exports.a = a; //index.js -- 引用模块a
let aModule = require('./a.js');
console.log(aModule.num);//
console.log(aModule.str);//undefined

这里你会发现只有被exports执行了导出的num成员才能被正常导出,而str成员没有被执行导出,在依赖a.js模块的index.js中是不能引用到a.js模块中的str成员。可能你会说这不是很正常吗?都没有导出怎么引用呢?

不错,这是一个非常正常情况,因为语法就告诉了我们,要想引用一个模块的成员就必须先在被引用的模块中导出该成员。然而这里要讨论的当然不会是导出与引用这个问题,而是模块给我实现了一个非常友好的设计,假设我现在在a.js中有成员str,在index.js模块中也有成员str,这回冲突吗?显然是不会的,即使在a.js中导出str并且在index.js中引用a.js模块,因为index.js要使用a.js模块的成员str,需要使用接收模块变量aModule.str来使用。

 //a.js
let num = 123;
let str = "this is module 'a'";
exports.num = num;
exports.str = str; //index.js
let aModule = require('./a.js');
let str = "this is module 'index'"
console.log(aModule.num);//
console.log(aModule.str);//this is module 'a'
console.log(str);//this is module 'index'

基于开闭原则的设计方式,封闭可以让模块的内部实现隐藏起来,开放又可以友好的实现模块之间的相互依赖,这相对于之前我们常用的回调函数解决方案,程序设计变得更清晰,代码复用变得更灵活,更关键的是还解决了js中一个非常棘手的问题——命名冲突问题,上面的示例就是最好的证明。这里需要抛出一个问题,看示例:

 //下面这种写法有什么问题?
//a.js
let num = 123;
module.exports = num; //index.js
let aModule = require('./a.js');
let str = "this is module 'index'"
console.log(aModule);//

这种写法不会报错,也能正常达到目前的需求,如果从能解决目前的功能需求角度来说,它没错。但是开闭原则的重要思想就是让模块保持相对封闭,又有更好的拓展性,这样写显然不合适,比如就上面的代码写完上线以后,业务又出现了一个新的需求需要a.js模块导出一个成员str,这时候显然需要同时更改a.js模块和index.js模块,即使新需求不需要index.js来实现也是需要改的。所以维持模块的开闭原则是良好的编码风格。

二、nodejs模块的底层实现原理

2.1 module.exports与exports的区别:

//a.js
console.log(module.exports == exports);//true //然后在控制台直接执行a.js模块
node a.js

实际上它们是没有区别的,那为什么在之前的exports不能直接等于一个对象,而module.exports可以呢?这关乎于js的引用值指向问题:

当export被赋值一个对象时,就发生了一下变化:

这时候我们可以确定node不会导出exports,因为前面的示例已经说明了这一点,但是值得我们继续思考的是,node模块是依据module.exports、exports、还是它们指向的初始对象呢?这里你肯定会说是module.exports,因为前面已经有示例是module.exports指向一个新的对象被成功导出,但是我并不觉得前面那些示例能说服我,比如下面这种情况:

 //a.js模块
let num = 123;
function foo(val){
console.log(val);
}
module.exports = {
num:num
}
exports = {
foo:foo
}
//index.js模块
let aModule = require('./a.js');
console.log(aModule);//这里会打印出什么?

我们现不测试也不猜测,先通过下面的示图来看下现在的a.js模块中module.exports、exports、以及它们两初始指向的空对象的关系图:

这时候我们来看一下index.js执行会输出什么?

{ num: 123 }

所以从这个结果可以看出,最后require()最后导入的是被引用模块的module.exports。探讨到这里的时候并没有到达node模块的终点,我们这里module.exports、exports、require()是从哪里来的?node系统内置变量?还是别的?

2.2 node模块的底层实现原理

这部分的内容其实也没有太多可以说的,就前面提出来的问题其实有一个方式就可以让你一目了然,只需要在一个js文件中编写以下代码,然后使用node执行这个js文件就可以了:

 console.log(require);      // 一个方法
console.log(module); // 一个对象
console.log(exports); // 一个空对象
console.log(__dirname); // 当前模块所在路径
console.log(__filename); // 当前文件的路径

这时因为node模块实际上底层是被放到一个立即执行函数内(不要在乎xyz这个名称,因为我也不知道node底层到底用的什么名称),这些变量其实就是这个函数的参数,这个函数大概是一下形式:

 function xyz(module.exports,require,module,__filename,__dirname){
//...
// 这里就是我们在模块中写入的代码
//...
return module.exports;
}

通过上面的推断就可以得到下面这样的结果:

 console.log(module.exports == arguments[0]);//true
console.log(require == arguments[1]);//true
console.log(module == arguments[2]);//true
console.log(__filename == arguments[3]);//true
console.log(__dirname == arguments[4]);//true

通过执行这段打印代码也确实可以得到这样的结果,到这里又有一个值得我们关注的内容,就是每个模块的module参数:

 console.log(module);
Module {
id: '.',//当前模块的id都是'.',在后面的parent和children里面的模块对象上的id就是的对应模块的filename
exports: {},//这里是模块导出对象
parent: null,//这里是当前模块被那些模块引用的模块对象列表,意思是当前模块作为那些模块的父级模块
filename:'',//这里是当前文件路径的绝对路径
loaded: false,//模块加载状态,如果在模块内部输出module对象它永远都会是false,因为只有这个模块加载完成之后才会被修改成true
children: [
// 这里是引用模块module对象列表,意思是当前模块作为了那些模块的子模块
],
paths:[
// 这里是外部模块包的路径列表,从最近的路径(模块所在同级路径)到系统盘路径所有的node_modules文件夹路径
]
}

到这里有可能你还会问为什么底层实现里面只有module.exports,没有export,这个解释起来真的费劲,下面这一行代码帮你搞定:

let exports = module.exports;

这篇博客主要介绍了node模块的内部内容,并未就node模块基于commonjs规范做任何介绍,是因为在之前的博客中已经有了非常全面的解析,详细参考博客开始时的连接,关于node模块加载相关内容也是在那篇博客。

nodejs入门之模块的更多相关文章

  1. NodeJS入门(四)—— path对象

    很快Node就会迎来4.0的时代,届时将并入现有的iojs,所以先前写过的iojs入门系列直接更名为NodeJS入门. 本篇开始将逐个介绍Node的各主要模块,依循API文档走一遍,但会给出比API文 ...

  2. nodejs之url模块

    鄙人初步学习nodejs,目前在读<nodejs入门>这一本书,书很小,但是让我知道了如何用nodejs创建一个简单的小项目.例如如何创建一个服务器啦,例如http.createServe ...

  3. NodeJS入门简介

    NodeJS入门简介 二.模块 在Node.js中,以模块为单位划分所有功能,并且提供了一个完整的模块加载机制,这时的我们可以将应用程序划分为各个不同的部分. const http = require ...

  4. nodejs入门教程之http的get和request简介及应用

    nodejs入门教程之http的get和request简介及应用 前言 上一篇文章,我介绍了nodejs的几个常用的模块及简单的案例,今天我们再来重点看一下nodejs的http模块,关于http模块 ...

  5. nodeJs 5.0.0 安装配置与nodeJs入门例子学习

    新手学习笔记,高手请自动略过 安装可以先看这篇:http://blog.csdn.net/bushizhuanjia/article/details/7915017 1.首先到官网去下载exe,或者m ...

  6. 关于Nodejs的多进程模块Cluster

    关于Nodejs的多进程模块Cluster   前述 我们都知道nodejs最大的特点就是单进程.无阻塞运行,并且是异步事件驱动的.Nodejs的这些特性能够很好的解决一些问题,例如在服务器开发中,并 ...

  7. Nodejs中cluster模块的多进程共享数据问题

    Nodejs中cluster模块的多进程共享数据问题 前述 nodejs在v0.6.x之后增加了一个模块cluster用于实现多进程,利用child_process模块来创建和管理进程,增加程序在多核 ...

  8. nodejs的require模块及路径

    在nodejs中,模块大概可以分为核心模块和文件模块. 核心模块是被编译成二进制代码,引用的时候只需require表示符即可,如(require('net')). 文件模块,则是指js文件.json文 ...

  9. 使用nodejs的net模块创建TCP服务器

    使用nodejs的net模块创建TCP服务器 laiqun@msn.cn Contents 1. 代码实现 2. 使用telnet连接服务器测试 3. 创建一个TCP的client 1. 代码实现 ; ...

随机推荐

  1. 初识mpvue

    听说mpvue可以实现H5和小程序的同时开发  对使用过vue的选手几乎是0难度 忍不住搓搓小手手 看了文  唔~ 似乎不是很难的样子 然后实际上手操作了一下 老规矩:新建项目 npm install ...

  2. java学习2-数据类型和运算符

    1.数据类型分类 java是强类型语言:a.所有的变量必须先声明后使用 b.指定类型的变量只能接受类型与之匹配的值 java语言支持的类型分为两类:基本类型和引用类型. 基本类型:包括boolean类 ...

  3. MyBatis 之源码浅读

    环境简介与入口 记录一下尝试阅读Mybatis源码的过程,这篇笔记是我一边读,一遍记录下来的,虽然内容也不多,对Mybatis整体的架构体系也没有摸的很清楚,起码也能把这个过程整理下来,这也是我比较喜 ...

  4. 玩转PubSubClient MQTT库

    1.前言     在ESP8266学习系列中,博主一直使用HTTP协议.HTTP连接属于短连接,而在物联网应用中,广泛应用的却是MQTT协议.所以,本篇我们将学习Arduino平台上的MQTT实现库 ...

  5. dubbo初学采坑记

    写在前面的话 dubbo 现在是apache组织旗下的项目,相信国内也有很多人使用.最近一个同事离职,我就接手了他的项目.远程通讯就是用的dubbo框架来实现的.使用Intelij idea 写了一个 ...

  6. SpringCloud学习--Eureka 服务注册与发现

    目录 一:构建项目 二:服务注册与发现 为什么选择Eureka,请看上一篇博客 Eureka -- 浅谈Eureka 项目构建 IDEA 选择 New Project 选择 Spring Initia ...

  7. mp-vue拖拽组件的实现

    作为一个效率还不错的小前端,自己的任务做完之后真的好闲啊,千盼万盼终于盼来了业务的新需求,他要我多加一个排序题,然后用户通过拖拽来排序,项目经理看我是个实习生,说有点复杂做不出来就算了,我这么闲的一个 ...

  8. jQuery选择器 大于 空格 波浪线 加号

    JQuery选择器 大于 空格 波浪线 加号的区别 元素遍历 符号 说明 空格 $(‘parent child’)表示获取parent下的所有的child节点(所有的子孙). 大于号 $(‘paren ...

  9. Linux常用命令及示例(全)

    NO 分类 PS1 命令名 用法及参数 功能注解1 显示目录信息 # ls ls -a 列出当前目录下的所有文件,包括以.头的隐含文件 # ls ls -l或ll 列出当前目录下文件的详细信息 # l ...

  10. 爬虫连接mongodb、多线程多进程的使用

    一.连接mongodb 1.            设置数据库 client=pymongo.MongoClient(‘localhost’) 2.            db=client[‘lag ...