nodejs中require的认识

关于Nodejs的优缺点以及它的使用场景这里不会多说,因为既然是想要看Nodejs的文章,最主要的原因,应该是为了想要掌握这门技术吧?我这么认为。在刚接触Nodejs的最初的几天里,感觉自己一直就像个没头的苍蝇,到处乱撞,看东西感觉心烦意乱的,后来慢慢的发现了一些规律,理解了一些基本的东西,才开始变得顺理成章了,而在这个过程中,对require的认识,我觉得是至关重要的,所以就有了这篇文章。

Nodejs的核心方法–require

如果说Nodejs中最基础最核心的一个方法,我认为就是模块加载方法了,如何正确的理解这个方法的使用,关系到是否能更好更熟练的写出nodejs的代码,最近要使用nodejs,所以也花了挺多时间看这方面的资料,因为不喜欢记忆,喜欢去理解一个东西的我来说,快速的掌握这个,并且把常用模块API都记下来简直就是不太现实的,所以当中也有过暴躁抓狂的时候,不过也算是走了过来,现在虽然仍然是处于学习阶段,但是对于require也有了一点自己的理解,所以就在这里分享一下,如果有说的不对的地方,请多指教。

这里,我就首先假设,您的电脑上已经配置了Nodejs,并且,您对Nodejs有一个最基础的认识,比如知道如何执行一个nodejs的文件,就足够了,如果这些您还不了解,那么请先暂停阅读,先安装nodejs,并了解一个最简单的nodejs程序,是怎么写的,比如使用nodejs输出“Hello World”。如果这些您都了解,那么请继续向下看。

使用方法–类似于匿名的自执行函数

我是做前端的,那么就以前端的JS代码进行对比的说明,首先要说到的就是模块化,前端的模块化最常见的就是封装到一个函数内部吧,而我觉得比较常用的就是使用匿名函数直接生成一个单独的模块,就像下面这样:


(function(){
	//一段逻辑代码。
})();


这样,外部的所有的程序都不能访问该模块内部的属性和方法,require也具有相同的性质,举个最简单的例子(变量的访问):

app.js文件中,使用require加载一个test.js的模块。可以在app.js中这么写:


var strapp = "this is app.js";
require("./test");
console.log("next two log is from app.js");
if(typeof strapp == "undefined"){
    console.log("strapp is undefined!");
}else{
    console.log("strapp="+strapp);
}
if(typeof strtest == "undefined"){
    console.log("strtest is undefined!");
}else{
    console.log("strtest="+strtest);
}


test.js中这么写:


var strtest = "this is test.js";

console.log("next two log is from test.js");
if(typeof strapp == "undefined"){
    console.log("strapp is undefined!");
}else{
    console.log("strapp="+strapp);
}
if(typeof strtest == "undefined"){
    console.log("strtest is undefined!");
}else{
    console.log("strtest="+strtest);
}


那么在执行app.js之后,就会在Nodejs command面板中,显示以下信息:


next two log is from test.js
strapp is undefined!
strtest=this is test.js
next two log is from app.js
strapp=this is app.js
strtest is undefined!


对比使用JS中匿名自执行函数来写一个相同的例子:


var strapp = "this is app.js";
(function(){
    //代替nodejs中的require("./test");
    var strtest = "this is test.js";

    console.log("next two log is from test.js");
    if(typeof strapp == "undefined"){
	console.log("strapp is undefined!");
    }else{
	console.log("strapp="+strapp);
    }
    if(typeof strtest == "undefined"){
	console.log("strtest is undefined!");
    }else{
	console.log("strtest="+strtest);
    }
})();
console.log("next two log is from app.js");
if(typeof strapp == "undefined"){
    console.log("strapp is undefined!");
}else{
    console.log("strapp="+strapp);
}
if(typeof strtest == "undefined"){
    console.log("strtest is undefined!");
}else{
    console.log("strtest="+strtest);
}


这个时候的执行结果,对于JS作用域有所了解的,都能想象到,最后的输出结果吧?结果就是:


next two log is from test.js
strapp=this is app.js
strtest=this is test.js
next two log is from app.js
strapp=this is app.js
strtest is undefined!


如果您对这段JS的执行结果,有什么疑问,说明您对作用域的理解还不足,请先查看另外一篇文章,也许可以给你一些帮助:浅析作用域链–JS基础核心之一

类似于对象

这种方法算是最常见的了,因为所有的核心模块,都算是基于该类的。

那就是在执行require加载模块之后,会返回一个对象,一个称之为module.exports(关于module以后再进行说明,本篇不涉及)的对象,既然返回的是一个对象,那么这个对象就有可能是一个空对象,包含一个或多个属性或者方法的对象。

首先,我们来看下,什么时候会返回一个空对象?

就像是上面的代码,上面的那个实例的代码,其实就是返回了一个空对象。下面我们把app.js的内容进行简化为如下:


var strapp = "this is app.js";
var test = require("./test");
console.log(test);


执行node app.js,看看在command的控制台上,会出现什么样的信息呢?


next two log is from test.js
strapp is undefined!
strtest=this is test.js
{}     //注意这里,这就是console.log打印出的结果


OK,到了这里,我们就对这个require有了更多一点的认识。

