您的位置:首页 > Web前端 > Node.js

第二章. node中的模块和require

2015-08-13 18:03 507 查看

一 什么是模块.

JavaScript诞生初,它只不过是一个网页的小脚本而已,没有人会想到它会发展到现在能有大量的库,工具,组件变得如此复杂,慢慢地javascript发展中,人们发现javascript有个先天的缺陷--缺少模块.

在其他语言中,java有类,python有import, php 有include和require甚至比它底层的C也有include.而javascript只能通过 <script>标签引入,这种方式令代码变得杂乱,依赖变得不清晰,安全性也不好(全局变量容易被污染).

为了解决问题,node引入了模块这个概念(准确来说是CommonJS引入了模块).nodejs中的模块具有隔离作变量用域(包)防止变量污染和实习私有变量,开放接口和成员,提供外部引用,解决依赖不清晰的问题.

关于隔离和接口,在后面会再讲

PS:nodejs模块实现代码主要在 lib/module.js.


二 node模块分类

node模块有下面几种:

1.系统模块(核心模块):由nodejs自带提供,可以是用js或者C++编写的,已经经过编译的模块.
2.文件模块(用户模块):由用户编写的.

核心模块在源代码中已经编译了,以二进制文件存在,模块引入时可以直接加到内存,文件定位和编译都省略掉,并在路径分析中优先,所以速度是最快的.

文件莫模块运行的时候动态加载,需要经过路径分析和文件定位,编译,速度一般比核心模块慢.





三 require 方法:

1.require作用:

require方法是nodejs提供的用于引用模块的方法.例如我们需要引入文件模块,则使用下面代码




varmod = require('module_name')


此句执行后,Node内部会载入内置模块或通过NPM安装的模块。require函数会返回一个对象,该对象公开的API可能是函数,对象,或者属性如函数,数组,甚至任意类型的JS对象。


2.后缀:

require 默认接受以下后缀:

1 .js 文件 node会通过fs模块同步方式读取文件内容,在文件头尾加入内容,以实现作用域隔离和module.exports,编译并且执行. 其他非node,json,js后缀的统一也当作.js处理.
2.json 文件 node会通过fs模块同步方式读取文件内容,并调用JSON.parse 返回执行JSON.parse后的json对象.
3.node 文件 这是C++编写文件 node会调用dlopen()方法加载编译后生出来的文件.

当文件不带后缀,node会依照 目录 .js .json .node 的顺序进行查找.如果所有模块路径都找不到该文件,则抛出异常.


3. 模块路径(路径分析):

require方法接受下面的引用方式
1.require("模块名"),或者 require('目录/模块名') 不以./开头的相对路径
如果模块名不是路径,也不是内置模块,Node将试图去当前目录的node_modules文件夹里搜索。如果当前目录的node_modules里没有找到,Node会从父目录的node_modules里搜索,这样递归下去直到根目录。如果找不到文件,则会抛出异常

2.require("./模块名")或者 require('./目录/模块名') , 以 ./开头的相对路径
node会直接加载以node运行的工作目录为基准的该模块.如果模块不存在则抛出异常

3 require("/目录/模块名") 或者 require('F:/目录/模块名') 绝对路径
nodej会直接加载该模块.如果模块不存在则抛出异常.

*在这里值得注意的是,如果是想加载当前目录的模块 必须使用:require("./模块名"),使用require("模块名")是不行的


四.模块缓存

对于已加载的模块Node会缓存下来(核心模块会放在NativeModele._cache上,文件模块放在Module._cache上),而不必每次都重新搜索和编译执行。多次require的同个模块只会被执行一次,下面是一个示例

//modA.js


console.log('模块modA开始加载...')

exports =function() {

console.log('Hi')

}

console.log('模块modA加载完毕')


//init.js


varmod1 = require('./modA')

varmod2 = require('./modA')

console.log(mod1 === mod2)


命令行执行:


node init.js

输出如下

$node init.js
模块modA开始加载...
模块modA加载完毕
true



可以看到虽然require了两次,但modA.js仍然只执行了一次。mod1和mod2是相同的,即两个引用都指向了同一个模块对象。

模块的缓存是基于文件的绝对路径相关为key的数组,这样保证了模块缓存的唯一性,不会因为require中不同的写法导致重复载入.

例如:





//文件:demon/init.js

varmod1 = require('../demon/modA') ;

varmod2 = require('./modA')

console.log(mod1 === mod2)

我们依旧可以得到相同的结果.


五.模块的作用域隔离:

1.什么是作用域.

任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域是通过"闭包"来实现,由于闭包作用范围,我们又把其分为全局变量和局部变量.





1.1 闭包

闭包是javascript的作用域实现方式,简单来说,一个闭包的范围就是一个function的开始到结束之间.而整个程序我们可以认为是最大的闭包.而闭包具有"单向链性",即子级的闭包可以访问父级闭包自身的和能访问的对象.
例如下面代码:

var globalVar='I am golbal val';

