node js 循环依赖问题

循环依赖,简单点来说就是a文件中require b文件,然后b文件中又反过来require a文件。这个问题我们平时可能并不大注意到,但如果处理不好可能会引起一些让人摸不清的问题。在node中,是如何处理循环依赖的问题的呢?

写个简单的例子来试验一下看吧。
定义两个文件:
a.js
  var b = require(’./b’);
  console.log(‘a.js get b:’ + b.b);
  module.exports.a = 1;</pre>
b.js
  var a = require(’./a’);
  console.log(‘b.js get a:’ + a.a);
  module.exports.b = 2;</pre>
执行
node a.js
输出的结果是

b.js get a:undefined
a.js get b:2

从打印的轨迹上来看,代码执行的流程大致如下:
<pre>a.js: b.js:
var b = require(’./b’);
var a = require(’./a’); // a = {}
console.log(‘b.js get a:’ + a.a);
module.exports.b = 2;
// b = {b: 2}
console.log(‘a.js get b:’ + b.b);
module.exports.a = 1;</pre>
node的加载过程,可以在lib/module.js文件中找到。与这个过程相关的代码主要集中在Module._load方法里。可以看到,node会为每个新加载的文件创建一个Module对象(假设为a),这个就是我们在a.js代码中看到的module了。在创建a之后,node会先将a放到cache中,然后再对它进行加载操作。也就是说,如果在加载a的过程中,有其他的代码(假设为b)require a.js的话,那么b可以从cache中直接取到a的module,从而不会引起重复加载的死循环。但带来的代价就是在load过程中,b看到的是不完整的a,也就是为什么前面打印undefined的原因。

Module的构造函数
function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;

  this.filename = null;
  this.loaded = false;
  this.exited = false;
  this.children = [];
}
Module._load方法
Module._load = function(request, parent, isMain) {
  //…

  var module = new Module(id, parent);

  //…

  Module._cache[filename] = module;
  try {
    module.load(filename);
  } catch (err) {
    delete Module._cache[filename];
    throw err;
  }

  return module.exports;
};
这个看似简单粗暴的处理手法,但实际上是node作者权衡各方面因素的结果。我们敬爱的npm作者issacs强调说了,这不是bug,而且近期内不会做什么改变。当然,issacs也给出了一些规避这个陷阱的建议。我总结了一下,主要有两点:一个是在造成循环依赖的require之前把需要的东西exports出去;另一个是不要在load过程中操作未完成的模块

所以上面的例子的一种处理方法就是把各自的exports语句放到require语句前面,然后再运行,可以看到打印了正确的值。

从前面的分析来看,循环依赖的陷阱出现的条件比较苛刻:一个是循环依赖,另一个是在load期间调用未加载完成的对象。所以大家平常不怎么会遇到。但我之前就曾华丽丽的邂逅了这个陷阱,在这里拿出来当一下反面教材。。。
场景简化后大致如下:我有一堆service,每一个service负责消费某一类消息,并且可能会产生新的消息给其他service消费。从消息传递上来看,并没有产生循环依赖。但我为了解耦,定义了一个消息中心center的角色出来进行消息分发。center主要是维护一个type -> service的map来路由消息,这样center就得把所有的service加载进来,于是产生了center->service的依赖。另一面,每个service又需要通过center来分发它们新产生的消息,于是又出现了service->center的依赖,循环依赖就这么出来了。刚好在service加载的过程中,又调用了center的一个方法,就发生了undefined的错误。

这个问题查清楚原因以后,解决起来并不困难。
一种方法就是按前面的方法,在代码层面上规避循环依赖的陷阱;
另外也可以在设计的层面上彻底避免循环依赖的出现。我的场景之所以出现循环依赖,是因为center和service都需要知道对方的存在,即 center <- -> service。如果采用依赖注入的方式,则可以切断这种直接依赖,类似于center <- container -> service。即加入一个container角色,把center和service都先加载进来,然后再用IOC的方法把依赖关系建立好。这样center和service都无须知道对方具体的文件所在了,也就不会循环的require对方了。

总的来说,循环依赖的陷阱并不大容易出现,但一旦出现了,在实际的代码中也许还真不好定位。它的存在给我们提了个醒,注意你工程中的依赖关系

时间: 2024-08-29 17:06:52

node js 循环依赖问题的相关文章

