Node(3),3年前端开发工程师面试经验分享,2024年最新前端面试点梳理

而Node应用程序考虑到了这一点,在项目的package.json将依赖的第三方软件包分为了两类:开发依赖 和 项目依赖

开发依赖的第三方包 会被保存在 dev-dependencies中

项目依赖的第三方包 会被保存在 dependencies中

而我们只需要在下载第三方包的时候,指定一些参数即可实现分类:

npm i --save packagename  即下载时指定–save,npm就自动将下载的包添加到package.json的dependencies中,即项目依赖

简写形式是:npm i -S packagename

最简形式是:npm i packagename

目前 npm i packagename 默认会将下载的第三方包当成“项目依赖”

npm i --save-dev packagename 即下载时指定 --save-dev,npm就自动将下载的包添加到package.json的dev-dependencies中,即开发依赖

简写形式时:npm i -D packagename

本地下载和全局下载

我们使用 npm i packagename时,默认将第三方软件包下载到 项目根目录下的node_modules文件夹中。

此时该第三方包只在当前项目中起作用,我们称这种下载叫本地下载。

如果我们本地有多个Node项目,如果每个Node项目都依赖一些相同的第三方包,则为每一个Node项目都下载一遍,会造成内存浪费。此时就需要将多个项目都需要的公共包,下载到全局下,即所有项目都可以访问到的地方,避免内存浪费。此时下载叫做全局下载。

全局下载只需要添加一个-g参数

npm i -g packagename

如果想知道全局下载的软件包的存放路径,则可以使用命令

npm root -g

需要注意的是:

全局下载的Node软件包如果是命令行工具,则其命令会被自动被注册为系统变量,在任意目录下都可用。

而本地下载的命令行工具Node软件包,由于其命令不会被自动注册为系统变量,所以无法在任意目录下直接使用其命令,只能通过npx命令来调用

例如,只在项目中安装nodemon(保证全局下没有nodemon,使用npm list -g查看)

此时nodemon命令不会被自动注册为系统变量,所以无法在任意目录下使用nodemon命令。需要借助npx命令借调nodemon命令

为什么下载到全局的node软件包的命令会自动注册为系统变量呢?

1、安装好Node.js后,会自动将node_global目录加入系统变量Path中

2、我们下载全局软件包时,会自动存放在node_global下

3、如果下载的全局软件包是命令行工具,则会将cmd命令文件放到node_global目录下,这就保证了如nodemon.cmd这样的命令可以在任意目录下使用

那么我们是否可以将所有开发依赖的软件包都从项目本地,转移到全局呢?

一般来说不会这样搞,将项目本地的node包单分出一个开发依赖,其实就是为了团队之间的协同,如果大家都自己搞自己的,可能对于新人不太友好。

软件包的语义化版本

在package.json中有三个地方存在软件包的版本号信息

1、package.json的version配置

2、package.json的dependencies配置

3、package.json的dev-dependcies配置

其中

version 版本号,指的是当前项目的版本号,由我们控制版本的升级

而dependencies和dev-dependencies中的版本号,是当前项目依赖的第三方Node包的版本号,不由我们控制。

通过上图,我们可以发现版本号由三部分组成,即 major.minor.patch

解释升级时机
major主要版本号已有需求发生改变,并且改变了已有功能
minor次要版本号已有功能不变,开发了新需求的功能
patch补丁版本号修复了已有功能的bug,需求不变

分析一下,patch和minor升级是向低版本兼容的,而major升级不向低版本兼容。

所以,我们项目依赖一个第三方包后,一般都需要锁定major版本,防止更新包后,新版本的第三方包代码不向下兼容。

此时就需要给版本号加入语义化。如上图中 package.json的 dependencies 依赖的第三方包的版本号前面有个^,这就是语义化符号。

^major.minor.patch表示锁定major版本,只能升级minor和patch版本
~major.minor.patch表示锁定major和minor版本,只能升级patch版本
major.minor.patch表示锁定major,minor,patch版本,不让升级

下载指定版本号的第三方软件包

前面我们下载第三方软件包时,都是只指定了包名,没有指定具体版本号,此时下载就是第三方包的最新版本,即

npm i packagename

下载的时packagename包的最新版本。

但是有时候最新版本并不稳定,所以我们一般下载比较稳定的老版本,此时就需要指定版本号

npm i packagename@major.minor.patch

但是我们可能并不知道对应第三方包有哪些版本,此时需要借助命令查询第三方包历史版本列表

npm view packagename viersions

更新第三方软件包

更新项目依赖的第三方软件包时,需要注意:

1、当前依赖的第三方软件包是否过时了

2、当前依赖的第三方软件包是否被锁定

