commonjs 规范

CommonJS是一种JavaScript模块化规范,每个文件被视为一个模块,拥有独立的作用域。模块通过module.exports或exports导出接口,require命令加载模块,返回导出的对象。加载过程有缓存机制,避免重复加载。require.main可判断模块执行方式。模块内部通过函数作用域实现变量隔离,确保模块间互不干扰。
摘要由CSDN通过智能技术生成

commonjs可以做以下内容:

  1. 服务端应用程序;
  2. 命令行工具;
  3. 基于桌面的应用程序;
  4. 混合开发。

1.commonjs规范

关于模块:

  • 每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
  • 在模块中使用global定义全局变量,不需要导出,在别的文件中可以访问到。
  • 每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外接口。
  • 通过require加载模块,读取并执行一个js文件,然后返回该模块的exports对象。
  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。

2.module对象

node内部提供一个Module构建函数。所有的模块其实都是Module的实例。

每个模块内部都有一个Module对象,代表当前模块。它有以下属性:

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

下图中打印module的结果

2.1 module.exports属性和exports变量

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

为了方便,node为每一个模块提供了一个exports变量,指向module.exports。这等同在每个模块头部,有一条这样的命令。

  1.  var exports= module.exports;

所以我们在对外输出模块接口时,可以向exports对象中添加方法或者属性。可以写成如下形式:

  1.  module.exports.name = "Zhangsan";
  2.  exports.name = 'Lisi';
  3.  exports.age = 18;

但注意,不可以直接将exports变量指向一个值,因为这样等于切断了exports和module.exports的联系。这意味着,如果一个模块的对外接口,是一个单一的值,这种情况就不能使用exports输出,只能使用module.exports输出。

  1. exports = "Lisi";        //错误的
  2. exports = function(){return "Lisi";};        //错误的

以下这种写法中,hello函数是无法对外输出的,因为module.exports被重新赋值了:

  1. exports.hello = function() {
  2.     return 'hello';
  3. };
  4. module.exports = 'hello world';

如果你觉得 exports 和 module.exports 之间的区别很难分清,一个简单的处理方法,就是放弃使用exports,只使用module.exports。

3.require

require命令 用于 加载模块文件。它的基本功能是,读取并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。

commonjs 模块的加载机制是输入的是被输出的值的拷贝;也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

3.1 require的内部处理流程

当使用require加载模块时,在node内部,会做以下处理:

  1. 先检查Module._cache,看里面有没有对应模块的缓存;
  2. 如果缓存没有的话,就创建一个新的module实例,并把它添加到缓存中,缓存的是它exports导出的值;
  3. 如果缓存中有的话,使用module.load()这个方法,去加载这个模块,读取文件内容后,使用module.compile()执行文件代码;
  4. 如果解析的过程中,出现异常,就从缓存中删除这个模块;
  5. 如果没有出现异常,最后返回这个模块的module.exports。

require 命令用于加载文件,后缀名默认为 .js。

  1.  var user = require('./user');
  2. //等同于
  3.  var user = require('./user.js');

3.2 加载规则

根据参数的不同规格,require命令会去不同路径寻找模块文件。

  1. 如果参数字符串以 “/” 开头,表示加载的是一个位于 绝对路径 的模块文件;
  2. 如果参数字符串以 “./” 开头,表示加载的是一个位于 相对路径(跟当前执行脚本的位置相比)的模块文件;
  3. 如果参数字符串 不以 “/” 或者 “./” 开头,则表示加载的是一个默认提供的 核心模块(位于Node的系统安装目录中),或者一个位于各级mode_modules目录的 已安装模块(全局安装或拒不安装);
  4. 如果参数字符串不以 “/”或者 “./” 开头,而是一个路径,则会先找到该路径目录,让后再以它为参数找到后续路径;
  5. 如果指定的模块文件没有被发现,Node会尝试为文件添加.js、.json、.node后,再去搜索。.js文件会以文本格式的javascript脚本文件解析,.json文件会以JSON格式的文本文件解析,.node文件会以编译后的二进制文件解析;
  6. 如果想得到require命令加载的确切文件名,使用require.resolve()方法。

3.3 模块的缓存

第一次加载某个模块时,Node会缓存该模块,以后再加载该模块,就直接从缓存取出该模块的module.exports属性。

