NodeJS探索系列(一) -- Node.js 的基本概念

前言

此篇文章作为NodeJS探索系列的开篇文章,虽然前面有写“全局概况”,但是从这篇文章开始,就真正意义迈入了NodeJS探索之路了,本文将会从NodeJS基本概念入手,探索其起源、发展历史、基本架构、运行机制、优势特点、应用场景等,欢迎点赞,关注!

正文

什么是nodejs

Node.js的定义和概述

Node.js是一个基于JavaScript语言的开源、跨平台的运行时环境,它可以在服务器端运行JavaScript代码。

  • 最初由Ryan Dahl2009年创建,旨在解决Web应用程序中的I/O瓶颈问题。
  • Node.js的出现使得JavaScript开发人员可以在服务器端构建高性能和可伸缩的应用程序。

Node.js使用了事件驱动、非阻塞I/O模型,这使得它非常适合构建实时Web应用程序和APINode.js还拥有一个强大的包管理器npm,它可以让开发人员轻松地共享和重复使用代码。

总的来说,Node.js是一个非常强大和灵活的开发平台,可以用于构建各种类型的应用程序,包括Web应用程序、命令行工具、桌面应用程序等。Node.js的出现彻底改变了JavaScript在服务器端的地位,使得它成为一个全栈开发语言。

Node.js与JavaScript的关系

Node.jsJavaScript之间有着非常紧密的关系,因为Node.js是一个基于JavaScript语言的运行时环境。

  • 最初是为了在浏览器中提供动态交互而创建的。随着Web应用程序变得越来越复杂,开发人员开始在浏览器中编写大量的JavaScript代码,以实现各种交互和动态效果。

随着时间的推移,JavaScript的能力逐渐扩展,变得越来越强大。

  • Node.js就是在这个背景下出现的,它为JavaScript开发人员提供了在服务器端运行JavaScript的能力。这使得开发人员可以使用同样的语言和工具在服务器端和客户端构建应用程序。

Node.js使用了Google的V8 JavaScript引擎,这是一个非常快速和高效的引擎,最初是为了在Chrome浏览器中运行JavaScript而创建的。Node.jsV8引擎嵌入到自己的运行时环境中,使得JavaScript可以在服务器端运行。

Node.js的起源和发展历程

Node.js的起源可以追溯到2009年,当时Ryan Dahl创建了Node.js,旨在解决Web应用程序中的I/O瓶颈问题。

早期的JavaScript

JavaScript,又称为原始JavaScript或“早期的ECMAScript,是指1995年至2000年期间开发的JavaScript版本。

  • JavaScript最初是由Netscape公司的Brendan Eich开发的,最初被称为Mocha,随后更名为LiveScript
  • 1995年,NetscapeSun Microsystems合作,将LiveScript更名为JavaScript,并在Netscape Navigator 2.0中发布了第一个JavaScript版本。

早期的JavaScript与现代的JavaScript有很大的区别。早期的JavaScript主要用于实现简单的交互功能,如表单验证和简单的动画效果。它的语法和特性非常基础,没有现在的高级语法特性和API支持。但是,正是由于它的简单和易用性,早期的JavaScript成为了Web开发的重要组成部分,并且对Web应用程序的开发产生了重要的影响。

  • 在早期的JavaScript中,浏览器提供了基本的DOM操作和事件处理功能,这些功能被称为“DOM Level 0”。
  • JavaScript代码可以直接访问和修改文档中的元素,例如修改文本或样式。
  • 此外,早期的JavaScript还提供了一些基本的数据类型和语法结构,如变量、函数和控制流语句。
  • 与现代JavaScript不同的是,早期的JavaScript没有模块化支持、类和继承、Promise和异步函数等高级特性。

Google的V8引擎

GoogleV8引擎是一款高性能的JavaScript引擎,最初由Google开发用于Google Chrome浏览器。V8引擎的目标是提供高效的JavaScript解释器,从而提升Web应用程序的性能。与其他JavaScript引擎相比,V8引擎的特点是其快速的执行速度和低内存消耗。

  • V8引擎的开发始于2008年,它是使用C++编写的。V8引擎使用了许多高级的编译技术,如即时编译(JIT)和内联缓存,这些技术可以将JavaScript代码编译成本地机器码并且实现优化。这些优化使得V8引擎可以快速执行JavaScript代码,并且处理大量数据时不会消耗太多的内存。

  • V8引擎的开源性质和高性能使得它被广泛应用于Node.js和其他JavaScript开发场景中。Node.js是一个基于V8引擎的JavaScript运行时环境,它允许开发人员使用JavaScript语言编写服务器端应用程序。V8引擎的高性能是Node.js的关键之一,它使得Node.js应用程序可以在处理大量并发请求时仍然具有出色的响应速度。

