文档章节

一起读书《深入浅出nodejs》-node模块机制

小草先森
 小草先森
发布于 06/23 22:54
字数 2221
阅读 30
收藏 0
点赞 0
评论 0

node 模块机制

前言

说到node,就不免得提到JavaScript。JavaScript自诞生以来,经历了工具类库、组件库、前端框架、前端应用的变迁。通过无数开发人员的努力,JavaScript不断被类聚和抽象,从而更好的组织业务逻辑。而从另一个角度而言,大家之所以将其整来整去,还不是为了弥补其先天缺乏的一项功能:模块。

与其他高级语言相比,Java有类文件、Python有import机制、PHP有include和require。要想更好的将JavaScript运用在node上,解决这一缺陷至关重要。

CommonJS规范

经过多年的发展,为了解决JavaScript在服务端上暴露出来的种种缺陷,社区为JavaScript制定了相应的规范,其中CommonJS规范的提出算是最为重要的里程碑

CommonJS出发点

CommonJS规范的提出是为了解决JavaScript在后端的缺陷,如:

  • 没有模块系统
  • 标准库较少 ECMAscript仅仅定义了部分核心库,更多的是用在浏览器端。而服务器端的文件系统,I/O流等常见需求却没有标准的API。虽然W3C在推进ECMAscript的标准化,但它也仅限于浏览器端。
  • 没有标准接口。 在JavaScript中,几乎没有定义过如web服务器或数据库之类的标准统一接口。
  • 缺乏包管理系统。 这个问题导致了JavaScript应用中基本没有自动加载和安装依赖的能力。

CommonJS规范为Javascript开发大型应用指明了一条非常棒的道路,规范涵盖了模块、二进制、Buffer、字符集编码、I/O流、进程环境、文件系统、嵌套字、单元测试、Web服务器网关接口、包管理等。

模块规范

CommonJs对模块的定义主要分为:模块引用、模块定义、模块标识3个部分。

  1. 模块引用
var math = require('math')

在CommonJS规范中,存在require()方法,这个方法接受模块标识,引入一个模块的API到当前上下文中。

  1. 模块定义
// hello.js
exports.sayHello = function () {
    console.log('hello world')
}

在另一个文件中,我们通过require()方法引入模块,就能调用定义的属性或变量了。

  1. 模块标识

模块标识其实就是传递给require()方法的参数,它必须是符合小驼峰命名的字符串或者以./、../开头的相对路径,或者绝对路径。它可以没有文件名后缀.js

CommonJS构建的这套模块导入和引入机制使得用户完全不必考虑变量污染。

node的模块实现

在node中引入模块,需要经历三个步骤

  • 路径分析
  • 文件定位
  • 编译执行

node中的模块分为两类:一类是node提供的模块,成为核心模块;另一类是用户编写的模块,称为文件模块。

node核心模块在node源码编译过程中编译进了二进制执行文件。在node启动时部分核心代码就直接加载进了内存中,所以在引入这部分核心模块时,文件定位和编译执行这两个步骤可以省略掉,并且在路径分析中优先判断,所以它的加载速度是最快的。

而文件模块则是在运行时加载,需要完整的经历上面三个步骤,速度当然就比核心模块慢。

不论是核心模块还是文件模块,require()方法对相同的模块的二次加载都一律采用缓存优先的方式。

模块编译

我们知道每个模块文件中存在着require、exports、module这3个变量,但它们在模块文件中并没有定义,那么从何而来呢?并且在node的api文档中,我们知道每个模块中还有__filename,__dirname这两个变量的存在,它们又是从何而来呢?如果我们把直接定义模块的过程放在浏览器端,就会存在污染全局变量的情况

事实上,在编译过程中,node对获取的JavaScript文件内容进行了头尾包装。在头部添加了

(function (exports, require, module, __filename, __dirname){\n

在尾部添加了

\n})

一个正常的JavaScript文件会被包装成如下模样:

(function (exports, require, module, __filename, __dirname) {
    var math = require('math)
    exports.area = function (radius) {
        return Math.PI * radius *radius
    }
})

这样每个模块文件之间都进行了作用域隔离。包装之后的代码会通过vm原生模块的runInThisContext()方法执行,返回一个具体的funtion对象。最后将当前模块对象的exports属性、require()方法、module(模块对象自身),以及在文件定位中得到的完整文件路径和文件目录作为参数传递给这个function()执行。

这就是这些变量并没有定义在每个模块文件中却存在的原因。此外,也许有人会纠结为何存在exports的情况下,还存在module.exports。理想情况下,只要赋值给exports即可:

exports = function () {
    // my class
}

但是通常都会得到一个失败的结果。其原因在于,exports对象是通过函数形参的方式传入的,直接赋值形参会改变形参的引用,但并不能改变作用域外的值。举个栗子:

var change = function (a) {
    a = 100
    console.log(a) // => 100
}
var a = 10
change(a)
console.log(a) // => 10

如果要达到require引入一个类的效果,我们应该赋值给module.exports对象。这个变通的方案不改变形参的引用。

包与NPM

node组织了自身的核心模块,也使得第三方文件模块可以有序地编写和使用。但是在第三方模块中,模块与模块之间仍然是分散在各地的,相互之间不能直接引用。而在模块之外,包和NPM则是将模块联系起来的一种机制。

CommonJS的包规范定义也十分简单,它由包结构和包描述文件两个部分组成。

包实际上是一个目录直接打包为.zip或tar.gz格式的文件,安装后解压还原为目录。完全符合CommonJS规范的包目录应该包含如下这些文件:

  • package.json:包描述文件
  • bin:用于存放可执行二进制文件的目录
  • lib:用于存放JavaScript代码的目录
  • doc:用于存放文档的目录
  • test: 用于存放单元测试用例的目录。

前端模块规范

浏览器端通过网络加载代码,而服务器端从磁盘中加载,两者加载速度不在一个数量级上。纵观node的模块引入过程,几乎全是同步的。但如果前端模块也采用同步的方式来引入,那么用户体验上会出现很大的问题。UI在初始化过程中需要花费很多时间来等待脚本加载完成。

鉴于网络原因,CommonJS为后端JavaScript制定的规范并不完全适合前端的应用场景。经过一段争执之后,AMD规范最终在前端应用场景中胜出。

AMD规范

AMD规范是CommonJS模块规范的一个延申,它的模块定义如下:

define(id?, dependencies?, factory)

它的模块id和依赖是可选的,与Node模块相似的地方在于factory的内容就是实际代码的内容。下面的代码定义了一个简单的模块:

define(function () {
    var exports = {}
    exports.sayHello = function () {
        alert('hello from module: ' + module.id)
        return exports
    }
})

不同之处在于AMD模块需要用define来明确定义一个模块,而在node实现中是隐式包装的,它们的目的是进行作用域隔离,仅在需要的时候被引入。另一个区别则是内容需要通过返回return的方式实现导出。

CMD规范

CMD规范是由国内的玉伯提出,与AMD规范主要区别在于定义模块和依赖引入部分。AMD需要在声明模块的时候指定所有的依赖,通过形参传递依赖到模块中。

define(['dep1', 'dep2'], function (dep1, dep2) {
    return function () {}
})

与AMD模块规范相比,CMD模块更接近于node对CommonJS规范的定义:

define(factory)

在依赖部分,CMD支持动态引入,示例如下:

define(function (require, exports, module) {
    // todo
})

require,exports和module通过形参传递给模块,在需要依赖模块时,随时调用require()引用即可。

© 著作权归作者所有

共有 人打赏支持
小草先森
粉丝 12
博文 40
码字总数 28529
作品 0
武汉
深入浅出Node.js_Index

深入浅出Node.js系列 【深入浅出Node.js系列一】什么是Node.js 【深入浅出Node.js系列二】Node.js&NPM的安装与配置 【深入浅出Node.js系列三】深入Node.js的模块机制 【深入浅出Node.js系列四...

陶邦仁
2016/01/07
416
0
【深入浅出Node.js系列十三】用Nodejs连接MySQL

深入浅出Node.js系列 【深入浅出Node.js系列一】什么是Node.js 【深入浅出Node.js系列二】Node.js&NPM的安装与配置 【深入浅出Node.js系列三】深入Node.js的模块机制 【深入浅出Node.js系列四...

陶邦仁
2016/01/19
253
0
【深入浅出Node.js系列十六】grunt让Nodejs规范起来

深入浅出Node.js系列 【深入浅出Node.js系列一】什么是Node.js 【深入浅出Node.js系列二】Node.js&NPM的安装与配置 【深入浅出Node.js系列三】深入Node.js的模块机制 【深入浅出Node.js系列四...

陶邦仁
2016/01/21
128
0
【深入浅出Node.js系列十五】Nodejs实现websocket的4种方式

深入浅出Node.js系列 【深入浅出Node.js系列一】什么是Node.js 【深入浅出Node.js系列二】Node.js&NPM的安装与配置 【深入浅出Node.js系列三】深入Node.js的模块机制 【深入浅出Node.js系列四...

陶邦仁
2016/01/20
368
1
【深入浅出Node.js系列十一】Node.js开发框架Express4.x

深入浅出Node.js系列 【深入浅出Node.js系列一】什么是Node.js 【深入浅出Node.js系列二】Node.js&NPM的安装与配置 【深入浅出Node.js系列三】深入Node.js的模块机制 【深入浅出Node.js系列四...

陶邦仁
2016/01/19
282
0
深入浅出Node.js(三):深入Node.js的模块机制

Node.js模块的实现 之前在网上查阅了许多介绍Node.js的文章,可惜对于Node.js的模块机制大都着墨不多。在后续介绍模块的使用之前,我认为有必要深入一下Node.js的模块机制。 CommonJS规范 早...

leeldy
2012/10/25
0
0
【深入浅出Node.js系列十四】Nodejs异步流程控制Async

深入浅出Node.js系列 【深入浅出Node.js系列一】什么是Node.js 【深入浅出Node.js系列二】Node.js&NPM的安装与配置 【深入浅出Node.js系列三】深入Node.js的模块机制 【深入浅出Node.js系列四...

陶邦仁
2016/01/19
250
0
深入浅出Node.js(一):什么是Node.js

专栏的第一篇文章《什么是Node.js》尝试从各个角度来阐述Node.js的基本概念、发展历史、优势等,对该领域不熟悉的开发人员可以通过本文了解Node.js的一些基础知识。 从名字说起 有关Node.js...

leeldy
2012/10/25
0
1
昨天直播的微信小程序讲义

狼叔带你一起玩转微信应用号 微信小程序开发适合你吗? 如果想邀请分享,请邮寄给我i5tinig@126.com,如果时间ok,我会尽量分享 文档 https://i5ting.github.io/stuq-wxapp/ 仓库 https://gi...

i5ting
2016/09/29
111
0
深入浅出Node.js(四):Node.js的事件机制

Node.js的事件机制 Node.js在其Github代码仓库(https://github.com/joyent/node)上有着一句短短的介绍:Evented I/O for V8 JavaScript。这句近似广告语的句子却道尽了Node.js自身的特色所...

leeldy
2012/10/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Java设计模式学习之工厂模式

在Java(或者叫做面向对象语言)的世界中,工厂模式被广泛应用于项目中,也许你并没有听说过,不过也许你已经在使用了。 简单来说,工厂模式的出现源于增加程序序的可扩展性,降低耦合度。之...

路小磊
21分钟前
0
0
npm profile 新功能介绍

转载地址 npm profile 新功能介绍 npm新版本新推来一个功能,npm profile,这个可以更改自己简介信息的命令,以后可以不用去登录网站来修改自己的简介了 具体的这个功能的支持大概是在6这个版...

durban
33分钟前
0
0
Serial2Ethernet Bi-redirection

Serial Tool Serial Tool is a utility for developing serial communications, custom protocols or device testing. You can set up bytes to send accordingly to your protocol and save......

zungyiu
38分钟前
0
0
python里求解物理学上的双弹簧质能系统

物理的模型如下: 在这个系统里有两个物体,它们的质量分别是m1和m2,被两个弹簧连接在一起,伸缩系统为k1和k2,左端固定。假定没有外力时,两个弹簧的长度为L1和L2。 由于两物体有重力,那么...

wangxuwei
53分钟前
0
0
apolloxlua 介绍

##项目介绍 apolloxlua 目前支持javascript到lua的翻译。可以在openresty和luajit里使用。这个工具分为两种模式, 一种是web模式,可以通过网页使用。另外一种是tool模式, 通常作为大规模翻...

钟元OSS
今天
0
0
Mybatis入门

简介: 定义:Mybatis是一个支持普通SQL查询、存储过程和高级映射的持久层框架。 途径:MyBatis通过XML文件或者注解的形式配置映射,实现数据库查询。 特性:动态SQL语句。 文件结构:Mybat...

霍淇滨
今天
0
0
开发技术瓶颈期,如何突破

前言 读书、学习的那些事情,以前我也陆续叨叨了不少,但总觉得 “学习方法” 就是一个永远在路上的话题。个人的能力、经验积累与习惯方法不尽相同,而且一篇文章甚至一本书都很难将学习方法...

_小迷糊
今天
0
0
安装tensorflow-XXX报错

报错: tensorflow-0.5.0-cp27-none-linux_x86_64.whl is not a supported wheel on this platform. 解决: wget https://bootstrap.pypa.io/get-pip.py sudo python2.7 get-pip.py sudo p......

Yao--靠自己
今天
0
0
JVM学习手册(一):JVM模型

一直从事JAVA开发,天天和JVM打交道,仔细想想对JVM还真的不是特别了解,实在是不应该.周六看了许多资料,也算有点心得,记录一下。 JVM内存模型分为5个区域:方法区,堆,虚拟机栈,本地方法栈,程序计...

勤奋的蚂蚁
今天
0
0
转行零基础该如何学Python?这些一定要明白!

转行零基础学Python编程开发难度大吗?从哪学起?近期很多小伙伴问我,如果自己转行学习Python,完全0基础能否学会呢?Python的难度到底有多大?今天,小编就来为大家详细解读一下这个问题。...

猫咪编程
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部