这篇文章主要介绍基于Node.js的前端面试题有哪些,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!Node.js 是一个开源与跨平台的 JavaScript 运行时环境。在浏览器外运行 V8 JavaScript 引擎(Google Chrome 的内核),利用事件驱动、非阻塞和异步输入输出模型等技术提高性能。我们可以理解为:Node.js 就是一个服务器端的、非阻塞式I/O的、事件驱动的JavaScript运行环境。理解Node,有几个基础的概念:非阻塞异步和事件驱动。非阻塞异步: Nodejs采用了非阻塞型I/O机制,在做I/O操作的时候不会造成任何的阻塞,当完成之后,以时间的形式通知执行操作。例如,在执行了访问数据库的代码之后,将立即转而执行其后面的代码,把数据库返回结果的处理代码放在回调函数中,从而提高了程序的执行效率。事件驱动: 事件驱动就是当进来一个新的请求的时,请求将会被压入一个事件队列中,然后通过一个循环来检测队列中的事件状态变化,如果检测到有状态变化的事件,那么就执行该事件对应的处理代码,一般都是回调函数。比如,读取一个文件,文件读取完毕后,就会触发对应的状态,然后通过对应的回调函数来进行处理。Node.js适合用于I/O密集型应用,值的是应用在运行极限时,CPU占用率仍然比较低,大部分时间是在做 I/O硬盘内存读写操作。缺点如下:不适合CPU密集型应用只支持单核CPU,不能充分利用CPU可靠性低,一旦代码某个环节崩溃,整个系统都崩溃对于第三点,常用的解决方案是,使用Nnigx反向代理,开多个进程绑定多个端口,或者开多个进程监听同一个端口。在熟悉了Nodejs的优点和弊端后,我们可以看到它适合以下的应用场景:善于I/O,不善于计算。因为Nodejs是一个单线程,如果计算(同步)太多,则会阻塞这个线程。大量并发的I/O,应用程序内部并不需要进行非常复杂的处理。与 WeSocket 配合,开发长连接的实时交互应用程序。具体的使用场景如下:用户表单收集系统、后台管理系统、实时交互系统、考试系统、联网软件、高并发量的web应用程序。基于web、canvas等多人联网游戏。基于web的多人实时聊天客户端、聊天室、图文直播。单页面浏览器应用程序。操作数据库、为前端和移动端提供基于json的API。在浏览器 JavaScript 中,window 是全局对象, 而 Nodejs 中的全局对象则是 global。在NodeJS里,是不可能在最外层定义一个变量,因为所有的用户代码都是当前模块的,只在当前模块里可用,但可以通过exports对象的使用将其传递给模块外部。所以,在NodeJS中,用var声明的变量并不属于全局的变量,只在当前模块生效。像上述的global全局对象则在全局作用域中,任何全局变量、函数、对象都是该对象的一个属性值。Node常见的全局对象有如下一些:Class:BufferprocessconsoleclearInterval、setIntervalclearTimeout、setTimeoutglobalClass:BufferClass:Buffer可以用来处理二进制以及非Unicode编码的数据,在Buffer类实例化中存储了原始数据。Buffer类似于一个整数数组,在V8堆原始存储空间给它分配了内存,一旦创建了Buffer实例,则无法改变大小。processprocess表示进程对象,提供有关当前过程的信息和控制。包括在执行node程序的过程中,如果需要传递参数,我们想要获取这个参数需要在process内置对象中。比如,我们有如下一个文件:当我们需要启动一个进程时,可以使用下面的命令:consoleconsole主要用来打印stdout和stderr,最常用的比如日志输出:console.log
。清空控制台的命令为:console.clear
。如果需要打印函数的调用栈,可以使用命令console.trace
。clearInterval、setIntervalsetInterval用于设置定时器,语法格式如下:clearInterval则用于清除定时器,callback每delay毫秒重复执行一次。clearTimeout、setTimeout和setInterval一样,setTimeout主要用于设置延时器,而clearTimeout则用于清除设置的延时器。globalglobal是一个全局命名空间对象,前面讲到的process、console、setTimeout等可以放到global中,例如:除了系统提供的全局对象外,还有一些只是在模块中出现,看起来像全局变量,如下所示:__dirname__filenameexportsmodulerequire__dirname__dirname主要用于获取当前文件所在的路径,不包括后面的文件名。比如,在/Users/mjr
中运行 node example.js
,打印结果如下:__filename__filename用于获取当前文件所在的路径和文件名称,包括后面的文件名称。比如,在/Users/mjr
中运行 node example.js
,打印的结果如下:exportsmodule.exports 用于导出一个指定模块所的内容,然后也可以使用require() 访问里面的内容。requirerequire主要用于引入模块、 JSON、或本地文件, 可以从 node_modules 引入模块。可以使用相对路径引入本地模块或JSON文件,路径会根据__dirname定义的目录名或当前工作目录进行处理。我们知道,进程计算机系统进行资源分配和调度的基本单位,是操作系统结构的基础,是线程的容器。当我们启动一个js文件,实际就是开启了一个服务进程,每个进程都拥有自己的独立空间地址、数据栈,像另一个进程无法访问当前进程的变量、数据结构,只有数据通信后,进程之间才可以数据共享。process 对象是Node的一个全局变量,提供了有关当前 Node.js 进程的信息并对其进行控制。
由于JavaScript是一个单线程语言,所以通过node xxx启动一个文件后,只有一条主线程。process的常见属性如下:process.env:环境变量,例如通过 `process.env.NODE_ENV 获取不同环境项目配置信息process.nextTick:这个在谈及 EventLoop 时经常为会提到process.pid:获取当前进程idprocess.ppid:当前进程对应的父进程process.cwd():获取当前进程工作目录process.platform:获取当前进程运行的操作系统平台process.uptime():当前进程已运行时间,例如:pm2 守护进程的 uptime 值进程事件: process.on(‘uncaughtException’,cb) 捕获异常信息、 process.on(‘exit’,cb)进程推出监听三个标准流: process.stdout 标准输出、 process.stdin 标准输入、 process.stderr 标准错误输出process.title:用于指定进程名称,有的时候需要给进程指定一个名称fs(filesystem)是文件系统模块,该模块提供本地文件的读写能力,基本上是POSIX文件操作命令的简单包装。可以说,所有与文件的操作都是通过fs核心模块来实现的。使用之前,需要先导入fs模块,如下:在计算机中,有关于文件的基础知识有如下一些:权限位 mode标识位 flag文件描述为 fd针对文件所有者、文件所属组、其他用户进行权限分配,其中类型又分成读、写和执行,具备权限位4、2、1,不具备权限为0。如在linux查看文件权限位的命令如下:在开头前十位中,d为文件夹,-为文件,后九位就代表当前用户、用户所属组和其他用户的权限位,按每三位划分,分别代表读(r)、写(w)和执行(x),- 代表没有当前位对应的权限。标识位代表着对文件的操作方式,如可读、可写、即可读又可写等等,如下表所示:操作系统会为每个打开的文件分配一个名为文件描述符的数值标识,文件操作使用这些文件描述符来识别与追踪每个特定的文件。Window 系统使用了一个不同但概念类似的机制来追踪资源,为方便用户,NodeJS 抽象了不同操作系统间的差异,为所有打开的文件分配了数值的文件描述符。在 NodeJS 中,每操作一个文件,文件描述符是递增的,文件描述符一般从 3 开始,因为前面有 0、1、2三个比较特殊的描述符,分别代表 process.stdin(标准输入)、process.stdout(标准输出)和 process.stderr(错误输出)。由于fs模块主要是操作文件的,所以常见的文件操作方法有如下一些:文件读取文件写入文件追加写入文件拷贝创建目录常用的文件读取有readFileSync和readFile两个方法。其中,readFileSync表示同步读取,如下:第一个参数为读取文件的路径或文件描述符。第二个参数为 options,默认值为 null,其中有 encoding(编码,默认为 null)和 flag(标识位,默认为 r),也可直接传入 encoding。readFile为异步读取方法, readFile 与 readFileSync 的前两个参数相同,最后一个参数为回调函数,函数内有两个参数 err(错误)和 data(数据),该方法没有返回值,回调函数在读取文件成功后执行。文件写入需要用到writeFileSync和writeFile两个方法。writeFileSync表示同步写入,如下所示。第一个参数为写入文件的路径或文件描述符。第二个参数为写入的数据,类型为 String 或 Buffer。第三个参数为 options,默认值为 null,其中有 encoding(编码,默认为 utf8)、 flag(标识位,默认为 w)和 mode(权限位,默认为 0o666),也可直接传入 encoding。writeFile表示异步写入,writeFile 与 writeFileSync 的前三个参数相同,最后一个参数为回调函数,函数内有一个参数 err(错误),回调函数在文件写入数据成功后执行。文件追加写入需要用到appendFileSync和appendFile两个方法。appendFileSync表示同步写入,如下。第一个参数为写入文件的路径或文件描述符。第二个参数为写入的数据,类型为 String 或 Buffer。第三个参数为 options,默认值为 null,其中有 encoding(编码,默认为 utf8)、 flag(标识位,默认为 a)和 mode(权限位,默认为 0o666),也可直接传入 encoding。appendFile表示异步追加写入,方法 appendFile 与 appendFileSync 的前三个参数相同,最后一个参数为回调函数,函数内有一个参数 err(错误),回调函数在文件追加写入数据成功后执行,如下所示。创建目录主要有mkdirSync和mkdir两个方法。其中,mkdirSync为同步创建,参数为一个目录的路径,没有返回值,在创建目录的过程中,必须保证传入的路径前面的文件目录都存在,否则会抛出异常。mkdir为异步创建,第二个参数为回调函数,如下所示。流(Stream)是一种数据传输的手段,是一种端到端信息交换的方式,而且是有顺序的,是逐块读取数据、处理内容,用于顺序读取输入或写入输出。在Node中,Stream分成三部分:source、dest、pipe。其中,在source和dest之间有一个连接的管道pipe,它的基本语法是source.pipe(dest),source和dest就是通过pipe连接,让数据从source流向dest,如下图所示:在Node,流可以分成四个种类:可写流:可写入数据的流,例如 fs.createWriteStream() 可以使用流将数据写入文件。可读流: 可读取数据的流,例如fs.createReadStream() 可以从文件读取内容。双工流: 既可读又可写的流,例如 net.Socket。转换流: 可以在数据写入和读取时修改或转换数据的流。例如,在文件压缩操作中,可以向文件写入压缩数据,并从文件中读取解压数据。在Node的HTTP服务器模块中,request 是可读流,response 是可写流。对于fs 模块来说,能同时处理可读和可写文件流可读流和可写流都是单向的,比较容易理解。而Socket是双向的,可读可写。在Node中,比较的常见的全双工通信就是websocket,因为发送方和接受方都是各自独立的方法,发送和接收都没有任何关系。基本的使用方法如下:流的常见使用场景有:get请求返回文件给客户端文件操作一些打包工具的底层操作流一个常见的使用场景就是网络请求,比如使用stream流返回文件,res也是一个stream对象,通过pipe管道将文件数据返回。文件的读取也是流操作,创建一个可读数据流readStream,一个可写数据流writeStream,通过pipe管道把数据流转过去。另外,一些打包工具,Webpack和Vite等都涉及很多流的操作。Node.js 在主线程里维护了一个事件队列,当接到请求后,就将该请求作为一个事件放入这个队列中,然后继续接收其他请求。当主线程空闲时(没有请求接入时),就开始循环事件队列,检查队列中是否有要处理的事件,这时要分两种情况:如果是非 I/O 任务,就亲自处理,并通过回调函数返回到上层调用;如果是 I/O 任务,就从 线程池 中拿出一个线程来处理这个事件,并指定回调函数,然后继续循环队列中的其他事件。当线程中的 I/O 任务完成以后,就执行指定的回调函数,并把这个完成的事件放到事件队列的尾部,等待事件循环,当主线程再次循环到该事件时,就直接处理并返回给上层调用。 这个过程就叫 事件循环 (Event Loop),其运行原理如下图所示。从左到右,从上到下,Node.js 被分为了四层,分别是 应用层、V8引擎层、Node API层 和 LIBUV层。应用层: 即 JavaScript 交互层,常见的就是 Node.js 的模块,比如 http,fsV8引擎层: 即利用 V8 引擎来解析JavaScript 语法,进而和下层 API 交互Node API层: 为上层模块提供系统调用,一般是由 C 语言来实现,和操作系统进行交互 。LIBUV层: 是跨平台的底层封装,实现了 事件循环、文件操作等,是 免费云主机、域名Node.js 实现异步的核心 。在Node中,我们所说的事件循环是基于libuv实现的,libuv是一个多平台的专注于异步IO的库。上图的EVENT_QUEUE 给人看起来只有一个队列,但事实上EventLoop存在6个阶段,每个阶段都有对应的一个先进先出的回调队列。事件循环一共可以分成了六个阶段,如下图所示。timers阶段:此阶段主要执行timer(setTimeout、setInterval)的回调。I/O事件回调阶段(I/O callbacks):执行延迟到下一个循环迭代的 I/O 回调,即上一轮循环中未被执行的一些I/O回调。闲置阶段(idle、prepare):仅系统内部使用。轮询阶段(poll):检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。检查阶段(check):setImmediate() 回调函数在这里执行关闭事件回调阶段(close callback):一些关闭的回调函数,如:socket.on(‘close’, …)每个阶段对应一个队列,当事件循环进入某个阶段时, 将会在该阶段内执行回调,直到队列耗尽或者回调的最大数量已执行, 那么将进入下一个处理阶段,如下图所示。前文说过,Node采用了事件驱动机制,而EventEmitter 就是Node实现事件驱动的基础。在EventEmitter的基础上,Node 几乎所有的模块都继承了这个类,这些模块拥有了自己的事件,可以绑定、触发监听器,实现了异步操作。Node.js 里面的许多对象都会分发事件,比如 fs.readStream 对象会在文件被打开的时候触发一个事件,这些产生事件的对象都是 events.EventEmitter 的实例,用于将一个或多个函数绑定到命名事件上。Node的events模块只提供了一个EventEmitter类,这个类实现了Node异步事件驱动架构的基本模式:观察者模式。在这种模式中,被观察者(主体)维护着一组其他对象派来(注册)的观察者,有新的对象对主体感兴趣就注册观察者,不感兴趣就取消订阅,主体有更新会依次通知观察者,使用方式如下。在上面的代码中,我们通过实例对象的on方法注册一个名为event的事件,通过emit方法触发该事件,而removeListener用于取消事件的监听。除了上面介绍的一些方法外,其他常用的方法还有如下一些:emitter.addListener/on(eventName, listener) :添加类型为 eventName 的监听事件到事件数组尾部。emitter.prependListener(eventName, listener):添加类型为 eventName 的监听事件到事件数组头部。emitter.emit(eventName[, …args]):触发类型为 eventName 的监听事件。emitter.removeListener/off(eventName, listener):移除类型为 eventName 的监听事件。emitter.once(eventName, listener):添加类型为 eventName 的监听事件,以后只能执行一次并删除。emitter.removeAllListeners([eventName]): 移除全部类型为 eventName 的监听事件。EventEmitter其实是一个构造函数,内部存在一个包含所有事件的对象。其中,events存放的监听事件的函数的结构如下:然后,开始一步步实现实例方法,首先是emit,第一个参数为事件的类型,第二个参数开始为触发事件函数的参数,实现如下:
这篇文章主要讲解了“如何理解算法时间复杂度”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何理解算法时间复杂度”吧!我们可以用下面的表达式来表示:通常主要有以下几种表达式来描述时间复杂度:O(1):常量时间O(…