总的来说,GoogleV8引擎是一款高性能的JavaScript引擎,它为现代JavaScript的发展提供了强有力的支持,同时也对Web应用程序的性能和发展产生了重要的影响。

Ryan Dahl创建的Node.js项目

Node.js是一个基于GoogleV8引擎开发的JavaScript运行时环境,它由Ryan Dahl2009年创建。

  • Ryan Dahl是一位软件开发人员,他认为JavaScript在浏览器中的表现非常出色,但是在服务器端的表现并不理想。因此,他创建了Node.js项目,旨在将JavaScript应用于服务器端编程。

Node.js的核心思想是基于事件驱动的非阻塞I/O模型,这意味着在处理I/O操作时,Node.js不会阻塞线程,而是将I/O操作的结果作为事件通知应用程序。

这种非阻塞I/O模型使得Node.js应用程序可以同时处理大量并发请求,具有出色的性能和可扩展性。

  • Ryan Dahl最初的想法是使用JavaScript语言编写高性能的网络服务器,以便在大规模并发请求的情况下提供出色的性能。

  • 在创建Node.js之前,Ryan DahlC++中实现了类似的服务器。但是,使用JavaScript可以提供更高的开发效率和更好的可读性,因此他决定使用JavaScript开发Node.js

随着时间的推移,Node.js逐渐成为一个非常流行的开发平台。它在很多领域都被广泛应用,如Web应用程序、网络服务器、命令行工具、物联网等等。Node.js的成功离不开Ryan Dahl的创新思想和出色的设计,同时也离不开广大开发者的积极参与和贡献。

Node.js的发展历程和版本更新

Ryan Dahl创建Node.js以来,它已经经历了多个版本的更新和改进。在Node.js的发展历程中,有几个关键节点:

  • 2009年,Node.js的第一个版本0.1.0发布。
  • 2010年,npmNode.js的包管理器)发布,使得Node.js社区更加活跃和繁荣。
  • 2011年,Node.js的版本0.4.0发布,这是Node.js的第一个稳定版本。这个版本在性能和可靠性方面都有很大的改进。
  • 2012年,Node.js的版本0.8.0发布,引入了Windows平台的支持,并且改进了HTTP模块的性能。
  • 2013年,Node.js的版本0.10.0发布,引入了Streams2,这个版本是Node.js的一个重要的版本更新,它的改进包括性能、稳定性、API的完善以及内置模块的改进。
  • 2015年,Node.js的版本4.0.0发布,这个版本是Node.js的一个里程碑式的版本更新,它将io.jsNode.js的代码库合并,使得Node.js的开发和维护更加统一和高效。同时,这个版本还引入了ES6的支持,这使得Node.js可以使用最新的JavaScript语言特性。
  • 2017年,Node.js的版本8.0.0发布,这个版本引入了async/await语法的支持,这使得异步编程变得更加易于理解和编写。
  • 2018年,Node.js的版本10.0.0发布,这个版本在性能、可靠性和安全性方面都有很大的改进,同时引入了HTTP/2协议的支持。
  • 2020年,Node.js的版本14.0.0发布,这个版本在性能、稳定性和安全性方面都有很大的改进,同时引入了Diagnostic Reports的支持,这使得开发者可以更方便地调试和分析Node.js应用程序的问题。

除了版本更新和改进,Node.js的发展历程中还涌现出了很多优秀的第三方模块和工具,这些模块和工具丰富了Node.js的生态系统,为开发者提供了更多的选择和便利。

Node.js 的架构和运行机制

Node.js的基本架构

image.png