function packetA(){
var aVar='I am a var in A';
function packetB(){
var bVar ='I am a var in B';
console.log(globalVal); // 输出 I am golbal val
console.log(aVar); // 输出 I am a var in A
console.log(bVar);//bVar ='I am a var in B';
}

console.log(globalVal); // 输出 I am golbal val
console.log(aVar); // 输出 I am a var in A
console.log(bVar); // undefined
packetB();


}

packetA();

//packetB() //在这里由于packetB在packetA内,不可被访问,因此会抛出错误


2.node 作用域隔离的实现.

其实node隔离作用域实现非常简单,就是在require的实现中,针对.js后缀的文件,node并非直接把读入内容进行执行,而是在其开头加入到一個封包類这个类就是一个隔离,再通return实现开放接口.

思路大概这样
(function(){

return function(){
//你的代码
}

})()
当然,实际没有这么简单.我明白其利用reurn 产生开放接口,利用function来实现隔离就够了.

如果你写比较多的面向对象js,你看了这段代码,你会知道可以通过this来实现类似module.exports开放(public)的功能.

例如:
//文件 modA.js





this.publicVal=100
//文件 init.js




var test = require ("./modA.js");

console.log(test.
publicVal);//输出100

当然,官方并不希望我们这样做.node更希望我们在文件的末端统一使用 module.exports=开放的内容 这种方式实现开放内容.

附带:模块实现实际精简代码:
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
if (parent && parent.children) {
parent.children.push(this);
}

this.filename = null;
this.loaded = false;
this.children = [];
}

//....
module.exports = Module;
///...
Module._load = function(request, parent, isMain){
//...
return module.exports

}






六 ,module.exports

1.exports和module.exprts的疑惑

许多人都曾经纠结过为何存在exports情况下,还存在module.exports,理想情况只要给exports赋值即可.

首先,我们来理解一下js的对象引用机制.

var a=b={}, c={};
console.log(a);
console.log(b);
console.log(a==b); //true javascript对象的比较是比较指针是否指向同一个地址
console.log(a==c);//false javascript对象的比较是比较指针是否指向同一个地址而不是内容是否相同(基础类型除外)
a.test=1;
console.log(a);
console.log(b);
console.log(a==b); //true


b.test=2;
console.log(b);
console.log(a);
console.log(a==b); //true

a=123;
console.log(a);
console.log(b);
console.log(a==b);//false



js的对象赋值并非跟php那样复制一份,而是把对象的地址给了变量,直接给变量赋值时候javascript会创建一个新的内存,再把变量指针指向新的内存地址,从而改变值.(ps:形参也一样,exports实际是一个形参)


exports是为了方便我们更改module.exports而设立的,真正的开放的接口是module.exports(在_load方法里面 返回的是module.exports) ;
exports和module.exports一开始指向同一个内存空间,如果你不直接对exports直接赋值而是对其子成员赋值,例如:
exports.son=1;
那么你就不会改变 module.exports == exports的关系

但如果你这样:

exports={son:123};

那么就会改变module.exports == exports的关系, module.exports == exports将会不成立,因为他们已经指向不同的内存块.

当_load方法返回module.exports的时候(把它开放出去),你exports的内容实际不能被返回了,也就是它并没有被开放出去.

但我们可以这样:

module.exports={son:321};

虽然直接对module.exports进行赋值操作,一样会破坏module.exports == exports的关系,不过使用module.exports即使module.exports == exports的关系不再成立,但不影响其把对象开放出去.

在这种条件下,我建议使用module.exports作为一种习惯,另外最好不要像例子中这样直接对module.exports或者exports进行操作,而是操作其子对象,因为无论直接操作module.exports还是exports,都会破坏两者之间的正确关系(指向同一内存块).这种操作非常不安全,直接操作module.exports可能会丢失之前开放的对象,而直接操作exports会导致你没法开放对象.


2.使用this开放对象和使用module.exports开放对象分析.

官方提供了module.exports这个对象来开放对象,但通过代码实现的分析,我们知道我们可以用this来实现开放对象.

例如:


例如:
//文件 modA.js





this.publicVal=100

//文件 init.js




var test = require ("./modA.js");

console.log(test.a);//输出100






但这样做并不好,首先this的指向在javascript中就是一个非常容易犯错的东西,this会因为调用对象不一样而指向不一样.而module.exports具有固定指向性.如果使用this,那么只能在模块的根包范围(全局)范围内使用

看下面的例子:




/文件 modA.js





this.publicVal=100;

function test(){

this.want2Public =200

}

test();

//文件 init.js




var test = require ("./modA.js");

console.log(test.
publicVal);//100

console.log(test.want2Public);//undifined

由于this的不稳定,你很可能会自己挖坑.
而module.exports就不存在这个问题.
上面的例子,把this改为module.exports,那want2Public 也可以开放出来了.









(由于工作及生活还不稳定,没法提供一个准确的更新时间,博客不定期更新,如果觉得可以,希望大家多支持,点赞,如果有疑问请留言)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: