CommonJS 模块原理

·  阅读 1152

node全局对象

所有模块都可以直接调用

1)global:表示Node所在的全局环境,类似于浏览器中的window对象。

2)process:指向Node内置的process模块,允许开发者与当前进程互动。 例如你在DOS或终端窗口直接输入node,就会进入NODE的命令行方式(REPL环境)。退出要退出的话,可以输入 process.exit();

3)console:指向Node内置的console模块,提供命令行环境中的标准输入、标准输出功能。 通常是写console.log(),无须多言

准全局变量

这些变量在所有模块中都提供,所以看起来像是全局的,但实际不是。

1)__filename:指向当前运行的脚本文件名。

2)__dirname:指向当前运行的脚本所在的目录。

3)exports

4)module

5)require

CommonJS模块的特点

1) 所有代码运行在当前模块作用域中,不会污染全局作用域

2) 模块同步加载,根据代码中出现的顺序依次加载

3) 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。

module对象

根据CommonJS规范,每一个文件就是一个模块,在每个模块中,都会有一个module对象,这个对象就指向当前的模块。

module对象具有以下属性:

(1)id:当前模块的id

(2)exports:表示当前模块暴露给外部的值

(3)parent: 是一个对象,表示调用当前模块的模块

(4)children:是一个对象,表示当前模块调用的模块

(5)filename:模块的绝对路径

(6)paths:从当前文件目录开始查找node_modules目录;然后依次进入父目录,查找父目录下的node_modules目录;依次迭代,直到根目录下的node_modules目录

(7)loaded:一个布尔值,表示当前模块是否已经被完全加载

function Module(id, parent){
    this.id = id;
    this.exports = {};
    this.parent = parent;
    this.filename = null;
    this.loaded = false;
    this.children = []
}
module.exports = Module;
var module = new Module(filename, parent)
复制代码

可以看出,Node 定义了一个构造函数 Module ,所有的模块都是 Module 的实例。

模块实例的 require 方法

每个模块实例都有一个 require 方法。

Module.prototype.require = function(path){
  return Module._load(path, this)  
}
复制代码

由此可知,require 并不是全局命令,而是每个模块提供的一个内部方法,也就是说,只有在模块内部才能使用require命令。

Module._load = function(request, parent, isMain) {

  //  计算绝对路径
  var filename = Module._resolveFilename(request, parent);

  //  第一步:如果有缓存,取出缓存
  var cachedModule = Module._cache[filename];
  if (cachedModule) {
    return cachedModule.exports;
  }
  
  // 第二步:是否为内置模块
  if (NativeModule.exists(filename)) {
    return NativeModule.require(filename);
  }

  // 第三步:生成模块实例,存入缓存
  var module = new Module(filename, parent);
  Module._cache[filename] = module;

  // 第四步:加载模块
  try {
    module.load(filename);
    hadException = false;
  } finally {
    if (hadException) {
      delete Module._cache[filename];
    }
  }

  // 第五步:输出模块的exports属性
  return module.exports;
};
复制代码

load里面执行_compile

Module.prototype._compile = function(content, filename) {
var self = this;
var args = [self.exports, require, self, filename, dirname];
return compiledWrapper.apply(self.exports, args);
};
复制代码

将模块文件读取成字符串,然后执行模块代码,输出 exports 。

!!!

// b.js 中 引入 a.js
var a = require('a.js')

复制代码
  1. 调用ModuleB自身的require方法
  2. rquire函数中return _load 方法,很明显_load返回的应该是a.js中的export
  3. _load 函数中:

(1)判断a.js模块是否有缓存,若有则直接返回export (2)如无缓存,则 var module = new Module('a.js'), 生成一个新实例 (3)把这个新实例加入到缓存中 (4)加载该新实例,执行module.load函数 (5)返回该实例的exports

(4) 步骤中,如果给新实例module加exports 新实例的load函数中,即给新实例中添加exports属性 load中,执行_compile,然后 this.loaded = true

load = function() {
  var content = fs.readFileSync(filename, 'utf8');
  // a.js的文件字符串
  module._compile(stripBOM(content), filename);
})

_compile(content, filename) {
    // 这里的this指的是新创建的实例a.js
    // 执行源码,相当于把该新实例的几个变量传进去
    compiledWrapper(content, this.exports, this.require, this)    
}
var compiledWrapper = function(content, exports, require ,module) {
    // 执行文件代码字符串,这就是为什么content文件里面可以直接用exports, require, module 了,因为function(content, exports, require ,module)
    eval(content)
    // 所以文件里module.exports = xxxx   就赋值了
}
复制代码

ES6 与 CommonJS 的差异

两个重大差异:

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

CommonJS 模块的加载原理

CommonJS 的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。

即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS 模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。

ES6 模块的循环加载

ES6 的模块有几个需要注意的地方: 自动采用严格模式,即使你没有使用"use strict"; 顶层的this指向undefined;

|CommonJS|运行时加载|拷贝| |ES6 模块|编译时输出接口|引用|

commonJs的运行时加载和es6的编译时加载

循环加载的,看阮一峰吧

www.ruanyifeng.com/blog/2015/1…

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改