Node.js基本架构如上图所示,natives modules主要是上层的api方法,就是单纯的JavaScript写的脚本,但是JavaScript语言不能直接与硬件设备通信,所以需要借助第三方来实现,这个第三方也就是c/c++ bindings,他们实现了libuv等库,来帮助Node.js实现高性能的web服务。

  1. V8引擎: Node.js使用了Google的V8引擎,V8引擎是一款快速、高效、开源的JavaScript引擎,它由丹麦软件公司V8 Technology团队开发,主要用于Google Chrome浏览器中的JavaScript解释器,Node.js采用了这个引擎,使得Node.js可以像浏览器一样解析JavaScript代码。
  2. Libuv库: Node.js使用了Libuv库作为其事件循环和I/O操作的实现,这个库提供了一个跨平台的抽象层,使得Node.js可以在WindowsLinuxMacOS等操作系统上运行,并提供了异步I/O、定时器、线程池、进程管理等功能。
  3. 事件驱动机制: Node.js使用了事件驱动的机制,当一个异步操作完成时,Node.js会触发一个事件,然后执行对应的回调函数。这种机制使得Node.js可以处理大量并发请求,提高了系统的吞吐量和响应速度。
  4. 模块系统: Node.js使用了CommonJS规范来组织模块,每个文件都被视为一个独立的模块。在Node.js中,可以通过require函数来引入其他模块,并通过exports对象来向外部暴露接口。

通过这些组件的协同工作,Node.js可以高效地处理大量并发请求,提供了一个高性能、可扩展的服务器端框架,也逐渐成为了前端工程师构建后端服务的首选技术之一。

简单的架构图:

image.png

Node.js的运行时架构

Node.js的运行时架构是其能够高效处理I/O操作的重要原因之一。Node.js的架构主要包括进程架构线程架构事件驱动架构网络架构

  • Node.js进程架构由一个主进程和多个工作进程组成。主进程负责接受客户端请求,并将请求分发到工作进程。工作进程负责处理请求并返回响应。

  • Node.js提供了cluster模块,可以方便地使用多个工作进程来提高应用程序的性能和可靠性。开发人员可以在不同的CPU核心和服务器之间分配工作进程,从而最大化利用计算资源。

  • Node.js线程架构libuv库和线程池组成。

    • libuv库是一个跨平台的异步I/O库,它提供了一组事件驱动的API,可以实现非阻塞I/O操作。
    • 线程池则用于管理Node.jsI/O操作,并利用多线程的方式来提高应用程序的性能。
    • 开发人员可以根据应用程序的需求调整线程池的大小,以达到最优性能。

image.png

  • Node.js事件驱动架构是其核心特性之一。事件驱动架构包括事件循环事件队列回调函数

    • 事件循环是Node.js的核心,它持续运行,等待事件发生并处理事件。
    • 事件队列用于存储事件和回调函数。回调函数用于处理事件并返回结果。
    • Node.js的事件驱动架构实现了非阻塞I/O操作和异步编程,使得Node.js在处理大量I/O操作时能够高效地运行。
  • Node.js网络架构包括TCP/IP协议、HTTP协议和WebSocket协议。

    • Node.js可以通过这些协议实现基于网络的通信,包括客户端和服务端的通信以及跨服务器的通信。
    • 开发人员可以使用Node.js构建高性能、可扩展的网络应用程序,例如Web应用程序、API服务器和实时应用程序等。

Node.js的回调机制

Node.js的回调机制是其异步编程的核心,Node.js的回调机制也是基于事件驱动的架构实现的。由于Node.js主要用于编写服务器端应用程序,这些应用程序需要处理大量的I/O操作,而这些I/O操作需要花费大量的时间才能完成。

在传统的同步编程模型下,这些操作将会阻塞应用程序的执行,降低程序的响应速度和性能。而在Node.js中,采用了异步编程模型,通过回调函数来处理I/O操作,使得应用程序可以在等待I/O操作完成的同时,继续执行其他的操作,从而提高了程序的响应速度和性能。

  • Node.js执行I/O操作时,它会将该操作添加到事件循环的事件队列中,并继续执行其他的操作。当I/O操作完成时,Node.js将从事件队列中取出该操作,并将其对应的回调函数加入到回调队列中,等待下一次事件循环执行。当事件循环执行到该回调函数时,它会被调用,完成对I/O操作的处理。

  • 虽然Node.js的回调机制提供了非常高效的异步编程模型,但它也带来了一些挑战。

    • 一个常见的问题是回调地狱,即嵌套的回调函数使得代码难以阅读和维护。为了解决这个问题,可以使用一些技巧,如将回调函数定义为命名函数,使用Promiseasync/await来编写异步代码。

此外,错误处理也是使用回调机制时需要考虑的问题。当一个异步操作发生错误时,我们需要通过错误回调函数来处理这个错误。同时,我们也可以使用try/catch语句来捕获回调函数中的错误,从而实现更加健壮的错误处理。

Node.js的事件循环机制

Node.js的事件循环机制 Node.js的事件循环机制是一种基于事件驱动的机制,由事件队列事件循环触发器组成。

