前段时间面试了一个大厂,被问了一些犄角旮旯的面试题,整场下来,一脸懵逼,决定痛定思痛,把这些题总结下来,不断更新。每天一遍,每天进步。。。
1、npm cnpm yarn pnpm的区别,各自的优势是什么? 你项目中用的什么?
首先说一下Node.js,简单的说 Node.js 就是运行在服务端的 JavaScript。node通过更改连接到服务器的方式,可以处理高并发任务。
npm:
nodejs的包管理器,用于node插件管理(包括安装、卸载、管理依赖等)
npm安装速度慢的一个重要原因:npm远程服务器在国外,必须首先遍历所有的项目依赖关系,然后再决定如何生成扁平化的node_modules结构。不过,npm有本地缓存,它保存了已经下载的每个 版本的压缩包,缓存可以减少安装时间。
yarn
像npm一样,yarn使用本地缓存。与npm不同的是,yarn无需互联网连接就能安装本地缓存的依赖项,它提供了离线模式。
Yarn Workspaces(工作区)是Yarn提供的monorepo的依赖管理机制,从Yarn 1.0开始默认支持,用于在代码仓库的根目录下管理多个package的依赖。
Workspace 能更好的统一管理有多个项目的仓库,既可在每个项目下使用独立的 package.json 管理依赖,又可便利的享受一条 yarn命令安装或者升级所有依赖等。更重要的是可以使多个项目共享同一个 node_modules目录,提升开发效率和降低磁盘空间占用。
pnpm
利用硬链接和符号链接来避免复制本地缓存源文件,继承了yarn的所有优点,包括离线模式和确定性安装.内部基于内容寻址的文件系统存储磁盘上所有的文件:1.不会重复安装同一个包。2.即使包的不同版本,pnpm也会极大程度的复用之前版本的代码。
对比其优劣势
npm
node_modules扁平化结构
支持workspace,大部分开发人员用npm也能很好的打包
逐行安装(速度较慢)
问题:幻影依赖,依赖分身
yarn
node_modules扁平化解构
支持workspace,monorepo
yarn主要为了解决语义版本控制而导致npm安装不确定的问题,由yarn.lock文件,不过npm也出了package-lock文件
并行安装(速度较快)
问题:幻影依赖,依赖分身
pnpm
pnpm采用内存寻址存储的方法,通过软硬链接引用依赖,实现node_modules非扁平化结构,
支持workspace,monorepo,速度快,磁盘占用空间小(速度最快)
通过内容寻址存储解决了幻影依赖,依赖分身的问题,安全性高。
pnpm 的安装速度在大多数场景都比 npm 和 yarn 快 2 倍,节省的磁盘空间也更多。
问题:
因为依赖源文件是安装在 store 中,调试依赖或 patch-package 给依赖打补丁也不太方便,可能会影响其他项目。
由于 pnpm 创建的 node_modules 依赖软链接,因此在不支持软链接的环境中,无法使用 pnpm,比如 Electron 应用。
2、devDependencies、dependencies,peerDependencies的区别
npm install mypack & npm install mypack -save 将 mypack安装到node_modules目录下,并且将该包名放入package.json下的dependencies中
npm install mypack -save-dev 将 mypack安装到node_modules目录下,并且将该包名放入package.json下的devdependencies中
npm install mypack -g 将 mypack安装你 node 的安装目录 及 全局目录。该包名不放入package.json中,可以直接在命令行里使用。如npm
dependencies 字段中指定的是项目运行时需要的依赖包,也就是生产环境下需要的依赖。这些依赖将会被安装在生产环境中,并被打包进最终的发布版本中。一般来说,这些依赖是指与项目密切相关的库和框架,比如 express、lodash、axios 等。当我们使用 npm install 安装项目时,dependencies 中指定的依赖包也会被自动安装。使用场景:适用于项目的主要功能模块、框架以及必要的第三方库。这些依赖项会随着应用程序一起部署,并在生产环境中运行。
devDependencies 字段中指定的是开发环境下所需的依赖包。这些依赖通常是开发人员在编写和测试代码时使用的,而不会被打包到最终的发布版本中。一般来说,这些依赖包是用于构建、测试、调试等用途,比如 Babel、Webpack、Mocha 等。当我们使用 npm install 安装项目时,devDependencies 中指定的依赖包不会被安装。若需要安装 devDependencies 中的依赖,需要使用 npm install --dev。使用场景:适用于开发过程中的辅助工具、测试框架、构建工具、代码质量检查工具等。这些依赖项不会影响应用程序的实际运行,只在开发环境中使用。
peerDependencies 字段中指定的是项目所依赖的其他包的版本号范围。这些依赖会要求安装方在安装项目时手动安装所需要的版本。peerDependencies 通常用于告知用户项目运行时所依赖的某些库或框架的版本,而且这些库或框架已经被全局安装或者被安装在项目外面。peerDependencies 可以确保安装的库版本与项目所依赖的版本一致,从而减少版本兼容性问题。
使用场景:
开发库或模块依赖特定版本的外部库:如果你正在开发一个库或模块,它依赖于外部库的特定版本来实现某些功能,但你不希望将这些外部库包含在你的库中,那么你可以在你的 package.json 中指定这些外部库为 peerDependencies。
版本兼容性:有时,不同的库可能依赖于同一个外部库的不同版本,这可能导致版本冲突。通过在你的库中使用 peerDependencies,你可以确保使用者在安装你的库时会同时满足这些依赖项的版本要求,从而避免版本冲突。
库的可插拔性:如果你的库需要与其他库或模块协同工作,而这些库或模块可能在项目中以插件或扩展的形式存在,你可以使用 peerDependencies 来确保插件与你的库兼容。
提供建议性的依赖项:有时候,你可能希望为使用者提供一些建议性的依赖项,虽然这些依赖项不是强制性的。在这种情况下,你可以将这些依赖项列为 peerDependencies,使用者可以根据自己的需求来决定是否安装。
使用 peerDependencies 时需要注意以下几点:
使用 peerDependencies 不会自动安装依赖项,它只是告诉使用者需要安装这些外部依赖项,并确保版本兼容性。
使用 peerDependencies 时,使用者需要手动安装符合要求的外部依赖项,以便与你的库或模块正常工作。
为了避免冲突和混淆,建议在文档中清楚地说明使用者需要安装的外部依赖项以及版本要求。
3、生产环境,开发环境,测试环境等不同的环境的面试题
开发环境:
开发环境时程序猿们专门用于开发的服务器,配置可以比较随意,为了开发调试方便,一般打开全部错误报告和测试工具,是最基础的环境。开发环境的分支,一般是feature分支。
测试环境
一般是克隆一份生产环境的配置,一个程序在测试环境工作不正常,那么肯定不能把它发布到生产服务器上,是开发环境到生产环境的过度环境。测试环境的分支一般是develop分支,部署到公司私有的服务器或者局域网服务器上,主要用于测试是否存在bug,一般会不让用户和其他人看到,并且测试环境会尽量与生产环境相似。
生产环境
生产环境是指正式提供对外服务的,一般会关掉错误报告,打开错误日志,是最重要的环境。部署分支一般为master分支。
上述环境也可以说是系统开发的三个阶段:开发->测试->上线,其中生产环境也就是通产说的真实的环境,最后交给用户的环境。
如何在Vue中丝滑的切换环境呢?
- 在根目录建立.env系列文件
- .env.development(开发环境,用于serve启动的项目)
- .env.production(生产环境,用于build打包的项目)
- .env.test(测试环境)
//在文件中可以配置下面的变量 后面写请求服务器的地址 VUE_APP_BASE_API = '需要请求API'
1 //对应的 "scripts": 2 "dev": "vue-cli-service serve", 对应开发环境 3 "test": "vue-cli-service serve --mode test", 对应测试环境 4 "build": "vue-cli-service build", 对应生产环境 5 "build:test": "vue-cli-service build --mode test",对应测试环境
在vue中使用的话可以直接使用 process.env.VUE_APP_BASE_API 进行取值,也就是我们预先配置好的请求地址
1 const service = axios.create({ 2 baseURL: process.env.VUE_APP_BASE_API, 3 timeout: 10000, 4 })
生产环境中如何调试,如何查找报错对应的位置?
(1)服务器通过搜索生产环境的日志
- .env.test(测试环境)
在方法开头和结尾或者在catch中抓取写日志log。这种方式适合小范围集群或者单体服务,绝对不适用大量集群的情况,大量集群日志会分散到多个服务器,
具体调用哪台服务器根本就不知道,所以成本很高。
(2)SoueceMap
虽然map文件提供了便利,但是在生产环境,为了安全,是建议关闭SourceMap的,因为通过.map文件和编译后代码可以很容易反编译出项目的源码,这样就相当于泄露了项目的代码。
生产环境的代码,经过压缩、编译,很不利于debug。由于生产环境没有配置SourceMap,所以代码报错时,或是通过Fundebug,Sentry等工具搜集到的报错信息,得到报错代码的行和列都是编译后的代码,这样很不易于定位问题。针对这个问题,需要准备一份生产环境代码的map文件,为了方便,可以在项目的package.json增加debug命令用来生成map文件。这条命令除了开启sourcemap,其他的具体webpack配置和生产环境配置相同。
1 "scripts": {
2 "start": "vue-cli-service serve --mode dev",
3 "stage": "vue-cli-service build --mode staging",
4 "online": "vue-cli-service build",
5 "debug": "vue-cli-service build --mode debug"
6 },
有了map文件,通过SourceMap提供的API就可以定位到源码的位置。下面是实现的核心代码。
1 // Get file content
2 const sourceMap = require('source-map');
3 const readFile = function (filePath) {
4 return new Promise(function (resolve, reject) {
5 fs.readFile(filePath, {encoding:'utf-8'}, function(error, data) {
6 if (error) {
7 console.log(error)
8 return reject(error);
9 }
10 resolve(JSON.parse(data));
11 });
12 });
13 };
14
15 // Find the source location
16 async function searchSource(filePath, line, column) {
17 const rawSourceMap = await readFile(filePath)
18 const consumer = await new sourceMap.SourceMapConsumer(rawSourceMap);
19 const res = consumer.originalPositionFor({
20 'line' : line,
21 'column' : column
22 });
23 consumer.destroy()
24 return res
25 }
最重要的就是使用SourceMap提供的 originalPositionFor API。 SourceMapConsumer.prototype.originalPositionFor(generatedPosition)
originalPositionFor API的参数为一个包含line和column属性的对象
line 编译生成代码的行号,从1开始
column 编译生成代码的列号,从0开始
这个方法会返回一个具有以下属性的对象
1 { source: 'webpack:///src/pages/common/403.vue?c891', // 源代码文件的位置,如果无法获取,返回null。
2 line: 4, // 源代码的行号,从1开始,如果无法获取,返回null。
3 column: 24, // 源代码的列号,从0开始,如果无法获取,返回null。
4 name: 'get' // 源代码的标识,如果无法获取,返回null。
5 }
4、库的版本问题
(1)版本号前面的“^”和“~”代表什么:
“^”它表示尽量使用最新版本,但保证不产生兼容问题。
~符号,表示版本号只能改变最末尾那段(如果是 ~x.y 末尾就是 y,如果是 ~x.y.z 末尾就是 z),比如这种情况:~1.2 等于 >=1.2.0; <2.0.0
因为0的特殊性所以如果你要指定 0 开头的库那一定要注意:~0.1 这种写法是很危险的,因为 ~0.1 等于 >=0.1.0; <1.0.0,可能出现无法向下兼容的情况,比较保险的写法还是:^0.1(等于 >=0.1.0; <0.2.0)
(2)改变版本号怎么做
npm version
命令用于更改版本号的信息,并执行commit
操作;该命令执行后, package.json
里的 version
会自动更新。
一般来说,当版本有较大改动时,变更第一位, 执行命令:npm version major -m "description"
, 例如1.0.0
-> 2.0.0
;
当前包变动较小时,可变更第二位,执行命令:npm version minor -m "description"
, 例如: 1.0.0
-> 1.1.0
;
当前包只是修复了些问题时,可变更第三位,执行命令:npm version patch -m "description"
, 例如: 1.0.0
-> 1.0.1
;
(3)pack.json的配置有哪些?
- 描述配置 主要是项目的基本信息,包括名称,版本,描述,仓库,作者等,部分会展示在 npm 官网上。
- 文件配置 包括项目所包含的文件,以及入口等信息。
- 脚本配置
命令行方式启动预设置的脚本
- 依赖配置 项目依赖其他包引用的相关信息。
- 发布配置 主要是和项目发布相关的配置。
- 系统配置 和项目关联的系统配置,比如 node 版本或操作系统兼容性之类。这些要求只会起到提示警告的作用,即使用户的环境不符合要求,也不影响安装依赖包。
- 第三方配置 一些第三方库或应用在进行某些内部处理时会依赖这些字段,使用它们时需要安装对应的第三方库。
5、有关提升项目性能的方案
1、减少 HTTP 请求次数:可以使用雪碧图、图片压缩等方法,减少静态资源的 HTTP 请求次数。
2、浏览器缓存:在合适的情况下,使用浏览器缓存可以显著减少请求时间,提高页面加载速度。
通过设置 http 头信息,可用的参数如下:其中 Expires 和 Cache-control 属于强缓存;Last-Modified/If-Modified-Since 和 Etag/If-None-Match 属于协商缓存。
* Expires
* Cache-control
* Last-Modified/If-Modified-Since
* Etag/If-None-Match
3、使用 CDN 加速:使用 CDN 加速可以将静态资源分发到多个节点,减少请求延迟,提高页面加载速度。
4、延迟加载组件:对于一些比较耗时的组件,可以使用懒加载的方式,等到用户需要使用的时候再加载,避免在页面加载时一次性请求过多资源导致页面卡顿。
5、使用 Webpack 进行打包和压缩:Webpack 可以将多个 JS、CSS 文件打包成一个文件,减少 HTTP 请求次数;同时还可以进行代码压缩,减少文件大小,提高页面加载速度。
6、使用 Web Workers:对于一些计算密集型任务,可以使用 Web Workers 将任务分发到多个线程中,提高运算效率,避免页面卡顿。
7、优化 JavaScript 代码:优化 JavaScript 代码可以减少页面的运行时间,提高页面性能。一些常见的优化方案包括:避免使用全局变量,减少 DOM 操作,避免不必要的循环等。
8、使用响应式布局:使用响应式布局可以使页面适应不同大小的设备屏幕,提高用户体验,避免出现滚动条等不必要的元素。
9、使用 CSS3 动画:使用 CSS3 动画可以减少对 JavaScript 的依赖,提高动画效果的性能。
10、使用服务端渲染:服务端渲染可以将页面的渲染工作在服务端完成,减少客户端的渲染时间,提高页面性能。