2019独角兽企业重金招聘Python工程师标准>>>

一 什么是模块.

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 也可以开放出来了.

(由于工作及生活还不稳定,没法提供一个准确的更新时间,博客不定期更新,如果觉得可以,希望大家多支持,点赞,如果有疑问请留言)

转载于:https://my.oschina.net/gclinux/blog/492000

第二章. node中的模块和require相关推荐

  1. 计算机和学数制和码制,[文学]第二章 计算机中的数制和码制.ppt

    [文学]第二章 计算机中的数制和码制 注意:十六进制计数法是为了克服二进制计数法书写麻烦而引入的一种进位计数制:在编写汇编语言源程序时,如果一个十六进制数的最高位为A?F中的一个数字符号时,该数前面必 ...

  2. 世界上第一台数字计算机图片大全,第二章 计算机中的图世界

    <第二章 计算机中的图世界>由会员分享,可在线阅读,更多相关<第二章 计算机中的图世界(13页珍藏版)>请在人人文库网上搜索. 1.第二章第二章 计算机中的图世界计算机中的图世 ...

  3. (Java零基础学习笔记)第二章 Java中的基本语法

    前言: 大家好! 我是BA unravel .如果你想和我一起学习JAVA,欢迎大家一起来学习这个世界上最好的语言! 学习目标: 一周掌握 Java 入门知识 学习内容: 1. 搭建 Java 开发环 ...

  4. 半导体物理第二章 半导体中的杂质和缺陷能级

    半导体物理第二章 半导体中的杂质和缺陷能级 为什么要研究杂质和缺陷能级啊?第一是因为这事客观存在的:因为现实中有一些偏离理想的复杂情况,首先,原子自己不是静止的,不是在严格的周期性晶格的格点位置就不动 ...

  5. Node中HTTP模块

    文章目录 Node中的Http 一.HTTP服务器 1.http.server事件 2.http.ServerRequset请求信息 3.获取GET请求内容 4.获得POST请求内容 5.http.S ...

  6. 管理系统中计算机应用第二章,管理系统中计算机应用第二章.doc

    管理系统中计算机应用第二章.doc 第二章 企业管理的信息化平台 一.单项选择题(本大题共30小题,每小题1分,共30分) 1.从理论上分析,传统的IP地址(IPv4)最多可以访问的用户数是( ) A ...

  7. Caché程序员必须知道符号与缩写 第二章 ObjectScript中使用的缩写

    Caché程序员必须知道符号与缩写 第二章 Caché ObjectScript中使用的缩写 ObjectScript中可用的命令.函数和特殊变量的缩写表. 以下是ObjectScript中使用的名称 ...

  8. 仿牛客网项目第二章:开发社区登录模块(详细步骤和思路)

    目录 1. 发送邮件 1.0 三步走 1.1 邮箱设置 1.2 Spring Email 1.3 模板引擎 1.4 发送邮件的过程 1.5 检验发送邮件的过程 2. 开发注册功能 2.0 注册功能的步 ...

  9. C# 线程手册 第二章 .NET 中的线程 线程的优势

    额,我猜你现在可能会这么想"既然线程会对我的程序产生负面影响,那么我为什么要使用它呢?".其实问题的关键不在于到底用不用线程,而在于何时何地使用线程.知道在什么情况下应该使用线程是 ...

最新文章

  1. insertionSortList
  2. 深入.NET 4.0之,LazyT点滴
  3. UBOOT手动设置环境变量
  4. 深入理解Nginx 模块开发与架构解析-陶辉 读书笔记
  5. Spring Cloud原理
  6. c语言获取系统剩余内存_C语言编程中的“堆”和“栈”七大不同之处
  7. asp.net mysql helper_asp.net使用SQLHelper操作数据库
  8. 12.2 asmca fails with 'ORA-00845'
  9. .Net Core - 使用Supervisor进行托管部署
  10. 日常开销记账表格_日常NetQuote的开销我们的管理风格在很大程度上是一种欺骗...
  11. 如何提高FPGA的工作频率
  12. 小程序发布成功后搜索不到怎么办?
  13. ept技术_速懂X86虚拟化关键概念 - Intel EPT
  14. 手动配置协议和服务器POP,在outlook上添加账户并介绍邮件协议相关知识
  15. VMware vSphere 服务器虚拟化部署安装图解
  16. 自称很菜的二本大龄程序员居然拿到百度offer(百度面经)
  17. C++ Reference: Standard C++ Library reference: C Library: cmath: cosh
  18. Golang Append()详解
  19. 二叉排序树实现英文文章单词统计
  20. 英文SEO移动端搜索优化指南

热门文章

  1. ubuntu14.04安装tun/tap网络设备
  2. maven -Dmaven.skip.test=true 和 -DskipTests=true的区别
  3. 2016 大连网赛---Weak Pair(dfs+树状数组)
  4. 开发外包注意事项——iOS APP的开发
  5. 分享25个CSS前端网页设计常用技巧
  6. delphi打开word文件(刚刚学会的嘿嘿~)
  7. 自己遇到oracle的错误记录
  8. 模态对话框阻塞主线程的话不影响其他线程操作主线程控件(不阻塞)
  9. tcp 服务端如何判断客户端断开连接
  10. python3 join函数_Python3 join函数和os.path.join用法详解