node中的require和exports
发布于 13 年前 作者 leexiaosi 21663 次浏览 最后一次编辑是 8 年前

<h1>题记:</h1> <br/><span style=“font-size: 13px;font-weight: normal”> 有一天去面试,面试官问我,nodejs中的require是阻塞还是非阻塞。我心想,模块的引用都是先require之后再使用,应该是阻塞,但是对require的实现机制并不了解,就坦诚说不知道了。回来之后,我就翻了一遍node的源码,就简单写一下自己对node中模块的实现理解,纰漏之处,望大家指正。</span> <br/><h1>js模块的实现</h1> <br/>其实早先时候在cnodejs问答中已经有人提到,模块中module,exports是怎么实现的,什么含义。当时是我回答的说是一个开发的接口,其实这是不对的。node中模块的实现,其实是依赖于闭包的,也就是说,module,exports其实都是外部传入的参数,这个简化形式如下: <br/><pre escaped=“true” lang=“javascript” line=“1”>function NativeModule(id){ <br/> this.id=id; <br/> this.filename=id+".js"; <br/> this.exports={}; <br/>} <br/>NativeModule.require=function(id){ <br/> var module=new NativeModule(id); <br/> module.compile(); <br/> return module.exports(); <br/>}; <br/>NativeModule.prototype.compile = function() {   <br/>  (function(exports, require, module, __filename, __dirname){ <br/>        exports=module.exports=function(){alert(“leexiaosi”)}; <br/> }(this.exports, NativeModule.require, this, this.filename)); <br/>}; <br/>NativeModule.require(“leexiaosi”)</pre> <br/>(附:上面的代码可以复制出来直接运行) <br/>通过上面的代码我们惊奇的发现,其实我们看见的模块文件的代码,一般而言都是类似 <br/><pre escaped=“true” lang=“javascript” line=“1”>exports=module.exports=function(){alert(“leexiaosi”)};</pre> <br/>同时我们也就明白,为什么在模块文件中用var定义的变量都是私有的了。这里关键点就是 <br/><pre escaped=“true” lang=“javascript” line=“1”>NativeModule.prototype.compile</pre> <br/>这个函数的实现。为了方便理解,我们设 <br/><pre escaped=“true” lang=“javascript” line=“1”>var fnImport=function(exports, require, module, __filename, __dirname)</pre> <br/>那么,根据闭包的原理,这个函数在执行时,其[[scope]]会记录fnImport这个函数定义的作用域的各个变量, <br/><ul> <br/> <li>fnImport函数中的形参值,即this.exports, NativeModule.require, this, this.filename</li> <br/> <li>fnImport函数中function声明的函数</li> <br/> <li>fnImport函数中通过var声明的变量</li> <br/> <li>fnImport函数的父函数的作用域[[scope]]</li> <br/></ul> <br/>fnImport这个函数对其fnImport.[[scope]]的操作都是有权限的。故this.exports会被更改,尽管在NativeModule的构造函数中this.exports的定义是空对象。 <br/>当然,事实上的NativeModule的实现要比这个复杂得多,尽管一般而言,非核心模块加载都是依赖于module这个模块,但是,module的实现模块加载的基本思想也是这样,只是module中增加了模块的检索功能。而模块文件中,require正是NativeModule.require这个函数。从return module.exports()来看,require初次加载模块时候必然是阻塞的(初次加载之后会被缓存,所以加载之后再require就不是阻塞的了)。

8 回复

翻源码找答案的人伤不起啊!支持

最近也剛好有機會用到nodejs <br/> <br/>看到你的Blog覺得很棒 <br/> <br/>不過我不太懂您的阻塞跟非阻塞的意思, 請問有無英文單字可供參考? <br/> <br/>謝謝您

我英文不太好 <br/>阻塞应该是blocking <br/>非阻塞应该是nonblocking. <br/>其实是这样的,例如核心脚本,在lib下的node脚本,如module,fs,http等,其编译后的存储是使用的json对象,来避免文件的读写,即形式如{“module”:"…",“http”:"…"},会伴随着node.exe一起加载,require核心模块是不涉及到IO读写的,但是非核心脚本,存在着IO读写,但关键是,无论是核心模块还是非核心模块的脚本都是使用了js中类似"eval"的 <br/>process.binding(“evals”).NodeScript.runInThisContext来解析,解析必然是阻塞的

謝謝你的回答, 清楚許多^^ thanks

最近我也在看相关内容,补充一下。 <br/>nodejs的环境变量 NODE_MODULE_CONTEXTS。 <br/>默认情况下它为0,表示module不使用独立的context。 <br/>设置为1,表示为每个module创建独立的context。 <br/>一般我们没有设置,也就是默认情况,nodejs为module创建了闭包环境 <br/>lz说的情况主要是只这个默认情况下。 <br/>对于 var a = “foo”; 这两种情况没有什么区别。 <br/>但 a = “foo” 就有明显区别了,默认情况下a将成为全局变量。而=1时a仍然是局部变量。

最近我也在看相关内容,补充一下。 <br/>nodejs的环境变量 NODE_MODULE_CONTEXTS。 <br/>默认情况下它为0,表示module不使用独立的context。 <br/>设置为1,表示为每个module创建独立的context。 <br/>一般我们没有设置,也就是默认情况,nodejs为module创建了闭包环境 <br/>lz说的情况主要是指这个默认情况下。 <br/>对于 var a = “foo”; 这两种情况没有什么区别。 <br/>但 a = “foo” 就有明显区别了,默认情况下a将成为全局变量。而=1时a仍然是局部变量。

光是这个问题,其实很好回答, 如var express = require(‘express’), 这个require必然是阻塞的,不然后面就没法调用到 express了。

每个模块都是一个对象 而且这个对象在整个项目中都是同一个 它只会初始化一次 就是第一次require的时候

回到顶部