Node.js遇到一个异步操作时,它会将该操作添加到事件队列中,然后继续执行下一个操作。一旦异步操作完成,Node.js会将该操作的回调函数添加到事件队列中,并通过事件循环机制来执行这些回调函数。

  • 事件循环机制允许Node.js进行非阻塞I/O操作,这意味着在进行I/O操作时,Node.js不会一直阻塞进程,而是将I/O操作交给操作系统进行处理,并继续执行下一个操作。

  • 一旦I/O操作完成,操作系统会通知Node.js,并将结果存储在事件队列中,Node.js在事件循环机制中执行相应的回调函数来处理结果。

  • 在事件循环机制中,有两个重要的函数:process.nextTicksetImmediate

    • process.nextTick函数将一个回调函数添加到下一个事件循环的开始处,这意味着在当前事件循环结束之前,该回调函数将被执行。
    • setImmediate函数将一个回调函数添加到下一个事件循环的末尾处,这意味着在当前事件循环结束后,该回调函数将被执行。这两个函数的作用在一些情况下是不同的,需要根据具体情况进行选择。

Node.js的事件循环机制是实现异步操作的重要机制之一,允许Node.js进行非阻塞I/O操作,从而提高了应用程序的性能和响应能力。同时,理解事件循环机制的运作原理,可以帮助我们编写高效的异步代码。

Node.js的模块加载机制

Node.js的模块加载机制是Node.js的一个核心功能,它允许开发者编写可重用的代码,方便模块之间的依赖关系和代码复用。下面将介绍Node.js的模块加载机制的三个方面。

模块缓存

Node.js中,所有的模块都会被缓存到内存中。当第一次加载模块时,Node.js会把模块缓存到内存中。当后续需要再次加载同一模块时,Node.js会直接从缓存中获取模块,而不是重新加载模块。这样可以提高模块加载的速度,避免重复的模块加载操作。

模块路径解析

Node.js的模块路径解析规则非常灵活,支持多种路径形式,

  • 包括绝对路径、
  • 相对路径、
  • 模块名称等。
  • 在加载模块时,Node.js会根据给定的路径或模块名称,解析出对应的文件路径,并加载对应的模块。这个过程是按照一定的规则进行的,首先是从当前模块的目录开始搜索,然后是逐级往上级目录搜索,直到找到对应的模块。
模块包装器

Node.js的模块包装器是一个非常重要的机制,它可以将模块代码封装在一个函数内部,并注入一些常用的变量和方法,以便在模块内部使用。这个函数的签名如下:

(function(exports, require, module, __filename, __dirname) {
  // 模块代码
});
复制代码

其中,

  • exports是一个空对象,用于导出模块的接口;
  • require是一个方法,用于加载其他模块;
  • module是一个对象,用于表示当前模块的信息;
  • __filename__dirname分别表示当前模块的文件路径和目录路径。这个包装器函数的作用是隔离模块内部的变量和方法,避免模块之间的冲突。

Node.js中,有两种模块规范:CommonJS模块规范和ES6模块规范。其中:

  • CommonJS模块规范是Node.js默认的模块规范。
  • ES6模块规范是ECMAScript 6标准引入的模块规范。
  • 这两种模块规范在语法和用法上有些不同,但都是基于模块化思想设计的,可以方便地编写可重用的模块。

模块加载机制是Node.js的一个核心功能,它提供了灵活的模块路径解析、模块缓存和模块包装器等机制,方便开发者编写可重用的模块

Node.js的垃圾回收机制

Node.js的垃圾回收机制,包括V8引擎的垃圾回收策略和Node.js的内存管理 Node.js内存泄漏内存溢出问题,以及如何避免它们 Node.jsheapdumpprofiler工具,以及如何使用它们进行性能分析和优化

  • Node.js的垃圾回收机制是指V8引擎的垃圾回收策略Node.js的内存管理方式。

    • V8引擎采用了分代垃圾回收机制,将内存分为新生代老生代两个区域。
    • 新生代用于存放短时间内创建的对象,采用Scavenge算法进行垃圾回收;
    • 老生代用于存放长时间存在的对象,采用Mark-SweepMark-Compact算法进行垃圾回收。
    • Node.js通过调用V8引擎的API来与垃圾回收机制交互。
  • Node.js的内存管理方式包括使用垃圾回收机制回收内存、使用缓存池避免频繁分配释放内存、使用Buffer类来管理内存和使用内存限制来避免内存溢出

    • Node.js中,内存泄漏是指由于代码中存在对于不再需要的对象的引用而导致的内存无法回收,而内存溢出是指程序需要的内存超过了可用内存的情况。

    • 内存泄漏可以通过避免创建不必要的对象和及时释放对象的引用来解决,内存溢出可以通过增加可用内存或者优化代码来解决。

    • Node.js提供了一些工具来进行性能分析和优化,包括heapdumpprofiler

      • heapdump可以生成堆转储文件,用于分析内存泄漏和内存溢出问题;
      • profiler可以生成CPU性能分析报告,用于分析CPU性能瓶颈。这些工具可以帮助开发者识别代码中的性能问题并进行优化。