3.3.1 模块缓存的案例1

  1.  require('./user.js');
  2.  require('./user.js').message = "hello";
  3.  require('./user.js').message;
  4.  // "hello"

上面代码中,连续三次使用require命令,加载同一个模块。第二次加载的时候,为输出的对象添加了一个message属性。但是第三次加载的时候,这个message属性依然存在,这就证明require命令并没有重新加载模块文件,而是输出了缓存。

3.3.2 模块缓存的案例2

再来看下面这个案例:

  1.  //a.js
  2.  exports.x = 'a1';
  3.  console.log('a.js',require('./b.js').x);
  4. exports.x = 'a2';
  5.  
  6.  //b.js
  7.  exports.x = 'b1';
  8.  console.log('b.js', require('./a.js').x);
  9.  exports.x = 'b2';
  10.  
  11.  //main.js
  12.  console.log('main.js ', require('./a.js').x);
  13.  console.log('main.js ', require('./b.js').x);

上面的例子中,当执行main.js时,加载了a.js,先输出x为a1,又加载了b.js,b.js中输出x为b1,又加载a.js,此时因为之前已经加载过a.js了,所以直接从缓存中读取,a.js的x为a1,所以先打印出“b.js a1”;然后b.js输出x为b2,所以a.js中打印“a.js b2”,然后a.js中又输出x为a2;所以main.js中打印“main.js a2”,第二次再打印的时候,因为再打印的时候,因为a.js和b.js都已经被缓存了,所以直接读取他们的exports的值,所以直接打印出“main.js b2”。

所有缓存的模块保存在require.cache之中,如果想删除模块的缓存,可以像下面这样写:

  1. // 删除指定模块的缓存
  2.  delete require.cache[moduleName];
  3.  
  4. // 删除所有模块的缓存
  5.  Object.keys(require.cache).forEach(function(key) {
  6.       delete require.cache[key];
  7.  })

注意:缓存是根据绝对路径识别模块的,如果相同的模块名,但是保存在不同的路径,require命令还是会重新加载该模块。

3.3.3 require.main

require方法有一个main属性,可以用来判断模块是直接执行还是被调用执行;

直接执行的时候(node module.js),require.main属性指向模块本身:

  1.  require.main === module
  2.  // true

调用执行的时候(通过require加载该脚本执行),上面的表达式返回false。

4. 深入了解模块原理

hello.js

  1.  var s = 'hello';
  2.  var name = 'world';
  3. console.log(s + ' '+ name + '!');

Node.js加载了hello.js后,它可以把代码包装一下,变成这样执行:

  1.  (function() {
  2.       //读取的hello.js代码
  3.      var s = 'hello';
  4.      var name = 'world';
  5.      console.log(s + ' ' + name + '!');
  6.      //hello.js代码结束
  7.  })();

这样一来,原来的全局变量s现在变成了匿名函数内部的局部变量。如果Node.js继续加载其他模块,这些模块中定义的“全局”变量s也互不干扰。

所以,Node利用JavaScript的函数式编程的特性,轻而易举地实现了模块的隔离。

但是,模块的输出module.exports怎么实现呢?

这个也很容易实现,Node可以先准备一个对象module:

  1.  //准备module对象
  2.  var module = {
  3.      id: 'hello',
  4.      exports: {}
  5.  };
  6.  var load = function(module) {
  7.      // 读取的hello.js代码
  8.      function greet(name) {
  9.          console.log('hello, ' + name + '!');
  10.      }
  11.  
  12.      module.exports = greet;
  13.      // hello.js 代码结束
  14.      return module.exports;
  15.  };
  16.  
  17.  var exported = load(module);
  18.  var exports = module.exports;
  19.  // exports = "helloworld"; 错误
  20.  //保存module
  21.  save(module, exported);

可见,变量module是Node在加载js文件前准备的一个变量,并将其传入加载函数,我们在hello。js中可以直接使用变量module原因就在于它实际上是函数的一个参数: 

  1.  module.exports = greet;

通过把参数module传递给load()函数,hello.js就顺利地把一个变量传递给了Node执行环境,Node会把module变量保存到某个地方。

由于Node保存了所以导入的module,当我们用require()获取module时,Node找到对应的module,把这个module的exports变量返回,这样,另一个模块就顺便拿到了模块的输出。

参考链接:https://javascript.ruanyifeng.com/nodejs/module.html

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值