Node.js之模块系统

2023-10-27

CommonJS规范

希望JavaScript可以在任何地方运行,以达到像Java、PHP、Ruby、Python具备开发大型应用的能力。

出发点:
没有模块系统
标准库较少
缺乏包管理系统

CommonJS模块规范

  1. 模块引用require
  2. 模块定义
    一个文件就是一个模块
    将方法挂载到exports对象上作为属性即可定义导出的方式
  3. 模块标识
    必须是符合小驼峰命名的字符串
    以.、..开头的相对路径
    绝对路径
    可以没有文件名后缀.js

CommonJS模块特点

1、所有代码都运行在模块作用域,不会污染全局作用域
2、模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就缓存了,以后再加载,就直接读取缓存结果。
3、模块加载的顺序,按照其在代码中出现的顺序

Node.js模块介绍

1、Node程序由许多模块组成,每个模块就是一个文件。Node模块采用了CommonJS规范。
2、Node.js本身就是一个高度模块化的一个平台。
3、根据CommonJS规范,每一个模块都是一个单独的作用域。
4、CommonJS规定,每个文件对外的接口是module.exports对象,该对象所有属性和方法,都可以被其它文件导入。

模块系统

  • 1、什么是模块

    一个文件就是一个模块

  • 2、模块作用域

    在一个模块内变量、函数、对象都属于这个模块,对外是封闭的。

  • 3、require

require("路径")
//路径现阶段使用相对路径

在Node.js中,require命令用于加载模块文件。

基本功能:
读取并执行一个JavaScript文件
然后返回该模块的exports对象
如果没有发现指定模块,会报错

  • 4、exports

① 是module.exports的一个别名、引用,exports能做的module.exports都可以完成。

② 为了方便,Node为每个模块提供一个exports变量,指向module.exports。

相当于在每个模块头部,有这样一行命令:
var exports = module.exports;

结果就是:在对外输出模块接口时,可以向exports对象添加方法

③ 注意:不能直接给exports赋值,因为这样等于切断了exports和module.exports的联系

module.exports 别名
module.exports=123;已经使用过一次了,exports就不生效了
module.exports.a=12;这样使用,exports是还可以使用的
  • 5、module.exports

1、module.exports属性表示当前模块对外输出的接口,其它文件加载该模块,实际上就是读取module.exports属性

2、点儿导出单个函数、对象或者值的时候非常有用,本质上就是少了一个.

3、最终想曝露出去对象、属性、方法就挂载到module.exports

补充: module对象
1、 是一个全局对象,它的作用是存储模块信息的。每一个js是一个模块,每一个模块里面都有一个module,module里面还存储了父子结构。直接给module.exports赋值,exports是无效,用属性的形式去赋值的时候二者都有效。

2、Node内部提供一个Module构造函数,所有模块都是Module的实例。
每个模块内部,都有一个module对象,代表当前模块。

module.id 带有绝对路径的模块文件名
module.filename 模块的文件名,带有绝对路径
module.loaded 表示模块是否已经完成加载
module.parent 返回一个对象,表示调用该模块的模块。
module.children 返回一个数组,表示该模块要用到的其他模块。
module.exports 模块对外输出的值

1 模块种类

在Node.js中,模块分为两类:

第一类,核心模块(原生模块),node自带,用名称直接可以加载。

- 核心模块
  fs: file system与文件系统交互
  http: 提供http服务器功能
  os
  path: 处理文件路径
  querystring : 解析url查询字符串
  url:解析url
  util: 提供一系列实用小工具
  ...

核心模块的源码都在Node的lib子目录中。为了提高运行速度,它们安装的时候都会被编译成二进制文件。

第二类,文件模块,用路径加载,用户自己编写的模块。有一种特殊的文件模块—–>包,可以直接用名字。

包就是在模块的基础之上进一步组织JavaScript代码。包中必须要有一个包说明文件package.json。

规范的包目录结构:

package.json : 包描述文件,说明文件
Bin : 存放可执行二进制文件的目录
Lib : 存放JavaScript代码的目录
Doc : 存放文档的目录
Test : 存放单元测试用例的代码

其中package.json:

name
    包的名称
description
    包的简介