Node.js的应用场景和用途

  • Web服务器和API服务:
    • Node.js非常适合构建高性能的Web服务器和API服务。由于Node.js采用异步I/O模型,可以同时处理大量的并发请求。因此,Node.js可以轻松地处理高并发、低延迟的Web应用程序。务。
  • 实时通信和数据处理:
    • 由于Node.js可以快速、轻松地处理大量的并发请求,因此它非常适合实时通信和数据处理应用程序。一些知名的实时通信应用程序,此外,Node.js还可以用于处理实时数据流、实时日志分析等。
  • 命令行工具和脚本编写:
    • Node.js提供了丰富的模块和工具,可以用于编写各种命令行工具和脚本。Node.js的模块加载机制和包管理工具(npm)使得开发人员可以轻松地编写、发布和共享自己的命令行工具和脚本。
  • 网络爬虫和数据采集:
    • Node.js可以轻松地处理网络爬虫和数据采集应用程序。由于Node.js可以快速地处理大量的并发请求,因此它非常适合用于爬取和处理大量的Web数据。一些知名的爬虫工具,如Crawley等都采用Node.js构建。
  • 云计算和容器化:
    • 由于Node.js采用轻量级的、高度可扩展的架构,因此它非常适合用于云计算和容器化场景。
    • Node.js可以快速地响应云计算环境中的动态需求,可以轻松地部署在各种云计算平台和容器平台中。
    • 此外,由于Node.js采用JavaScript语言,可以使得开发人员在云计算和容器化环境中进行更加灵活、高效的开发和部署。

Node.js的特性和优势

Node.js的特性和优势包括:

  1. 事件驱动和异步I/ONode.js采用事件驱动和异步I/O的编程模型,可以处理高并发的网络应用,同时提高系统的吞吐量响应性能
  2. 单线程非阻塞I/ONode.js采用单线程的设计,通过异步I/O实现非阻塞I/O操作,避免了多线程带来的资源竞争死锁线程切换的开销,提高了系统的稳定性和可维护性。
  3. 快速、轻量级和可扩展:Node.js采用基于V8引擎的高性能JavaScript解释器,具有快速启动和执行速度,同时内存占用较小。Node.js可以通过多进程和集群等方式实现水平扩展,适合处理高并发和大规模的网络应用。
  4. 模块化设计和npm生态系统:Node.js采用模块化的设计,可以通过模块化编程提高代码的可读性可维护性。同时,Node.js具有丰富的npm生态系统,可以方便地使用第三方模块和工具来实现各种功能和应用场景。

Node.js的环境和安装

  • Node.js是一个基于Chrome V8引擎的JavaScript运行环境,可以在服务器端运行JavaScript代码。
  • Node.js在设计之初就考虑了高效处理I/O操作和构建可扩展网络应用程序的需求,具有事件驱动、非阻塞I/O和单线程等特点,因此被广泛应用于Web服务器、API服务、实时通信、命令行工具、数据采集、云计算和容器化等领域。

要安装Node.jsnpm,可以按照以下步骤进行:

  1. 打开Node.js官网

image.png

  1. 双击安装包,按照安装向导的提示完成安装。在安装过程中可以选择是否安装npm包管理器,建议选择安装。

  2. 安装完成后,打开终端(Windows平台可以使用PowerShell或命令提示符,macOS平台可以使用终端),输入以下命令检查Node.jsnpm是否安装成功:

    node -v // 16.19.1
    npm -v // 8.11.0
    复制代码

    如果输出了版本号,则表示安装成功。

现在就可以使用Node.jsnpm来开发和运行JavaScript应用程序了。下一章 >>> NodeJS探索系列(二) -- Node.js 的核心模块和 API

猜你喜欢

转载自juejin.im/post/7218117377053098039
今日推荐