针对模块化,CommonJS规范,require原理和包查找机制等的整理

来源:互联网 发布:涟源行知中学校花图片 编辑:程序博客网 时间:2024/06/06 12:49

模块化的好处: 为了更有效的组织代码,提高重用性,增大开发效率,我们会把项目拆分成不同模块,每个模块,职责单一,多人协同,高效运行,易于维护。

模块的发展历程

  • js 不像其他高级语言有模块系统,标准库较少和更缺乏包管理系统。
  • js 起初只有全局对象的形式,通过一个个小函数来实现不同的模块功能
  • 渐渐发展,通过构建对象的形式,来武装不同的功能
  • 继续发展,通过立即执行函数和闭包的形式来分离一个又一个的小组件
  • 当对象多起来的时候,又开始通过命名空间,来实现分级管理
  • 最终,历经十多年,社区渐渐发展壮大,commonJS规范的提出成了javascript 历史上最重要的里程碑。
  • Best Wishes : hope javascript can run everywhere!

CommonJS规范

CommonJS只是一个规范,不是文件或者框架,这点一定要清楚。它的官方网址:http://www.commonjs.org/ ,CommonJS中大部分只是草案或者说是理论,在近几年的发展中已有显著成效,这些规范包括:

  • 模块
  • 二进制
  • I/O流
  • 进程环境
  • 文件系统
  • 套接字
  • 单元测试
  • web服务器网关
  • 包管理等等涵盖了方方面面。

在后端nodejs显然已经是CommonJS规范的最佳实践。

nodejs中的require的内部机制

我们知道,在nodejs 中每个文件就是一个独立的模块。在模块之间不会造成变量污染和命名冲突等问题。通过exports 或者 module.exports 输出功能或者说提供API,我们通过require 来引入一个模块,很像前端通过script标签来引入js文件。但是它的内部细节又如何呢,在朴灵的那本《深入浅出》中有关javascript的模块编译那块已经做了一些讲解,那么我们现在去繁留简来深入体验一把:

  • require 函数的源码可以这样理解,当然我们简化了不少东西,现在我们只把它抽离出来 :

    // 模拟require的实现function _require(path) {    // 定义一个Module对象    var Module = function() {        this.exports = {};    }    // 引入nodejs 文件模块 下面是nodejs中原生的require方法    var fs = require('fs');    // 同步读取该文件    var sourceCode = fs.readFileSync(path, 'utf8');    // 头尾拼接包装成新的字符串    var packSourceCode = '(function(module,exports){ ' + sourceCode + ' return module.exports; })';    // 字符串转换成函数    var packFunc = eval(packSourceCode);    // 实例化一个Module 里面有一个exports属性    var module = new Module();    // 把module 和 它内部的module.exports都作为参数传进去     // 并得到挂在到module.exports 或 exports上的功能    var res = packFunc(module, module.exports);    // 最终我们拿到了path代表的文件模块提供的API    return res; }
  • 说明: 上面举了一个小栗子来模拟nodejs中的require的实现,当然源码不会这样写,源码涉及的东西会更复杂些。上面只是一些帮助理解的小demo。

  • 我们自己写的逻辑功能全在path所代表的文件模块中,在内部把所有功能挂在到module.exports 或者exports对象上,最终return 他们得以暴露。

  • 我们可以看出 module.exports 和 exports 指向的是同一对象

  • 在require一个模块时大致经历了这几步:
    • 路径分析
    • 文件定位
    • 编译执行

一个小的 require 模块引入 案例

  • 在 util.js文件模块中,我们这样写

    // 定义一个计算器对象var Calculator = function() {};Calculator.prototype = {    add: function(x, y) {        return x + y;    },    subtract: function(x, y) {        return x - y;    },    multiply: function(x, y) {        return x * y;    },    divide: function(x, y) {        return x / y;    }}// 虽然exports 和 module.exports 指向同一个对象// 但是此处不能使用 exports = new Calculator(); 具体原因,下一篇博客分析module.exports = new Calculator(); // 暴露API对象
  • 在 index.js 文件中,我们这样写:

    var cal = require('./util');var res = cal.add(1, 2);console.log(res); // 3
  • 在 CLI 中,我们执行index模块,将会输出3

    $ node index3

nodejs中的模块分类和加载顺序

  • nodejs 中核心模块和文件模块的引用方式:

    • 核心模块: require(‘核心模块名’);
    • 文件模块: require(‘路径+文件名’); 路径可以用./代表的相对路径或者绝对路径,其中文件名可以省略后缀名。
    • 自定义模块:特殊的文件模块,可能是一个文件或者包的形式
  • 模块加载顺序 :

    • 优先从缓存加载
    • 核心模块:如http、fs、path等 (优先查找核心模块)
    • 文件模块: ./或../开始的相对路径文件模块,以/开始的绝对路径文件
    • 自定义模块:查找最费时

目录和包查找原则

  • 比如有如下的模块路径: 查找规则是沿路径向上逐级递归,直到根目录的node_modules目录:

    ├─/node_modules/  └─/home/node_modules/└─/user/test/node_modules/
  • 这就是自定义模块加载速度最慢的原因了。

  • 当我们require 的标识符 不包含扩展名node 会按照 .js .json .node 的次序补足扩展名 ,依次尝试。

  • 如果在require过程中,没有查找到对应文件,却得到一个目录,此时 node 会将当前目录当作一个包来处理。

    • 此时,node 会查找目录下的package.json文件,通过JSON.parse() 解析包描述对象,从中拿到main属性指定的文件名进行定位
    • 如果main属性指定错误,或者没有package.json文件,那么node会将index作为默认的文件名,去依次查找index.js , index.json , index.node
    • 在目录分析中没有定位到任何模块,那么它会遍历自己的上一级目录进行查找,如果还没找到,抛出查找失败的异常。
1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 妻子和她妈一起和我睡小说 孑母奸情在线播放 大嫂中文字幕129视频在线 做饭的时候儿子从后面 艳姆1-6无删减完整 儿子你不能这样啊国语 在线 在厕所里肉妈咪第一章 儿子你不能这样啊国语17分钟 不行 我们不能这样 征服护士妈全文目录34章 迷糊故意穿超短裙坐公交 大妈咪女教师全集 雪白短裙教师妈咪风雨夜 母亲轮陷公交 客厅弄醒穿花裙子午睡的妈 沙发午睡花裙子在线资源 公交上的妈咪 儿子你要高就快点你国语 花裙子母亲午睡 儿子你不能这样啊国语高清 被要求穿超级短的超短裙 穿花裙子躺在沙发上 客厅弄醒午睡的妈连接 家庭毋HH伦s线视频中字 客厅弄硬午睡的儿子短文 客厅搞午睡的母亲 弄醒客厅午睡的母亲在线播放 弄醒午睡的妈视频连接 在客厅睡的午的母亲电影 客厅午睡的母亲在线下载 韩国午睡弄醒午睡的妈 在客厅弄醒午睡的 客厅午睡的母亲穿裙子在线播放 客厅午睡的母亲自拍 客厅里硬搞午睡的母亲视频 对白搞硬沙发午睡的儿子 客厅沙发儿子碎花裙 电影客厅午睡的母亲 客厅午睡以为你是爸爸 客厅弄醒午唾的儿子 客厅搞硬午睡的儿子小说