通过命令 npm outdate 就可以查看当前项目依赖的第三方包是否存在过时的,如果有会全部列出来

我们发现 eslint和nodemon都过时了,且nodemon最新版本是2.0.15,但是只能升级到1.19.4。而eslint最新版本是8.6.0,但是只能升级到1.10.3,即当前版本,即eslint无法再升级了。

再看这两个包的版本锁定情况

发现都被锁死在了major版本,即major版本不允许升级,minor、patch版本可以升级。而这也是导致eslint和nodemon无法升级到最新版本,只能升级到major为1的最高版本。

更新版本时,使用如下命令可以更新所有过时的第三方包到锁定的最高版本,而不是最新版本

npm update

而 npm outdate 和 npm update 命令加上-g参数,就可以查看和更新全局下的过时Node包

删除第三方软件包

通常使用命令

npm uninstall packagename

来删除项目本地指定的第三方软件包,

或者使用其简写命令

npm un packagename

加上 -g 参数就可以删除全局下的第三方软件包

查看第三方软件包元数据

一个第三方软件包,包含了各种元数据,比如包名,最近更新时间,历史版本列表,作者,

这里我们可以使用命令

npm view packagename

来查看指定软件包的所有元数据信息

也可以加入元数据配置项参数,来查看特定元数据信息,如命令

npm view packagename views

获取依赖的所有第三方软件包,及其依赖包列表

比如,获取项目本地下载的所有第三方软件包使用命令

npm list

加上 -g 参数表示,获取全局下载的所有第三方软件包

npm list -g

但是npm list目前只能查看被依赖的第三方软件包本身,无法查看第三方软件包依赖的包,这里提供了 --depth num,来指定查看的层级,默认npm list相当于 npm list --depth 0

npm list -g --depth 1

项目直接依赖包和项目间接依赖包

我们项目下载了一个第三方软件包,比如mongoose,那么它就成为了我们项目的一个依赖包。

但是mongoose本身也是一个项目,它也有很多依赖包,也需要被下载。

即:我们下载mongoose包,则会自动下载mongoose包的依赖包。其中mongoose就是当前项目直接依赖包,mongoose的依赖包就是当前项目间接依赖包。它们都是当前项目必须的。

上面查看,当前项目的依赖包只有一个mongoose,但是实际上,当前项目根目录node_moudules文件夹中下载的第三方包包含:

可以发现 “项目直接依赖包” 和 “项目间接依赖包” 都会被存放在 项目的node_modules文件夹下

这就会造成一个问题:

比如项目下载 A包,A包依赖于 Z@1.0.0包,项目又下载了B包,B包依赖于Z@2.0.0包

那么按照上面下载规则,A,B,Z包都会放在项目的node_modules文件夹下,那么Z包是否会发生冲突覆盖?

答案:不会,因为 项目间接依赖包 下载遵循如下规则

项目直接依赖包 会被直接下载到 项目的node_modules下,

而项目间接依赖包 下载前,会检查当前项目node_modules下是否已有该包,

没有的话,则将项目间接依赖包 下载到 项目的node_modules下,

有的话,则将 项目间接依赖包 下载到 项目直接依赖包 的node_modules下

这样就避免了项目间接依赖包之间发生冲突。

那么为什么不直接将 项目间接依赖包 直接下载到对应的  项目直接依赖包的node_modules文件夹下呢?

因为,项目直接依赖包 之间 存在大量重复的 依赖包,如果将项目间接依赖包 直接 存放再 项目直接依赖包下,可能会造成大量的重复包下载,浪费内存。

npm常用命令总结

增 

不指定版本号新增

(最新版本)

npm i pakcage
指定版本号新增npm i package@major.minor.patch
项目新增
开发依赖-D
项目依赖-S 或 无
全局新增-g
npm un package
项目删除
全局删除-g
查询过期依赖包npm outdate 

更新过期依赖包

(到锁定最高版本)

npm update 

项目更新
全局更新-g

查询项目所有依赖包npm list --depth
查询项目依赖包元数据npm view package 元数据配置
项目更新
全局更新-g

项目node_modules目录是否需要提交git

答案:不需要,项目node_modules存放的都是依赖第三方软件包代码,体积很大,如果上传到git仓库的话,很浪费时间。

所以我们一般在项目根目录的.gitignore文件中设置忽略 node_modules/ 的提交

那么项目发布到npmjs.com后,其他人下载后,如何获取项目的node_modules依赖包。

当我们项目下载依赖包后,依赖包的版本号和下载地址都保存在了项目根目录下的package-lock.json中,当其他人下载项目后,使用npm i package会自动根据软件包的package-lock.json去下载对应的依赖包。