接下来,再修改test.js的内容为如下内容:


var strtest = "this is test.js";
module.exports = strtest;


这个时候,再次执行node app.js,会有什么效果呢?此时就只有一条输出,其内容为:


this is test.js


由此可以看出,在通过require("./test")加载模块之后,这里返回的却直接是一个字符串了,为了能确认这一点,可以自己查看一下test的类型就能确定了。这里不再多说。

说到这里,想必也能发现一个问题了,就是前文有提到过,require加载模块之后,会返回module.exports对象,这里说是对象就有些不严谨了,因为像上面这样,返回的并不是对象,而是一个字符串,所以,只能说是,require加载模块之后,返回的是module.exports的属性值。

并且,通过上面的例子,我们还能发现一个问题就是,Nodejs在变量类型上,是完全和js相同的,比如这里,可以给一个空对象赋值,把这个对象变成字符串。其实吧,也算是正常,Nodejs本就是使用的js的语法,只是个人总觉得,有些东西,只有试过,尝试了多种用法之后,才能放心的使用。

既然这里可以返回一个字符串,那肯定也可以返回其他类型的数据了,比如一个函数,一个对象?所以,我们就可以利用这一特性,把一些单独的模块直接拉出来,然后进行处理,并且,这些单独的模块,我们还不需要担心,定义的变量什么的会影响到其他的模块。因为就算是在加载模块的文件中(例如上面的app.js),也不能调用被加载的模块(例如上面的test.js)中的私有变量,只能通过require的返回值,调用module.exports的属性值中的信息。

说到这里,依然拿出JS中类似的写法,这里的require依然类似于JS中的自执行函数,比如下面的JS例子,就可以完成和上面的nodejs中两个文件相同的效果。说的更相关一点,就是require就是在使用闭包。


var test = (function(){
    var strtest = "this is test.js";
    return strtest;
})();

var app = (function(){
    var apptest = "this is app.js",
	gettest = test;
	
    console.log(gettest);
})();


使用js执行,就可以有相同的效果,并且,test中的私有变量不能访问app中的私有变量,而app中的私有变量,也不能访问test中的私有变量,这样,就达到了和nodejs相同的效果了。当然啦,同样可以通过一些对外的公开的接口访问到内部的一些信息,比如这里的test方法,就返回一个strtest的属性值。

虽然,这里都是写的最简单的,但是转念想一想,如果test方法返回的是一个对象呢?是一个函数呢?这个熟悉JS的都应该能明白的,不多说。

总结其实看到这里,应该也对require有了一点基本的认识了吧,我就觉得,require其实就是会返回一个数据的自执行函数,就像前面的这个代码一样,只是写法不同而已。

再对比到nodejs这边,如果使用require加载了一个模块中,module.exports返回的是一个函数呢,返回的是一个对象呢?嘿嘿…相同的用法的。

所以,想象一下,我们常使用的一些核心模块的方法,比如:


var exp = require('express'),
    app = exp.createServer();


现在看来,也只是在require中,返回了express模块中的module.exports的属性值,并把该值保持到了exp变量中,那么该变量中,就有包含了所有的module.exports中的属性值,所以通过不同的方法名,就可以调用到相应的方法。

所以也就通过exp.createServer方法,创建了一个服务。有兴趣的话,可以看看express中,存在多少的属性和方法,直接在上述的基础上,使用console.log(exp)即可(参数只要exp,才能看到的哦,还有就是,你当前的工程,是添加了express模块的,可以检查当前工程的package.json文件中的dependencies属性中,是否有express的相关信息,也可以直接查看:node_modules文件夹中查看,是不是有express的模块,这里不对这个进行细说)。

说到这里,我觉得如果能理解到require的工作原理,也算是nodejs的基本入门了吧,因为再其他的,就是了解模块的方法的功能了,了解怎么用了。这些都是需要一些记忆和练习的,我个人觉得,这都是次要的了。

重复加载模块

当有时候,我们重复加载了同一个模块,会不是有问题呢?比如下面这样的代码:app.js中:


var test1 = require('./test.js');
console.log("test1="+test1);

var test2 = require('./test.js');
console.log("test2="+test2);


test.js中:


var num = Math.random();
console.log(num);
module.exports = num;


这个时候,会不会加载两次呢?试试就知道了,不会的,这个时候的控制台的信息显示结果顺序为(是一个随机数):


console.log(num);
console.log("test1="+test1);
console.log("test2="+test2);


所以肯定是require中进行了处理,保证在require时,如果已经加载过该模块,那么就把之前加载的进行赋值,所以,在这里,app.js中的test2的赋值,其实质也就是:test2 = test1

这些都清楚了,我觉得对于nodejs就算是有一点认识了吧,反正在我刚学习nodejs的前几天,没有想通一些事情时,就会感觉有劲无处使,没有方向的乱撞的感觉。明白了本文的这些东西之后,就感觉一切都通顺了。

nodejs这块,我还只是个新手,所以对于require的理解,也有很多不足,如果文中有什么错误,或者描述不当,请指教。

暂时的认识只有这么多,其他新的认识,留待以后的补充。

本文地址:http://www.zhangyunling.com/?p=170

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>