Node.js入门系列(二)模块、REPL

14,508 阅读11分钟

写在前面

上一期《Node.js入门系列(一)》更新以后,有很多小伙伴评论和私信,这个系列能学到很多东西。整理和修改了一个星期,终于,写出了自己比较满意的版本。

Node.js入门系列将是一整套参考教程,系列将以结构脑图、文字解释、概括总结、练习实例、面试考点的形式讲述知识点。如果喜欢,欢迎关注点赞,后续会出续更。

上集回顾

上集我们谈了:Node.js能解决什么前端痛点问题、Node.js特点及适用场景。

如果想再次阅览,请点击这里挑选需要阅读的章节:
漫谈Node.js入门
Node.js入门系列(一)

本节学习目标

认识Node.js 的模块,以及Node.js的交互式运行环境。

本节偏理论,可以脱离IDE进行阅读,适合在碎片时间进行阅读和记忆。

本节学习目录

一 Node.js中的模块

二 Node.js的交互式运行环境

三 本节相关面试考点

四 本节小结

五 文末

一、Node.js中的模块

在说Node.js之前,先忘掉这个概念,头脑风暴一下,你在哪些地方听说过"模块"这个词?没错,前端模块化。作为入门教程,我觉得还是有必要详细介绍一下模块的相关东西,虽然前端模块化不属于本节的重点。

模块与整体项目的关系可以类比成积木与房子的关系。每一个积木模块都是有自己的特殊形状和功能的,只有按照一定的顺序才能最终搭建成一个稳定可靠的房子。为什么要把一整个房子拆成小的积木模块呢?因为小的积木模块功能单一,方便制作,某一块坏掉了,换掉就行,不至于造成整体都坏掉,维护起来成本低。思维回到前端:

在早期的时候,前端一个页面包含三部分,CSS、HTML、JavaScript,一个页面就是一个.html文件,之所以能这么写,是因为当初的前端页面主要作用就一个:给用户看。职能单一、逻辑简单,使得开发过程中不需要单独写CSS、JS文件然后再引入。

到后来,慢慢的前端复杂了,不仅要好看,还要有交互,这样一来前端项目就开始复杂了,前端为了提升开发效率,节省迭代成本,开始将代码分开,一个整体功能,分成很多文件,每一个文件实现一个功能,最后再将他们引入到页面中去,这就是前端模块化的第一阶段:文件拆分。这样确实给开发带了方便,但同时也带来了问题,比如:我们在任何一个文件都可以引入和修改其他模块文件,会造成误删误改,命名冲突的问题;另外呢,模块之间的依赖关系,需要我们手动如维护,多个模块的情况下,关系网混乱,耦合性依然很高。问题综合起来就是:没有私有作用域,模块之间依赖关系复杂,无法做到按需加载。

随着前端的发展,前端模块化到了第二阶段:立即执行函数,将每一个模块的成员都放在一个立即执行函数中,这样,立即执行函数就为模块提供了私有空间,对于需要暴露给全局的成员,可以通过挂载到全局对象的方式去实现,并且呢,这种方式可以通过参数的形式标明模块内部依赖的模块,这种方式解决了全局作用域污染、作用域冲突和模块依赖关系的问题。

其中,最为突出的例子就是JQuery源码,JQuery源码在封装的时候就使用了这种思想。经历过JQuery阶段的小伙伴也知道,在JQuery时期,多人合作开发时,或者手写轮子的时候,都是用立即执行函数来封装自己的代码。刚刚说的模块的三个大问题,立即执行函数解决了两个,剩下一个:按需加载。

接下来就到了第三阶段,前端形成了统一的模块规范,这一块的知识,我准备放到《从零学webpack》来细讲,里面会从模块化初始讲起,一直到手写webpack配置插件,目前已在拟稿。这样一来,前端、Node服务端的模块化知识树就完整了。

下面回到本节重点:

前面讲了前端中模块化,而模块,在Node.js中并不神秘,Node.js把一个.js文件就称为一个模块。只要这个文件是.js结尾的,它就是个Node.js模块,不管它内部的内容是JavaScript代码、还是JSON,还是编译过的C/C++扩展。

弄清楚了什么是模块,我们再来想一个问题:前端有模块化解决方案,而在Node中,一个js文件就是一个模块,那,Node是如何解决:私有作用域、依赖关系管理和按需加载的呢?

Node.js以模块为单位,划分各个功能,而每一个模块是一个js文件,Node.js实现了在每一个模块中定义的全局变量和函数只能在这个模块内部使用,只有通过exports导出的对象才能传递到外部,这样就解决了私有作用域的问题。这里有个小小的知识点,虽然在公众号系列文中会讲到,但这里简单提一下:exports是什么.

我们在用JavaScript写浏览器端代码时,js文件的引入无非两种方式:一是使用script标签引入;二是使用ES Modules标准,使用import进行引入。

而Node.js作为JavaScript在服务端的实现,它遵循一套叫common.js的规范。每个模块都有一个全局对象module,同时module对象中有一个对象exports,使用exports对象实现模块导出。exports是module.exports对象的别名,提供便捷的属性和方法设置。

require可以加载文件模块(.js、.code、.json)和nodejs核心模块,最终获取到的是module.exports对象。第一次加载的时候执行代码,第二次从缓存中获取module.exports对象,如果没有发现指定模块就报错not find module。

模块标识可以不包含后缀名,所以Node.js在文件定位时会依次补充.js,.json,.node后缀名,然后去进行文件定位,因为Node.js是单线程,所以文件定位时会发生堵塞,所以如果引入的模块后缀是.json或者.node,可以在引入的时候加上后缀,可以提高查找速度。