项目开发过程中,拉取git仓库代码到本地,也是没有node_modules的,此时只需要 npm install 即可自动根据package-lock.json快速下载依赖包

发布软件包和更新软件包版本号以及撤回软件包

当我们项目开发完成后,需要共享到npmjs.com时,即表示发布软件包,发布软件包需要先登录npmjs.com,使用命令

npm login

登录到npmjs.com后,再使用如下命令来发布项目到npmjs.com的个人空间中

npm publish

当项目发生版本更新后,可以使用如下命令来更新项目的版本号,即package.json中的version

npm version major  // 更新major版本

npm version minor // 更新minor版本

npm version patch // 更新patch版本

当项目发布错误,或者版本存在重大Bug时,需要撤回已发布的版本时,如果在24h内,则可以撤回,24h后不可撤回,撤回成功后,24h后才能重新发布,且重新发布的包的名称和版本号都要修改

npm unpublish package --force

更改npm镜像地址

在国内更改npm镜像地址是一个有必要的操作,因为默认按照的npm的地址是npmjs.com,这个网址是国外网站,下载上传速度很慢。而npm的地址是npm的配置信息之一。我们可以查看npm的所有配置,使用如下命令

npm config list

可以使用如下命令获取特定的npm配置信息

npm config get registry

可以使用如下命令设置特定的npm配置信息

npm config set registry https://registry.npm.taobao.org/

可以使用如下命令删除特定的npm配置信息

npm config delete registry

可以使用如下命令修改特定npm配置信息

npm config edit registry https://registry.npmjs.com/

而npm的配置信息都保存在一个文件中,我们可以通过如下命令获取到对应文件的地址

npm config get user

Node.js的异步编程(异步编程方案+Event Loop)


异步原理

Node.js 是基于 Chorme v8引擎开发的JS运行环境,所以Node.js生来支持ECMAScript语法。

而我们知道JavaScript代码是单线程运行的,为了提高JS代码执行效率,不让JS代码被某些不依赖于CPU的操作所阻塞,如I/O操作,出现了很多事件驱动的,基于回调函数的Node APIs。

“事件驱动,基于回调” 的 意思是:当 … 时候,JS单线程再来执行… 回调。

这就是JS的异步编程原理。

异步任务串行的实现

而随着JS的发展,逐渐出现了三种渐进式的异步编程方案:

1、基于回调的异步编程

2、基于Promise的异步编程

3、基于生成器,迭代器,co函数,Promise的异步编程

其实如果2,3的提出,都是为了处理一个问题:如何优雅地实现异步任务地串行执行

1 可以实现异步任务地串行执行,但是很不优雅,外界人送称号“回调地狱”

2 通过链式来实现异步任务串行执行,解决了回调地狱,但是依旧不够优雅

3 是异步任务串行最终解决方案,它实现异步任务可以像同步代码一样顺序执行

但是基于生成器,迭代器,co函数,promise的异步编程 在代码编写上过于繁琐,特别是co函数完全是模板化代码,所以ES6(ES7),提供了async await语法糖,将繁琐的,套路化的代码都封装在了底层,只需要使用语法糖就可以完成生成器,迭代器,co函数的异步编程。

具体详见:

随笔-对Promise的深入理解_qfc_128220的博客-CSDN博客

随笔-Iterator、Generator、co_qfc_128220的博客-CSDN博客

随笔-Promise的缺点以及async await语法糖_qfc_128220的博客-CSDN博客

Node Event Loop

为什么需要事件循环?

我们知道Node采用了Chorme v8引擎来作为JS代码的执行环境,Chrome v8引擎只提供了一个线程来执行JS代码,这个线程就是JS主线程。

而JS主线程为了防止程序运行过程中被某段代码阻塞,就将JS代码做了分类,分为同步代码和异步代码。

而JS异步代码的特定就是带有回调函数的函数,即异步API,如定时器setTimeout、文件读取fs.readFile。

这些异步代码都有一个特点:就是耗费时间,且操作一般不占用CPU,比如文件读取的I/O操作依赖的是硬件工作(磁盘和内存间的数据传递)

所以异步代码执行过程中,对CPU资源造成一种浪费。

而为了高效利用CPU,JS主线程会将异步代码交给其他线程处理,比如Node中就是libuv模块的线程池中的线程处理。

JS主线程全力处理同步代码,尽量榨干CPU资源,而线程池线程负责开启异步任务,并时刻监听异步结果,当异步任务有了结果,线程池线程就会将结果和回调函数放入一个任务队列中,之后自己回归线程池。

而JS主线程在处理完同步代码后,就会去处理异步代码的回调函数。那么回调函数在哪呢?

答:就在任务队列中。

JS主线程需要取出任务队列中的回调函数,然后执行。

