node.js中的模块实现原理

329 阅读2分钟

起源

javascript当初被设计出来用于浏览器,js的能力大小取决于运行环境对于api的支持能力。在web的发展中,浏览器中出现了更多标准api,但是后端javascript规范却远远落后。对于javscsript语言而言, 它薄弱环节是缺少规范:

  • 没有模块系统
  • 标准库少
  • 没有标准接口
  • 缺乏包管理系统

commonJS规范的提出,主要是弥补了js没有标准的缺陷,以具备开发大型应用的基础能力,而不是停留在小型脚本的处境。在发展中,commonJS的成长中,规范涵盖了模块、二进制、buffer、字符集编码、i/o流、进程环境、文件系统、套接字、单元测试、tcp/ip、包管理等。

commonJS规范

  • 模块引用:使用require()引用一个模块
  • 模块定义:在模块中,上下文提供require()引入外部模块,导入相应功能。上下文提供了export()方法导出当前模块的方法或者变量。并且这是唯一到导出出口。模块中还存在一个module对象。他代表模块自身,export是module的属性。在node中,一个文件就是一个模块
  • 模块标示:模块标示就是传递给require()的参数,内部模块可以是驼峰字符串,自己写的模块可以使相对路径和绝对路径,可以忽略后缀名。

Node的模块实现

先上图:

首先判断引用的模块是否是核心模块。如果是,则直接使用。因为核心模块在node启动时就已经加载进了内存中。如果非核心模块,则为文件模块。引用文件模块首先会查询缓存中是否存在,如果存在就直接执行。若不存在,则会对require中传入的标识符进行地址补全,转化成绝对路径,再依次添加.js/.node/.json进行读取尝试。

如果目标文件为json,会通过JSON.parse()转化后返回。 如果目标文件为.node,会通过dlopen()方法进行加载。 如果目标文件为.js,先在读取的文件字符串首尾依次拼接(function(){}),目的是解析执行时为模块代码添加一个封闭作用域。然后调用vm原生模块的runInThisContext()方法执行。返回一个具体的function对象。不会污染全局。最后call()执行。