node.js 下依赖Express 实现post 4种方式提交参数

上面这个图好有意思啊,哈哈, v8威武啊.... 在2014年的最后一天和大家分享关于node.js 如何提交4种格式的post数据. 上上一篇说到了关于http协议里定义的4种常见数据的post方法 ,详细介绍请点击查看. 分别是这四种: www-form-urlencoded, form-data, application/json, text/xml Express 依赖 bodyParser 对请求的包体进行解析,默认支持:application/json, application/x-

node.js模块依赖及版本号

摘要: Node.js最重要的一个文件就是package.json,其中的配置参数决定了功能.例如下面就是一个例子 { "name": "test", "version": "1.0.0", "description": "test", "main": "main.js", "keywords": [ "test&qu

新 V8 即将推出和 Node.js

Node.js 就依赖于 V8 引擎, V8 引擎是由 Google 为 Chrome 浏览器编写的 JavaScript 虚拟机.从一开始,V8 的主要目标就是使 JavaScript 运行更加快速,或至少比竞争对手快.对于高度动态这并不容易.这部分是关于 V8 和 JS 引擎性能的演变. V8 引擎的核心部分是能够高速执行 JavaScript 的 JIT(Just In Time)编译器.这是一个动态编译器,可以在运行时优化代码.当 V8 最初构建时,JIT 编译器被称为 FullCode

node.js开发环境搭建

本篇介绍MacOSX下node.js开发环境的搭建. 目录: 1.介绍 2.搭建环境 3.开发 4.参考资料 1.介绍 node.js是一个基于Chrome JavaScript运行时建立的平台, 用于方便地搭建响应速度快.易于扩展的网络应用.Node.js 使用事件驱动, 非阻塞I/O 模型而得以轻量和高效,非常适合在分布式设备上运行的数据密集型的实时应用. node是一个Javascript运行环境(runtime).实际上它是对Google V8引擎进行了封装.V8引 擎执行Javascr

使用Node.js完成的第一个项目的实践总结

http://blog.csdn.net/yanghua_kobe/article/details/17199417 项目简介 这是一个资产管理项目,主要的目的就是实现对资产的无纸化管理.通过为每个资产生成二维码,来联合移动终端完成对资产的审核等.这个项目既提供了Web端的管理界面也提供移动端(Andorid)的资产审核.派发等相关功能.我们用Node.js构建该项目的Web端以及移动端的Serveice API. 项目主框架:Express 简介 Express 是一个非常流行的Node.js

seaJS循环依赖的解决原理

seajs模块的六个状态. var STATUS = {  'FETCHING': 1, // The module file is fetching now. 模块正在下载中  'FETCHED': 2, // The module file has been fetched. 模块已下载  'SAVED': 3, // The module info has been saved. 模块信息已保存  'READY': 4, // All dependencies and self are r

理解 node.js 的事件循环

node.js 的第一个基本观点是,I/O 操作是昂贵的: 目前的编程技术最大的浪费来自等待 I/O 操作的完成.有几种方法可以解决这些对性能的影响(来自Sam Rushing): 同步:依次处理单个请求. 优点:简单. 缺点:任何一个请求都会阻塞其余请求. 创建新进程:为每个请求创建一个进程处理 优点:容易. 缺点:扩展性不好,数百个连接意味着数百个进程.fork()是 Unix 程序员的锤子.因为它很有用,所有的问题都像是钉子.但这通常是多余的. 线程:为每个请求创建一个线程处理. 优点:容

node.js的作用、回调、同步异步代码、事件循环

http://www.nodeclass.com/articles/39274 一.node.js的作用 I/O的意义,(I/O是输入/输出的简写,如:键盘敲入文本,输入,屏幕上看到文本显示输出.鼠标移动,在屏幕上看到鼠标的移动.终端的输入,和看到的输出.等等) node.js想解决的问题,(处理输入,输入,高并发 .如 在线游戏中可能会有上百万个游戏者,则有上百万的输入等等)(node.js适合的范畴:当应用程序需要在网络上发送和接收数据时Node.js最为适合.这可能是第三方的API,联网设

Node.js包的依赖及版本号(转)

原文:  http://www.cnphp6.com/archives/64130 Node.js最重要的一个文件就是package.json,其中的配置参数决定了功能.例如下面就是一个例子 { "name": "test", "version": "1.0.0", "description": "test", "main": "main.js",