version
    包的版本号
keywords
    关键词数组,用于在npm中分类搜索
author
    包的作者
main
    配置包的入口,默认是模块根目录下的index.js
dependencies
    包的依赖项,npm会通过该属性自动加载依赖包
scripts
    指定了运行脚本命令的npmm命令行缩写,例如start

package.json全字段解析](http://blog.csdn.net/woxueliuyun/article/details/39294375

一个例子:

//test_package/node_modules/myPackage/package.json
{
  "name": "test",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "keywords": [
  ],
  "author": "KingNigel",
  "repository": {
    "type": "git",
    "url": "https://github.com/KingNigel/test.git"
  },
  "bugs": {
    "url": "https://github.com/KingNigel/test/issues"
  },
  "license": "MIT",
  "devDependencies": {
    "test": "*"
  },
  "dependencies": {
    "test": "*"
  }
}

//test_package/node_modules/myPackage/index.js
exports.a=123;

//test_package/index.js
var foo = require('myPackage'); //文件夹名就是包名
console.log(foo); //{a:123}

2 优先从缓存加载

common.js规范----->加载后,再次加载时,去缓存中取module.exports

只在第一次加载的时候,从硬盘中读取js,执行代码,缓存整个module对象。第二次加载去缓存中取module.exports。

这里写图片描述

加载模块时将运行模块文件中的每一行代码,相同模块多次引用不会引起模块内代码多次执行。

//add.js
var add = function(a,b){
    return a+b;
}
module.exports = add;
console.log('执行add.js');

//sub.js
var sub = function(a,b){
    return a-b;
}

module.exports = sub;
console.log('执行sub.js');
//math.js
var add1 = require('./add.js');
var add2 = require('./add.js');
var add3 = require('./add.js');
var sub1 = require('./sub.js');

console.log(add1(3,5));
console.log(add3(1,5));

//执行结果:
执行add.js
执行sub.js
8
6

测试结果可知,add1和add3只加载一次;sub没有使用,也会加载执行

3 require参数解析及加载规则

核心模块是Node.js原生提供的,加载核心模块的时候,不需要传入路径,因为Node.js已经将核心模块的文件代码 编译到了二进制的可执行文件中了。 在加载的过程中,原生的核心模块的优先级是是最高的。

加载文件模块的时候,需要给定准确的路径。
require 加载包的时候: 直接写包名,先在包内的node_modules目录下查找,去父级目录下的node_modules目录下查找, 依次向上查找,直到根目录。

  • ./ 或 ../ 开始的相对路径文件模块
    • 在加载一个自己编写的模块的时候,最好使用__dirname 和 你要加载的模块的文件名拼接。
  • 以 / 开始的绝对路径文件模块(不建议使用)

    • 在Linux或者MAc的操作系统中,/表示系统的根路径
    • 在Windows中,/表示当前文件模块所属的根磁盘路径
  • 参数字符串不以“./“或”/”开始

    • 表示加载核心模块,或者一个位于各级node_modules目录已安装的模块。
  • 标识符中可以不包含扩展名

    • Node会按照 .js、.node、.json 的次序补足扩展名,依次尝试
    • Node.js会通过同步阻塞的方式看这个路径是否存在。依次尝试,直到找到为止, 如果找不到,报错。

    路径.js 以后自己在加载js文件模块的时候,就省略掉.js后缀就可以了
    路径.node 后缀为node的文件是c/c++写的一些扩展模块
    路径.json 如果是加载json文件模块,最好加上后缀.json,能稍微的提高一点加载的速度
    .json文件最终Node.js也是通过fs读文件的形式读取出来的,然后通过JSON.parse()转换成一个对象

  • 最终获取到的是module.exports

  • 参考田永强《深入浅出nodejs》

    4 require加载机制

    如果require绝对路径的文件,查找时不会去遍历每一个node_modules目录,其速度最快。其余流程如下:
    1. 从module path数组中取出第一个目录作为查找基准。
    2. 直接从目录中查找该文件,如果存在,则结束查找。如果不存在,则进行下一条查找。
    3. 尝试添加.js、.json、.node后缀后查找,如果存在文件,则结束查找。如果不存在,则进行下一条。
    4. 尝试将require的参数作为一个包来进行查找,读取目录下的package.json文件,取得main参数指定的文件。
    5. 尝试查找该文件,如果存在,则结束查找。如果不存在,则进行第3条查找。
    6. 如果继续失败,则取出module path数组中的下一个目录作为基准查找,循环第1至5个步骤。
    7. 如果继续失败,循环第1至6个步骤,直到module path中的最后一个值。
    8. 如果仍然失败,则抛出异常。

    这里写图片描述

    5 require的实现原理

    把代码从文件中读出来,用匿名函数的方式头尾包装,返回modules.exports对象,曝露出想要曝露出来的属性、方法、对象。
    补充了exports是指向modules.exports的一个指针,exports能做的,modules.exports都能做的。

    6 仿写MyRequire.js

    //MyRequire.js
     function MyRequire(path) {
        //定义一个构造函数Module
        function Module() {
          this.exports = {};
        }
        var fs = require('fs');
        //同步读取文件,文件的内容source
        var source = fs.readFileSync(path, 'utf-8');
        //拼接代码变成一个函数的string
        var package = '(function(exports,module){' + source + ' return module.exports;})';
        var packObj = eval(package);
    
        //调用构造函数创建Module
        var module = new Module();
        //var exports = module.exportsmodule.exports当实参传入,exports当形参去接
        var obj = packObj(module.exports, module);
        return obj;
      }
    
      var foo = MyRequire('./hia.js');
      console.log(foo);
      console.log(foo.a);
      foo.fun();
    //hia.js
    var a = 123;
    var fun = function(){
        console.log("我是fun函数");
    }
    console.log("helloworld");
    
    exports.a=a;
    exports.fun = fun;

    参考文档:

    package.json全字段解析:
    (http://blog.csdn.net/woxueliuyun/article/details/39294375

    参考田永强《深入浅出nodejs》:(http://www.infoq.com/cn/articles/nodejs-module-mechanism/)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Node.js之模块系统 的相关文章

随机推荐

  • 运行paddle-gpu相关项目报错

    报错1 RuntimeError PreconditionNotMet Cannot load cudnn shared library Cannot invoke method cudnnGetVersion Hint cudnn dso
  • JS 对象 Key使用变量代替

    JS 对象 Key使用变量代替 let key aa let data key value 普通 的对象类型数据中 直接用变量名填写key 最后 key会变成变成变量名的字符串 let data key value 使用中括号 以后 就会将
  • 实现点击img图片标签触发上传文件input按钮

    点击图片上传文件 简单有效 在网上找的一些文章都是A抄B B抄C 还都没啥用 自己蠢了 其实很简单 记录一下 Html 代码 div style width 30 margin left 3 height 1 img src images
  • Kendo UI开发教程(7): Kendo UI 模板概述

    Kendo UI 框架提供了一个易用 高性能的JavaScript模板引擎 通过模板可以创建一个HTML片段然后可以和JavaScript数据合并成最终的HTML元素 Kendo 模板侧重于UI显示 支持关键的模板功能 着重于性能而不是语法
  • 并发编程系列之线程的启动终止

    前言 上节我们对线程有了个基本的概念和认识 从线程状态转变过程我们也已经知道了线程通过调用start方法进行启动 直到run方法执行线程结束 今天我们就来详细的说说启动和终止线程的细节 OK 让我们开始今天的并发之旅吧 创建线程 在使用一个
  • 云解析DNS“免费版”与“付费版”的功能对比!新手引导

    阿里云域名DNS解析是免费的 阿里云云解析DNS是付费的 那么同样是DNS解析免费版和付费版有什么区别 aliyun网分享免费版DNS和云解析DNS的区别 DNS免费版和云解析DNS付费版的区别参数分类 参数项 参数值 免费版基础配置 最低
  • 如何将电子签名透明化处理

    如何将电子签名透明化处理 1 在纸上写上自己的名字 2 用拍照设备 如手机 拍下自己的签名 3 用 WPS图片 打开图片 点击图片 将图片转为png格式保存到一个地方 图1 4 打开word 我用的是win10自带的word 点击 插入 g
  • 告诉你什么是优雅的代码(11)----html5 之XXOO棋

    项目结项后的一星期又两天后 我又有了写些优雅代码的欲望 在我的记忆中 AI 在这个领域 我已近乎白痴 剪枝与寻路两种剑法 就在我离开校园之后 连同那蓝天碧草 相忘于江湖 江湖中只有 SSH 只有 SQL 只有汽车尾气 只有路人甲 在计算机语
  • 语雀实现收藏网页的功能(借助印象笔记)

    我是从印象笔记转为语雀的 之前在印象笔记上积累了1500条笔记 本来想全部搬迁至语雀 导出时发现内存达2 4GB 后来只迁移了一部分 现在两个软件都在用 一主一辅 迁移过程中意外发现 语雀导入html可以完美解决之间遇到的无法收藏网页的问题
  • your password has expired.To log in you must change itusing a client that supports expired passwords

    学java当然就少不了要跟mySQL打交道 终于经历了2小时的恶心安装后 舒舒服服的敲了一天demo 第二天再来的时候发现居然mySQL又特么出幺蛾子 无法连接上了 仔细看了一下错误原因 your password has expired
  • Nginx+keepalived双主配置(双机双主热备)

    简介 这种方案 使用两个VIP地址 前端使用2台机器 互为主备 同时有两台机器工作 当其中一台机器出现故障 两台机器的请求转移到一台机器负担 非常适合于生产架构环境 一 网络拓扑 四台虚拟机如下所示 为什么使用keepalived呢 使用k
  • Docker安装RabbitMQ

    安装Rabbitmq 1 使用docker查询rabbitmq的镜像 docker search rabbitmq 2 安装镜像 安装name为rabbitmq的这里是直接安装最新的 如果需要安装其他版本在rabbitmq后面跟上版本号即可
  • FILETIME, SYSTEMTIME and time_t

    先说个概念 Coordinated Universal Time UTC 又称为世界标准时间 比如 中国内地的时间与UTC的时差为 8 也就是UTC 8 Calendar Time 日历时间 是用 从一个标准时间点到此时的时间经过的秒数 来
  • 数学公式推导——理解梯度消失和梯度爆炸

    梯度消失和梯度爆炸的定义 梯度消失 又叫梯度弥散 是指模型梯度在反向传播时 梯度值接近零 导致模型权重不能正常更新 使模型无法正常收敛的现象 梯度爆炸 是指模型梯度在反向传播使 梯度值无限扩大 导致模型权重趋于无穷 使模型无法正常收敛的现象
  • CRC-16 / MODBUS 校验计算方法多项式A001H (1010 0000 0000 0001B)

    https blog csdn net u013625451 article details 81239572 utm medium distribute pc relevant none task blog BlogCommendFrom
  • ApacheBench_ab性能测试工具

    前言 在学习使用ab命令之前 首先要了解压力测试的几个概念 吞吐率 Requests per second 概念 服务器并发处理能力的量化描述 单位是reqs s 指的是某个并发用户数下单位时间内处理的请求数 某个并发用户数下单位时间内能处
  • VS中Qt项目不能打开ui文件

    在VS中创建的Qt项目 若不能打开ui文件 或者弹出以下提示的 其本质是找不到designer exe的路径 那么需要手动添加designer exe的路径 第一步 右键点击打不开的ui文件 选择打开方式 第二步 点击添加 第三步 点击右侧
  • 解空间的维数

    线性代数 齐次方程组的系数矩阵的秩与增广矩阵的秩为R a 解空间的维数就是n R a 维数为1 直线 维数为2 面 维数为3 空间
  • [个人笔记]FDTD solutions8.0 02

    先再次熟悉一下软件布局 演示实验 在玻璃基底上镀50nm厚的硅 测量400 800nm宽光谱的反射率和透射率 创建模型 玻璃底是一个基本的长方体 在结构中可以找到基本的几何体模型 如果想删除你创建的物体 找到Object Tree 选选中你
  • Node.js之模块系统

    CommonJS规范 希望JavaScript可以在任何地方运行 以达到像Java PHP Ruby Python具备开发大型应用的能力 出发点 没有模块系统 标准库较少 缺乏包管理系统 CommonJS模块规范 模块引用require 模