那么JS主线程如何判断任务队列不会再有回调函数了呢?

答案是无法判断,因为异步任务返回结果的时间是不确定的,所以回调函数进入任务队列的时间也是不确定的,所以JS主线程需要不断地去任务队列询问是否还有回调函数,有的话就取出执行,没有的话,就过一会再来询问。

JS主线程 反复去 任务队列 取回调函数 的过程就是事件循环。

而事件循环的目的是清空任务队列,而任务队列是用于存储已经得到异步任务结果的回调函数,而回调函数为什么要存储,就是因为JS主线程不想等待异步任务结果,而阻塞后续代码的执行。

所以事件循环说到底还是为异步编程。

Node的事件循环有六个阶段,每个阶段都有一个任务队列,用于存储特定的异步API的回调函数。

这六个阶段分别是:

timer阶段:它的任务队列用于存储setTimeout,setInterval的回调函数

~pending callbacks阶段:用于存储TCP error相关的回调函数(系统级别,一般开发不涉及)~

idle,prepare阶段:系统内部使用(一般开发不涉及)

poll阶段:它的任务队列用于存储I/O操作相关的回调函数

check阶段:它的任务队列用于存储setImmediate的回调函数

~close callbacks阶段:它的任务队列一般用于存储关闭操作的回调函数,如soket.on(‘close’, function),一般开发中用的不多~

另外在事件循环启动后,JS主线程去每个阶段的任务队列取回调是有顺序的,即事件循环六个阶段是有优先级的。

timer > pending callbacks > idle,prepare > poll > check > close callback

其中timer优先级最高,close callback优先级最低。

即JS主线程会先去timer队列中回调函数清空,再去后续阶段的队列中清空回调。

但是实际上,timer队列的执行优先级有时会落后于check队列。原因是JS主线程会在poll队列处考虑停留。以及setTimeout,setInterval的不及时性。

因为Node作为服务器端,处理网络I/O,数据库I/O,以及文件I/O较多,而每个I/O操作的结果都需要一个线程实时轮询,即一旦I/O操作有了结果,线程就会实时轮询到,并及时放到poll阶段的队列中。

所以Node会在事件循环过程中,在poll阶段会考虑是否需要停留一段时间,这样就能及时处理I/O操作的回调函数。

而是否停留的前提是:

1、timer队列中没有回调

2、check队列中没有回调

只要timer或check队列中存在回调,Node就不会在poll阶段停留,而是直接进入下一个check阶段。

需要注意的是,在事件循环六个阶段中,只有poll阶段享有停留特权,其他阶段没有。

setTimeout和setInterval虽然是定时器,但是它们无法实现准确计时,比如

setTimeout(function,0)

这里虽然指定暂停0ms,但是实际上,无论是浏览器还是Node都有最低的暂停时间限制,一般是1ms。

即setTimeout的回调函数至少要等1ms才能进入timer队列。

而1ms在底层是可以做很多事情的,比如事件循环在1ms内,检查到timer队列没有回调,就直接进入了下一阶段了,比如1ms内,setImmediate指定的回调函数进入了事件循环的check队列,此时就会发生check队列回调优先执行的现象。

另外,如果1ms内,setImmediate回调函数没有及时加入到事件循环的check队列,则依旧是timer回调优先于check回调执行。

setTimeout(()=>{

console.log(1);

})

setImmediate(()=>{

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
img

结尾

正式学习前端大概 3 年多了,很早就想整理这个书单了,因为常常会有朋友问,前端该如何学习,学习前端该看哪些书,我就讲讲我学习的道路中看的一些书,虽然整理的书不多,但是每一本都是那种看一本就秒不绝口的感觉。

以下大部分是我看过的,或者说身边的人推荐的书籍,每一本我都有些相关的推荐语,如果你有看到更好的书欢迎推荐呀。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

check回调执行。

setTimeout(()=>{

console.log(1);

})

setImmediate(()=>{

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-wOMKKaoL-1710828455616)]
[外链图片转存中…(img-LdLF430F-1710828455616)]
[外链图片转存中…(img-L21JWDbn-1710828455617)]
[外链图片转存中…(img-SNMaRoxY-1710828455617)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
[外链图片转存中…(img-7IZfpTWz-1710828455618)]

结尾

正式学习前端大概 3 年多了,很早就想整理这个书单了,因为常常会有朋友问,前端该如何学习,学习前端该看哪些书,我就讲讲我学习的道路中看的一些书,虽然整理的书不多,但是每一本都是那种看一本就秒不绝口的感觉。

以下大部分是我看过的,或者说身边的人推荐的书籍,每一本我都有些相关的推荐语,如果你有看到更好的书欢迎推荐呀。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端学习书籍导图-1

  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值