例如:foo.js模块中导出一个Foo函数

exports.Foo = function(){ return 'foo' }

这样,Foo就可以在外部访问到了。 在使用时,引入方式如下:

const foo = require('/foo.js'// 引入模块

foo.Foo() // 访问模块内的Foo函数

前面说到,Node.js的一个js文件就是一个模块,这种模块是可以自定义的。在Node.js内部,内置了很多模块,以供开发者使用,这里截图展示,后续或详细讲常用模块。


Node.js的核心模块如下:

Node.js一个强大的地方在于:超强的可扩展性。除了内置模块,自定义模块,还可以使用第三方模块安装对Node.js进行扩展。

前面说到模块化的三个问题:私有作用域、依赖关系管理和按需加载

一个模块就是一个js文件,文件外部只能通过文件的exports访问的机制解决了私有作用域问题。那么另外两个问题是如何解决的呢?

对于依赖关系的管理解决方案:npm。是不是很耳熟?没错,就是前端项目中常见的npm。它的作用就是将散落在各处的模块按照依赖关系重新组织起来。但npm的功能绝不仅仅于此,后续我们再详细展开讲。

对于按需加载,Node.js是在初始的时候一次性加载所有模块,对于使用过的模块就缓存起来,下次使用时再直接从缓存中读取,并且呢,由于Node.js运行在服务端,将文件从磁盘读取到内存中的速度是非常快的,因此,这个问题在Node中显得不那么突出。

小结:

1)Node.js中也有模块的概念,并且一个js文件就是一个模块;

2)每一个模块都有自己的作用域,在模块内部建立的全局变量和函数其他模块内无法访问,只有被导出的对象才能被其他模块访问。

3)模块的导出和引入遵循的是common.js模块规范,与前端模块规范是不同的。该规范解决了私有作用域和编码风格不统一问题。

4)模块类型按来源分可分为:内置模块、自定义模块和第三方扩展模块。

扩展知识:

Node.js是前端模块化的基础,webpack的模块化是建立在Node.js基础上的,因此,学webpack就会碰到Node.js,因此这也是为什么我要同时写Node系列和webpack系列的原因。

二、Node.js的交互式运行环境(REPL)

什么是REPL?

在Node.js中,为了方便开发者测试JavaScript代码,提供了一个名为REPL(Read Eval Print Loop:交互式解释器)的可交互运行环境,开发者可在该运行环境中输入任何表达式,然后按下回车键,REPL运行环境将显示该表达式的运行结果。

小结一下,REPL简单来说就是提供给开发者的调试工具,可以在REPL中测试代码。

细想一下,我们真的需要REPL吗?

我们真的需要REPL吗?

答案是肯定的。为什么?首先,我们前端写的代码要么运行在浏览器,要么运行在APP内部(如小程序),但是这些运行环境都会为我们提供开发者工具,可以去console,可以去审查元素,可Node.js是运行在服务端的,没有了开发者工具,于是REPL成为了我们的开发者工具,以供我们调试。所以,我们是真的需要REPL。

如何使用REPL

首先需要今日REPL环境,步骤如下:
对于windows系统操作如下:
1)windows键 + R,弹框命令框
2) 输入 cmd, 按下回车键,进入命令行窗口
3) 在命令行窗口输入:node, 按下回车键,出现命令提示符 “>” 就成功了.

REPL运用实例

Node 自带交互式解释器,可以执行以下任务:

读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中。

执行 - 执行输入的数据结构

打印 - 输出结果

循环 - 循环操作以上步骤直到用户两次按下 ctrl + c 按钮退出。

实例1--布尔值判断:

在刚刚进入的REPL环境中输入:3>2 回车 会输出布尔值: true

实例2--访问变量:

在REPL环境中输入:foo="bar" 回车 会输出字符串: 'bar'

然后访问变量值,在REPL中输入:foo 会输出字符串: 'bar'

REPL功能很强大,可以运行函数,可以定义并启动服务器,这里先不复杂演示。后续用到了就会了。

REPL中的快捷键

就像浏览器控制台有快捷键访问历史记录一样,REPL也提供了快捷键:_ 使用_来访问最近一次表达式,实例如下:

在REPL输入:

a=3 回车

再输入: _+=1 回车

会看到输出:4

REPL中的基础命令

Node.js提供了很多命令,以供开发者更好的使用REPL,这些命令均以一个点开始常见命令整理如下:

1)ctrl + c - 退出当前终端。

2)ctrl + c 按下两次 - 退出 Node REPL。

3)ctrl + d - 退出 Node REPL.

4)向上/向下 键 - 查看输入的历史命令

5)tab 键 - 列出当前命令

6).help - 列出使用命令

7).break - 退出多行表达式

8).clear - 退出多行表达式

9).save filename - 保存当前的 Node REPL 会话到指定文件

10).load filename - 载入当前 Node REPL 会话的文件内容。

小结:

1)REPL是Node.js提供的开发者调试环境,允许开发者在环境中调试代码;
2)REPL为了更好的体验,提供了常用的基础命令,注意基础命令前的点。

三、本节相关面试考点

1)Node.js是如何实现私有作用域的?
2)Node.js如何解决模块之间依赖问题?
3)常见Node.js模块有哪些?作用是什么?
4)什么是REPL?
5)说说你常用的REPL常用的命令

四、本节小结

本节主要是两块知识点:Node.js模块和REPL。每一小块都做了小结。可直接在每一块小结处进行查看。

五、文末

只写看得懂、读得懂、有价值的技术文章;

看完觉得有收获,点赞 + 关注,平台会为你推荐更多优质主题文章;

最后,我将更新连载《从零学webpack》,如果感兴趣,欢迎关注。