一、webpack 和 vite
1.webpack打包发生了啥
整体我们将会从上边5个方面来分析Webpack打包流程:
- 初始化参数阶段。
这一步会从我们配置的webpack.config.js中读取到对应的配置参数和shell命令中传入的参数进行合并得到最终打包配置参数。 - 开始编译准备阶段
这一步我们会通过调用webpack()方法返回一个compiler方法,创建我们的compiler对象,并且注册各个Webpack Plugin。找到配置入口中的entry代码,调用compiler.run()方法进行编译。 - 模块编译阶段
从入口模块进行分析,调用匹配文件的loaders对文件进行处理。同时分析模块依赖的模块,递归进行模块编译工作。 - 完成编译阶段
在递归完成后,每个引用模块通过loaders处理完成同时得到模块之间的相互依赖关系。 - 输出文件阶段
整理模块依赖关系,同时将处理后的文件输出到ouput的磁盘目录中。
webpack怎么处理组件依赖,例子:一个项目用到abc三个组件,然后a组件又有自己的依赖,那a组件的依赖会被打包到哪里,会被打包吗,怎么知道a依赖了什么
Webpack在处理组件依赖时,会根据你的配置和项目结构来确定依赖关系,并将这些依赖打包到最终的输出文件中。Webpack会按照以下步骤处理这些依赖:
- 识别依赖: 在Webpack的配置文件中,你需要指定入口文件或者入口文件数组。Webpack会从这些入口文件开始分析整个项目的依赖关系。
- 分析依赖树: 当Webpack遇到 import、require 等语句时,会根据这些语句的路径去寻找对应的模块或文件,并构建一个依赖树。
- 递归查找: 如果 a 组件引入了其他模块或组件,Webpack会递归地查找这些依赖,直到找到所有相关的依赖项。
- 打包处理: 最后,Webpack会将所有依赖项打包成一个或多个输出文件,通常是一个 bundle.js 文件。这个文件包含了项目中所有组件和它们的依赖关系,包括 a 组件所依赖的其他模块或组件。
关于如何知道 a 组件依赖了哪些内容,你可以通过查看 a 组件的代码或者其导入语句来了解。例如,如果 a 组件中有类似于 import xyz from 'xyz'; 的语句,那么就表示 a 组件依赖了 xyz 模块或组件。你也可以借助Webpack的一些插件或工具来生成依赖分析报告,以更清晰地了解项目的依赖关系。
Webpack的loader和plugin
loader的作用:
webpack中的loader是一个函数,主要为了实现源码的转换,所以loader函数会以源码作为参数,比如将ES6转换为ES5,将less转换为css,将css转换为js,以便能嵌入到html文件
常见的loader:
file-loader: 把文件输出到一个文件夹中,在代码中通过相对URL去引用输出文件
url-loader:和file-loader类似,但是能在文件很小的情况下以base64的方式把文件内容注入到代码中去
source-map-loader:加载额外的Source Map文件,以便断点调试
image-loader:加载并压缩图片文件
babel-loader:把ES6转换为ES5
css-loader:加载CSS,支持模块化、压缩、文件导入等特性
style-loader:把CSS代码注入到js中,通过DOM操作去加载CSS
eslint-loader:通过ESlint检查js代码
plugin的作用:
plugin是一个类,类中有一个apply()方法,主要用于Plugin的安装,可以在其中监听一些来自编译器发出的事件,在合适的时机做出一些事情。
常见的Plugin:
html-webpack-plugin:可以复制一个有结构的html文件,并自动引入打包输出的所有资源(JS/CSS)
clean-webpack-plugin:重新打包自动清空dist目录
mini-css-extract-plugin:提取js中的css成单独文件
optimize-css-assets-webpack-plugin:压缩css
uglifyjs-webpack-plugin:压缩js
commons-chunk-plugin:提取公共代码
define-plugin:定义环境变量
原文链接:blog.csdn.net/waouya/arti…
2. vite打包发生了啥?以及各阶段和webpack对比
以下是 Vite 详细打包过程的逐步解析,与 Webpack 的流程类似但更高效(基于 ES Modules 和 Rollup)。为了清晰对比,我将按照 阶段划分 并标注与 Webpack 的差异:
1. 初始化参数阶段
-
输入:
-
合并配置:
vite.config.js或vite.config.ts中的用户配置。- CLI 命令参数(如
vite build --mode production)。
-
默认配置:Vite 内置的 Rollup 默认选项(如
output.dir=dist)。
-
-
输出:
- 生成最终的
resolvedConfig对象(可通过console.log(config)调试)。
- 生成最终的
-
对比 Webpack:
- Vite 直接使用 Rollup 的配置结构(如
rollupOptions),而 Webpack 有自己的配置体系。
- Vite 直接使用 Rollup 的配置结构(如
2. 依赖预构建阶段(开发模式独有)
-
目的:
- 将第三方依赖(如
lodash)从 CommonJS 转换为 ESM 格式,加速开发时 HMR。
- 将第三方依赖(如
-
流程:
-
扫描依赖:
- 通过
esbuild分析项目import语句,找到node_modules中的依赖。
- 通过
-
合并打包:
- 使用
esbuild将依赖打包成单个 ESM 文件(如.vite/deps/react.js)。
- 使用
-
缓存:
- 结果存储在
node_modules/.vite/deps,通过optimizeDeps配置控制。
- 结果存储在
-
-
对比 Webpack:
- Webpack 无此阶段,依赖直接在运行时处理(可能导致冷启动慢)。
3. 生产构建阶段(vite build)
阶段 3.1:创建 Rollup 打包上下文
-
调用 Rollup:
- Vite 内部调用
rollup.rollup(),传入整合后的配置。
- Vite 内部调用
-
插件初始化:
- 加载 Vite 插件(如
@vitejs/plugin-vue)和 Rollup 插件(如terser)。
- 加载 Vite 插件(如
阶段 3.2:模块图构建
-
入口分析:
- 从
config.build.rollupOptions.input(默认index.html)找到入口文件(如src/main.js)。
- 从
-
递归解析:
-
使用
esbuild转译 TS/JSX,然后通过 Rollup 分析依赖关系:graph LR A[main.js] --> B[App.vue] B --> C[router.js] C --> D[home.js]
-
-
静态资源处理:
- 图片、CSS 等通过
file-loader类似逻辑处理(输出到assets/)。
- 图片、CSS 等通过
阶段 3.3:代码转换与优化
-
ESBuild 转译:
- 所有 JavaScript/TypeScript 文件通过
esbuild转译为 ES5/ES6。
- 所有 JavaScript/TypeScript 文件通过
-
CSS 处理:
- 提取为独立文件(除非
css.inline开启)。 - 使用
postcss处理前缀、压缩。
- 提取为独立文件(除非
-
Tree Shaking:
- Rollup 静态分析移除未使用的代码(如未导出的函数)。
阶段 3.4:分块(Code Splitting)
-
自动分块:
- 动态导入(
import('./module.js'))自动拆分为独立 chunk。
- 动态导入(
-
手动分块:
- 通过
rollupOptions.output.manualChunks配置(如单独打包lodash)。
- 通过
阶段 3.5:生成产物
-
调用 Rollup 的
bundle.generate():- 生成内存中的代码块(未写入磁盘)。
-
文件写入:
-
将 JS、CSS、资源文件写入
dist/目录,结构如下:dist/ ├── assets/ │ ├── index.abc123.js │ ├── style.def456.css │ └── logo.ghi789.png ├── chunk-vendors.js └── index.html
-
-
Hash 命名:
- 文件名包含哈希(如
index.abc123.js)避免缓存问题。
- 文件名包含哈希(如
4. 插件钩子执行顺序
Vite 在打包过程中依次触发插件钩子(类似 Webpack 的 Tapable):
configResolved→ 2.buildStart→ 3.transform(文件转译)→ 4.renderChunk(代码优化)→ 5.generateBundle→ 6.writeBundle(写入磁盘)。
与 Webpack 的关键差异
| 阶段 | Vite | Webpack |
|---|---|---|
| 依赖处理 | 预构建为 ESM,开发时直接浏览器加载 | 运行时分析,打包到 bundle |
| 打包工具 | Rollup + ESBuild | 自实现编译器 + Babel |
| 速度 | 更快(ESBuild 原生 Go 语言) | 较慢(JS 实现的 loader/plugin) |
| HMR | 原生 ESM 支持,毫秒级更新 | 通过 WebSocket 和 runtime 注入 |
2. 用户的缓存是怎么做的?
Last-Modified组整体流程如下:
- 服务器通过 Last-Modified 字段告知客户端,资源最后一次被修改的时间
- 浏览器将这个值和内容一起记录在缓存数据库中
- 下一次请求相同资源的时候,浏览器从自己的缓存中找出”不确定是否过期的”缓存。因此在请求头中将上次的 Last-Modified 的值写入到请求头的 If-Modified-since 字段
- 服务器会将 If-Modified-since 的值与 If-Modified 字段进行对比。如果相等,则表示未修改,响应 304;反之,表示修改响应 200 状态码并返回数据
Etag组整体流程如下:
浏览器在发起请求时,服务器在响应头中返回请求资源的唯一标识。在下一次请求时,会将上一次返回的 Etag 值赋值给 If-None-match 并添加在响应头中。服务器将浏览器传来的 if-no-matched 跟自己的本地的资源的 Etag 做对比,如果匹配,则返回 304 通知浏览器读取本地缓存,否则会返回 200 和更新后的资源一样。
4.如何避免黑客盗取正常用户的token从而正常登录进去
- 使用HTTPS协议:使用HTTPS协议可以确保通信过程中的数据加密,在传输过程中可以防止被窃听和篡改。
- 使用单次token:每次用户进行操作时,都生成一个新的token,这样即使攻击者获取了一个合法的token,也只能用于一次操作,增加了破解难度
9.HTTP的请求报文结构
JS事件轮询宏任务和微任务
JavaScript的事件循环(Event Loop)机制是异步编程的核心,它处理宏任务(Macrotask)和微任务(Microtask)。理解这两个概念对于理解JavaScript的异步执行至关重要。
宏任务(Macrotask):
包括:setTimeout、setInterval、I/O、UI渲染、事件处理(如点击事件)等。
宏任务会在事件循环的每次迭代开始时执行,一个宏任务执行完毕后才会执行下一个宏任务。
微任务(Microtask):
包括:Promise的回调(.then(), .catch(), .finally())、process.nextTick(Node.js环境)、MutationObserver等。
微任务在当前宏任务执行的末尾(即宏任务执行栈为空后)立即执行,而且在下一次宏任务开始之前。
在一个宏任务执行过程中,每次执行栈清空后,都会检查并执行所有可用的微任务。
事件循环的工作流程大致如下:
执行全局代码(初始化宏任务)。
执行当前宏任务中的所有代码,包括函数调用、定时器等。
当宏任务执行完毕后,检查微任务队列,执行所有的微任务。
渲染UI(如果需要)。
开始执行下一个宏任务,重复以上步骤。
console.log('start');
setTimeout(() => {
console.log('timeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise');
});
console.log('end');
start
end
promise
timeout
这是因为console.log('start')和console.log('end')是同步代码,先执行。setTimeout是宏任务,会被放到任务队列中等待。Promise.resolve().then是微任务,会在当前宏任务结束时立即执行。因此,输出顺序是先同步代码,然后微任务,最后宏任务。
为什么上边的例子里,同步任务执行完毕后,先执行的微任务,再执行的宏任务?
在JavaScript的事件循环机制中,同步任务执行完毕后,会先执行微任务,然后再开始下一个宏任务,这是出于效率和响应性的考虑。这是因为微任务通常用于处理一些快速的、内部的回调,比如Promise的回调,它们是立即执行的,不需要等待其他任何东西。
当一个宏任务(比如一个脚本或事件回调)执行完毕后,JavaScript引擎会检查微任务队列(Task Queue)并执行所有在该宏任务执行期间产生的微任务。这是因为微任务是异步处理的一部分,它们通常用来处理同步代码执行后的副作用,比如更新DOM、处理Promise链等,这些都需要尽快完成以保持应用的响应性。
例如,如果你有一个宏任务,它创建了一个Promise并立即 resolve 或 reject 它,那么在宏任务执行的末尾,Promise的回调(微任务)会被添加到微任务队列。一旦宏任务结束,这些微任务就会被立即执行,然后再开始下一个宏任务。
简而言之,这种设计允许JavaScript在不影响用户界面的情况下快速处理异步回调,提高应用的性能和用户体验。
10.TCP的三次握手
11.HTTP报文的生命周期(类似访问一个URL经历的过程)
12.用过哪些设计模式
13.为什么https比http安全
-
HTTPS 通过混合加密算法解决 HTTP 传输数据容易被窃听的问题,此过程需要协商
会话密钥。 -
HTTPS 通过摘要算法解决 HTTP 传输数据容易被篡改的问题,此过程需要协商
鉴别密钥。 -
HTTPS 通过数字证书解决 HTTP 协议中身份容易被伪造的问题,此过程需要客户端验证服务器的
证书。
14. vite和webpack的区别
Vite 和 Webpack 都是现代前端开发中常用的构建工具,但它们在设计理念和实现方式上存在一些区别:
-
构建速度:
- Vite 利用浏览器原生支持的 ES 模块特性,实现了快速的冷启动和热更新,构建速度通常比 Webpack 快。
- Webpack 需要将所有模块打包成一个或多个文件,每次修改都需要重新构建整个项目,导致构建速度随着项目规模增大而变慢。
-
开发模式:
- Vite 在开发阶段不需要将所有代码打包成一个 bundle,而是以原生 ES 模块的方式直接在浏览器中加载和运行文件,实现更快的冷启动和热更新。
- Webpack 在开发阶段需要将所有的代码打包成一个或多个 bundle,然后通过热加载或手动刷新浏览器来查看更新效果。
-
生产构建:
- Webpack 在生产环境下会将所有代码打包成一个或多个 bundle,进行优化、压缩和代码拆分等操作。
- Vite 在生产环境下保持了开发时的原生 ES 模块导入方式,不会将所有代码打包成一个 bundle,而是保留第三方依赖的单独引用,以实现更快的加载速度。
-
插件生态系统:
- Webpack 拥有广泛的插件生态系统,能够满足不同的构建需求,并与其他工具和框架良好集成。
- Vite 的插件生态系统相对较小,但依然可以满足常见的构建需求,并且在逐渐增长。
-
配置复杂度:
- Webpack 的配置比较复杂,需要花费较长的时间去学习和调试。
- Vite 的配置相对更简单,因为它无需进行大量的配置,只需指定一些基本的选项就可以开始开发。
-
旧版浏览器支持:
- Webpack 高度可配置,适合需要与现代和旧版浏览器兼容的项目。
- Vite 针对现代开发环境进行了优化,专注于支持 ES 模块的浏览器,对于旧版浏览器支持有限。
-
HMR(热模块替换) :
- Vite 的 HMR 实现了更细粒度的模块更新,可以实现局部模块的更新而无需刷新整个页面。
- Webpack 的 HMR 替换模块后需要刷新页面使 HMR 生效。
-
内存使用:
- Vite 的内存效率更高,在不同规模的项目中使用内存更少。
-
可扩展性:
- Vite 在项目增长时性能下降最小,而 Webpack 在大型项目中性能显著下降。
总结来说,Vite 以其快速的构建速度和轻量级的配置,适合现代化的框架和快速的开发周期,而 Webpack 以其强大的功能和灵活性,特别适用于需要细粒度控制构建输出、缓存和资产管理的大型、复杂项目。选择使用哪个工具应根据项目的具体需求和优先级来决定。
15.前端预防攻击的方式
前端预防攻击的方式主要包括以下几个方面:
-
防止XSS攻击:
- 利用模板引擎的HTML转义功能,避免使用内联事件和拼接HTML,保持警惕并增加攻击难度。
- 对用户输入的数据进行严格验证和过滤,确保不包含恶意脚本。
- 使用内容安全策略(CSP)限制浏览器加载外部资源的方式,减少XSS攻击的风险。
- 使用HttpOnly标记防止JavaScript访问Cookie,减少XSS攻击对Cookie的窃取。
- 定期更新和维护第三方库,减少XSS攻击的风险。
- 安全编程实践,避免直接拼接用户输入到HTML标签中,使用安全的方法动态生成DOM元素。
-
防止CSRF攻击:
- 使用CSRF Token,在每个用户请求中包含一个随机生成的CSRF Token,并在后端验证该Token的有效性。
-
防止点击劫持(Clickjacking) :
- 通过设置X-Frame-Options头,防止网页被嵌入到iframe中。
- 使用JavaScript来防止网页被嵌套到iframe中。
-
安全的跨域通信:
- 通过CORS(跨域资源共享)设置合适的CORS头,授权不同域的客户端进行跨域请求。
-
输入验证和数据加密:
- 对用户输入进行验证,确保输入的数据是合法的,防止注入攻击等。
- 使用HTTPS来加密传输的数据,确保数据在传输过程中不被窃取或篡改。
-
避免暴露敏感信息:
- 使用环境变量存储敏感信息,在构建过程中注入前端代码。
- 将敏感操作和数据处理放在后端,前端只负责与后端交互。
- 对需要传输的敏感数据进行加密处理。
-
采用安全的HTTP头:
- 使用Content Security Policy (CSP)限制资源加载来源,防止XSS攻击。
- 使用X-Content-Type-Options防止浏览器对MIME类型的猜测,确保文件类型正确。
- 使用X-Frame-Options防止点击劫持攻击,禁止网页被嵌入到iframe中。
-
使用HTTPS:
- 通过SSL/TLS协议加密数据传输,确保数据在传输过程中不被窃听或篡改。
-
访问控制:
- 设置Cookie的SameSite属性,限制跨站请求携带Cookie。
通过上述措施,可以有效地提高前端应用的安全性,预防各种常见的网络攻击。
3.软件设计原则有哪些
软件设计原则是设计模式的基石。目的只有一个,降低对象之间的耦合,增加程序的可复用性、可扩展性、可维护性
5.JS冒泡排序
function bubbleSort(arr) {
let n = arr.length;
for (let i = 0; i < n - 1; i++) {
// 设置一个标志位,用于检测本轮是否有交换
let swapped = false;
for (let j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
// 如果在这一轮中没有发生交换,说明数组已经有序,可以提前结束
if (!swapped) {
break;
}
}
return arr;
}
// 测试代码
const array = [64, 34, 25, 12, 22, 11, 90];
console.log("原始数组:", array);
const sortedArray = bubbleSort(array);
console.log("排序后的数组:", sortedArray);
关键点解释
外层循环:控制排序的轮数。从 0 到 n-2,共 n-1 轮。
内层循环:负责每轮的具体比较和可能的交换操作。每次内循环结束后,当前轮次中最大的未排序元素会被移动到正确的位置。
优化:通过引入 swapped 标志位,可以在某一轮没有发生任何交换的情况下提前结束排序,即数组已经是有序的,无需继续执行后续的比较操作。
减少不必要的比较:内层循环的范围是 0 到 n-1-i,因为每一轮结束后,最大的元素已经被放置在了正确的位置,所以后续的比较可以减少一次。
这个版本的冒泡排序同样具有 O(n²) 的时间复杂度,但在实际应用中,通过减少不必要的比较和提前终止,可以提高一些效率。
JS快排
function quickSort(arr, left = 0, right = arr.length - 1) {
if (left < right) {
const partitionIndex = partition(arr, left, right);
quickSort(arr, left, partitionIndex - 1); // 递归排序左子数组
quickSort(arr, partitionIndex + 1, right); // 递归排序右子数组
}
return arr;
}
function partition(arr, left, right) {
const pivot = arr[right]; // 选择最后一个元素作为基准
let i = left - 1; // i 指向小于基准值的最后一个元素
for (let j = left; j < right; j++) {
if (arr[j] < pivot) {
i++;
swap(arr, i, j); // 交换元素
}
}
swap(arr, i + 1, right); // 将基准值放到正确的位置
return i + 1; // 返回基准值的索引
}
function swap(arr, i, j) {
const temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 测试代码
const array = [64, 34, 25, 12, 22, 11, 90];
console.log("原始数组:", array);
const sortedArray = quickSort(array);
console.log("排序后的数组:", sortedArray);
快速排序原理
快速排序是一种高效的排序算法,采用分治法的思想。它的基本思路是通过一个分区操作将数组分成两个子数组,然后递归地对子数组进行排序。具体步骤如下:
选择基准值:从数组中选择一个元素作为基准值(pivot)。通常选择数组的最后一个元素,但也可以选择第一个元素、中间元素或随机元素。
分区操作:重新排列数组,使得所有小于基准值的元素都位于基准值的左侧,所有大于基准值的元素都位于基准值的右侧。这个过程称为分区操作。
递归排序:对基准值左侧的子数组和右侧的子数组分别递归地进行快速排序。
详细步骤
选择基准值:
选择数组的最后一个元素作为基准值。
分区操作:
初始化两个指针 i 和 j,其中 i 指向小于基准值的最后一个元素,j 用于遍历数组。
遍历数组,如果当前元素小于基准值,则将 i 增加 1 并交换 i 和 j 位置的元素。
遍历完成后,将基准值放到正确的位置(即 i + 1)。
递归排序:
递归地对基准值左侧的子数组进行快速排序。
递归地对基准值右侧的子数组进行快速排序。
关键点解释
快速排序函数 quickSort:
接受三个参数:待排序的数组 arr,左边界 left 和右边界 right。
如果 left 小于 right,则进行分区操作,并递归地对左右子数组进行排序。
最后返回排序后的数组。
分区函数 partition:
选择数组的最后一个元素作为基准值 pivot。
使用 i 指针记录小于基准值的最后一个元素的位置。
遍历数组,如果当前元素小于基准值,则将 i 增加 1 并交换 i 和当前元素的位置。
最后将基准值放到正确的位置,并返回基准值的索引。
交换函数 swap:
用于交换数组中两个指定位置的元素。
快速排序的特点
时间复杂度:平均时间复杂度为 O(n log n),最坏情况为 O(n²),但通过合理选择基准值可以避免最坏情况。
空间复杂度:O(log n),因为递归调用栈的深度。
稳定性:不稳定排序算法,因为相等的元素可能会因为交换而改变顺序
JS二分查找
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
for (let i = 0; left <= right; i++) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid; // 找到目标值,返回索引
} else if (arr[mid] < target) {
left = mid + 1; // 目标值在右半部分
} else {
right = mid - 1; // 目标值在左半部分
}
}
return -1; // 未找到目标值
}
// 测试代码
const array = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91];
const target = 23;
console.log("原始数组:", array);
const index = binarySearch(array, target);
if (index !== -1) {
console.log(`目标值 ${target} 在数组中的索引为: ${index}`);
} else {
console.log(`目标值 ${target} 不在数组中`);
}
关键点解释
初始化:left 和 right 分别初始化为数组的起始和结束索引。
循环条件:使用 for 循环,条件是 left <= right,确保查找范围有效。
计算中间位置:使用 Math.floor((left + right) / 2) 计算中间位置 mid,确保结果为整数。
比较中间值:
如果 arr[mid] 等于 target,返回 mid。
如果 arr[mid] 小于 target,说明目标值在右半部分,调整 left 指针。
如果 arr[mid] 大于 target,说明目标值在左半部分,调整 right 指针。
返回值:如果找到目标值,返回其索引;否则返回 -1 表示目标值不存在。
使用 for 循环的优势
结构清晰:for 循环的结构使得代码更加紧凑和易读。
控制变量:虽然在这个例子中我们没有使用 i 变量,但 for 循环的结构允许我们在需要时轻松添加额外的控制变量。
bind、call、apply
主要区别
调用时机:
call 和 apply 是立即调用函数。
bind 是创建一个新的函数,不会立即调用。
参数传递:
call 和 bind 都接受逗号分隔的参数列表。
apply 接受一个数组或类数组对象作为参数列表。
返回值:
call 和 apply 返回函数的执行结果。
bind 返回一个新的函数。
使用场景
call 和 apply:
适用于需要立即调用函数并改变 this 上下文的场景。
apply 特别适用于参数数量不确定的情况,可以通过数组传递参数。
bind:
适用于需要创建一个具有固定 this 上下文的新函数的场景。
常用于事件处理、回调函数等需要提前绑定 this 的情况。
function greet(name, age) {
console.log(`Hello, ${name}! I am ${age} years old. This is ${this}.`);
}
const obj = { name: 'Alice' };
// 使用 call
greet.call(obj, 'Bob', 30); // 输出: Hello, Bob! I am 30 years old. This is [object Object]
// 使用 apply
const args = ['Bob', 30];
greet.apply(obj, args); // 输出: Hello, Bob! I am 30 years old. This is [object Object]
// 使用 bind
const boundGreet = greet.bind(obj, 'Bob');
boundGreet(30); // 输出: Hello, Bob! I am 30 years old. This is [object Object]
on实现,内部是怎样的?
数据双向绑定、diff算法?
Js数据类型,基本和引用数据类型的区别?为什么基本存在栈上,引用数据类型存在堆上?
1、栈(stack)和堆(heap)
****stack为自动分配的内存空间,它由系统自动释放;而heap则是动态分配的内存,大小也不一定会自动释放
2、数据类型
JS分两种数据类型:
基本数据类型:Number、String、Boolean、Null、 Undefined、Symbol(ES6), 这些类型可以直接操作保存在变量中的实际值。
引用数据类型:Object(在JS中除了基本数据类型以外的都是对象,数据是对象,函数是对象,正则表达式是对象)
3、基本数据类型(存放在栈中)
基本数据类型是指存放在栈中的简单数据段,数据大小确定,内存空间大小可以分配, 它们是直接按值存放的,所以可以直接按值访问
4、引用数据类型(存放在堆内存中的对象,每个空间大小不一样,要根据情况进行特定的配置)
**** 引用类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。
引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象
Let const 和var的区别?
为什么const定义的应用数据类型可以变?
强缓存怎么设置,协商缓存怎么设置
如果第一次没有设置缓存,那么浏览器会缓存嘛?
本地缓存在哪,内存还是本地磁盘?
Service Worker
是一个注册在指定源和路径下的事件驱动 worker;特点是:
运行在 worker 上下文,因此它不能访问 DOM;独立于主线程之外,不会造成阻塞;设计完全异步,所以同步 API(如 XHR 和 localStorage )不能在 Service Worker 中使用;最后处于安全考虑,必须在 HTTPS 环境下才可以使用;说了这么多特点,那它和缓存有啥关系?其实它有一个功能就是离线缓存:Service Worker Cache;区别于浏览器内部的 memory cache 和 disk cache,它允许我们自己去操控缓存;通过 Service Worker 设置的缓存会出现在浏览器开发者工具 Application 面板下的 Cache Storage 中。
memory cache
是浏览器内存中的缓存,相比于 disk cache 它的特点是读取速度快,但容量小,且时效性短,一旦浏览器 tab 页关闭,memory cache 就将被清空。memory cache 会自动缓存所有资源嘛?答案肯定是否定的,当 HTTP 头设置了 Cache-Control: no-store 的时候或者浏览器设置了 Disabled cache 就无法把资源存入内存了,其实也无法存入硬盘。当从 memory cache 中查找缓存的时候,不仅仅会去匹配资源的 URL,还会看其 Content-type 是否相同。
disk cache
也叫 HTTP cache 是存在硬盘中的缓存,根据 HTTP 头部的各类字段进行判定资源的缓存规则,比如是否可以缓存,什么时候过期,过期之后需要重新发起请求吗?相比于 memory cache 的 disk cache 拥有存储空间时间长等优点,网站中的绝大多数资源都是存在 disk cache 中的。
浏览器如何判断一个资源是存入内存还是硬盘呢?关于这个问题,网上说法不一,不过比较靠谱的观点是:对于大文件大概率会存入硬盘中;当前系统内存使用率高的话,文件优先存入硬盘。
## vite 为什么比webpack快??
webpack: 一切皆可打包,是目前使用率最高的工程化框架,帮助我们打理代码调试到打包的全过程,但是也有一些缺点:
webpack在项目调试之前,要把所有文件的依赖关系收集完,打包后才能运行,这是它慢的主要原因,随着项目规模(强调,新手有这个视野很nice)的激增,慢的一坨屎一样(数分钟)。于是,针对webpack的bundle思路,社区推出了bundless思路框架:Vite。
从bundle到bundless,原因是浏览器里的JavaScript没有很好的方式去引入其他文件。Webpack(node环境运行) 只能通过require(commonJS) 将一堆js 按依赖关系组织起来,打包后运行。但是现在主流浏览器都支持ES6的module功能(import/export)。
<script type="module" src="/src/main.js"></script>
只要在script标签上添加type="module"标记, main.js 就可以直接使用import 语法(动态导入)去引入js 文件,所以可以不用webpack(node)打包功能,直接使用浏览器的module功能就可以组织我们的代码。
Vite 能够做到这么快的原因,还有一部分是因为使用了 esbuild 去解析 JavaScript 文件。esbuild 是一个用 Go 语言实现的 JavaScript 打包器,支持 JavaScript 和 TypeScript 语法,现在前端工程化领域的工具也越来越多地使用 Go 和 Rust 等更高效的语言书写,这也是性能优化的一个方向。
到这里,我们就向面试官讲清楚了 bundle 和bundless。这才是vite 更快的关键。
- 优缺点
- 扩展问题
-
webpack.config.js 中 entry, output, module.rules, plugins 等关键配置项的作用及其具体用法
-
代码分割、Tree Shaking 和懒加载等技术
-
Vite 开发时,如何处理 CSS 预处理器
-
手写Vite
-
热更新机制的理解
-
链表有接触嘛?
关系型和非关系型在底层有什么区别?
秋招第一面—北森云计算—前端(part1)
vue双向绑定的具体实现原理
给一个数组:[1,2,2,3,3,4,5,5,6] 得到[1,4,6]
深拷贝
- JSON 序列化和反序列化 JSON.parse(JSON.stringify(obj));
- 递归函数实现
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return null; // 处理 null
if (typeof obj !== 'object') return obj; // 处理基本类型
if (obj instanceof Date) return new Date(obj); // 处理日期对象
if (obj instanceof RegExp) return new RegExp(obj); // 处理正则对象
// 检查是否已经克隆过该对象
if (hash.has(obj)) return hash.get(obj);
let cloneObj = Array.isArray(obj) ? [] : {};
// 将原对象和克隆对象存入哈希表,防止循环引用
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
3. 使用库 例如 Lodash 的 _.cloneDeep 方法。 4. 使用 structuredClone(现代浏览器支持
虚拟dom树,为什么不直接操作实体dom,为什么操作实体dom会卡顿
虚拟DOM不会进行排版与重绘操作
什么事闭包
了解泛型吗
防抖与节流
- 防抖(Debounce) 防抖的原理是:在一定时间内,多次触发某个函数,只有当最后一次触发后,经过指定的时间间隔没有再次触发,才会执行该函数。防抖常用于输入框的搜索、窗口的调整大小等场景。
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
// 使用示例
const handleResize = () => {
console.log('Window resized');
};
window.addEventListener('resize', debounce(handleResize, 300));
- 节流(Throttle) 节流的原理是:在一定时间内,多次触发某个函数,只会执行一次。节流常用于滚动事件、鼠标移动事件等场景。
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// 使用示例
const handleScroll = () => {
console.log('Window scrolled');
};
window.addEventListener('scroll', throttle(handleScroll, 300));
vue什么时候会存在修改变量值,没有双向绑定,怎么解决
vue父子组件,父组件有一个变量,这个变量要传给子组件,当变量改变的时候,子组件根据条件选择改变或者不改变
vue 距离很远的组件之间的事件传递
浏览器同源策略,跨域
判断数组的方法
css:实现九宫格
osi七层模型
从一个客户端发到另一个客户端的消息,数据怎么流动的
tcp的包有序号吗
tcp为什么需要三次握手
TCP(Transmission Control Protocol)需要三次握手来建立连接的原因如下:
- 确认双方的接收和发送能力:
第一次握手:客户端发送一个SYN(同步序列编号)包给服务器,表明它希望建立连接。这确保了客户端至少能够发送数据。
第二次握手:服务器收到SYN包后,回复一个SYN+ACK(同步+确认)包,表示它收到了客户端的请求,并同意建立连接。这表明服务器能够接收和回应数据。
- 防止已失效的连接请求:
如果一个客户端的SYN包在途中丢失,但客户端没有重试,服务器可能会等待一段时间(称为SYN超时)才释放资源。第三次握手(客户端的ACK)确保服务器知道客户端确实收到了SYN+ACK,避免服务器为过时的请求保持打开的连接状态。
- 避免半开连接:
如果只需要两次握手,客户端发送SYN后,服务器回复SYN+ACK,但客户端因为某种原因没有确认,服务器会认为连接已经建立,而客户端可能没有。这种情况下,服务器会占用资源等待客户端的数据,而客户端实际上并没有连接。
- 防止重复的SYN包导致错误连接:
网络中可能存在延迟或重复的SYN包。如果只有两次握手,一个旧的SYN包可能在延迟后到达服务器,服务器会误以为这是新的连接请求。三次握手可以避免这种情况,因为服务器会等待客户端的确认,而不会为旧的SYN包建立新的连接。 通过三次握手,TCP确保了连接的可靠建立,同时避免了资源的无效占用和错误的连接状态。
dns查询过程数据怎么流动的
关于DNS的获取流程:
DNS是应用层协议,事实上他是为其他应用层协议工作的,包括不限于HTTP和SMTP以及FTP,用于将用户提供的主机名解析为ip地址。
具体过程如下:
①用户主机上运行着DNS的客户端,就是我们的PC机或者手机客户端运行着DNS客户端了
②浏览器将接收到的url中抽取出域名字段,就是访问的主机名,比如
http://www.baidu.com/
, 并将这个主机名传送给DNS应用的客户端
③DNS客户机端向DNS服务器端发送一份查询报文,报文中包含着要访问的主机名字段(中间包括一些列缓存查询以及分布式DNS集群的工作)
④该DNS客户机最终会收到一份回答报文,其中包含有该主机名对应的IP地址
⑤一旦该浏览器收到来自DNS的IP地址,就可以向该IP地址定位的HTTP服务器发起TCP连接
tcp拥塞控制
TCP拥塞控制是网络通信中非常重要的一部分,它主要用来防止过多的数据注入到网络中,从而避免网络拥塞和超负荷。TCP拥塞控制主要通过四种算法来实现:慢开始(slow-start)、拥塞避免、快重传和快恢复。
-
慢开始(Slow-Start) :
- 在TCP连接建立之初,拥塞窗口(cwnd)从1个最大报文段(MSS)开始,指数级增长,直到达到慢启动阈值(ssthresh)。
- 每收到一个对新报文段的确认,拥塞窗口的值就增加1,这种指数增长方式可以快速填充网络,直到网络出现拥塞的迹象。
-
拥塞避免:
- 当拥塞窗口增长到慢开始门限值时,就改为执行拥塞避免算法,此时拥塞窗口的增长方式从指数级变为线性,即每个传输轮次结束后,拥塞窗口值只能线性增加1。
-
快重传(Fast Retransmit) :
- 当发送方连续收到三个重复的确认(Duplicate ACK),就会立即重传丢失的报文段,而不必等待重传计时器超时。
-
快恢复(Fast Recovery) :
- 在快重传之后,慢开始门限值更新为当前拥塞窗口的一半,并且拥塞窗口设置为慢开始门限值,直接进入拥塞避免阶段而不是重新从慢开始阶段开始。
这些算法共同工作,使得TCP能够在网络中动态调整发送速率,以适应网络的拥塞情况。当网络出现拥塞时,TCP通过减少发送速率来减轻网络负担;而当网络状况良好时,则尝试增加发送速率以提高吞吐量。这种自适应的调整机制是TCP拥塞控制的核心。
发到同一个域名的多个请求可以一起发吗
HTTP/1.1中存在一个问题,单个TCP连接在同一时刻只能处理一个请求,意思是说:两个请求的生命周期不能重叠,任意两个HTTP请求从开始到结束的时间在同一个TCP连接里不能重叠
在HTTP/1.1存在Pipelining技术可以完成这个多个请求同时发送,但是由于浏览器默认关闭,所以可以认为这是不可行的。在HTTP2中由于Multiplexing特点的存在,多个HTTP请求可以同一个TCP连接中并行进行
那么在HTTP/1.1时代,浏览器是如何提高页面加载效率的呢?主要有以下两点:
维持和服务器已经建立的TCP连接,在同一个连接上处理多个请求
和服务器建立多个TCP连接
举个栗子
在某个快递运输公司上班的快递小哥,每一次上门换新的时候只能在一个时间点处理一个用户换新的问题
(HTTP/1.1中存在一个问题,单个TCP连接在同一时刻只能处理一个请求)
如果有很多个用户都需要换新的话,就只能排队等着快递小哥了
(两个请求的生命周期不能重叠,任意两个HTTP请求从开始到结束的时间在同一个TCP连接里不能重叠)
在最开始的时候,可以让附近的用户前往同一个地点,由快递小哥同时处理换新问题,但是因为这样怕影响用户的使用体验,所以公司禁止了这种行为。
(在HTTP1.1中存在Pipelining技术可以完成这个多个请求同时发送,但是由于浏览器默认关闭,所以可以认为这是不可行的)
后来,公司对自身的APP的功能进行了拓展,就可以使一个快递小哥同时处理多个换新业务
(在HTTP2中由于Multiplexing特点的存在,多个HTTP请求可以在同一个TCP连接中并行进行)
那么在一开始公司的APP没有进行功能拓展的时候该如何处理上述的情况的呢?
(在HTTP1.1时代,浏览器是如何提高页面加载效率的呢)
让快递小哥负责一个小区内的全部换新问题
(维持和服务器已经建立的TCP连接,在同一个连接上处理多个请求)
派多个快递小哥同时去处理这些问题
(和服务器建立多个TCP连接)
http缓存(如果有cdn会怎么样,没看过cdn,盲猜边缘服务器会询问中心服务器,猜对了一点点)
# 浏览器缓存、HTTP缓存(强缓存、协商缓存),浏览器缓存和CDN的关系
# 新手必看:访问url到加载全过程详解(看完不会我吃shi)
类似word的标题形式(1 xx 1.1 xx 1.1.1 xx),创建一种数据结构存储他们,并且用js生成对应的html
vue里,数据改变怎么触发页面渲染,如果没有节点使用这个数据,会是什么样的过程
promise.all和promise.race, 如果promise.race改一下,改成收到其中两个即可进行下一步动作怎么改
vue双向绑定,搞了个例子一边讲他一边提问,这是我答的最艰难的双向绑定和diff算法
组件化
最近看过的一篇技术文章,觉得有什么收获
学习能力强体现在哪里(简历里自我评价里提了一句,但是别人都不看这个啊)
中间讲vue双向绑定的时候,我说到会看一看源码,跟他说了不算源码,就是简单的实现代码,他问我看这个干什么,你觉得对你有什么帮助吗,你学了vue的双向绑定在工作中用到了吗?
职业规划
http由哪些字段组成
HTTP(Hypertext Transfer Protocol)请求和响应都由多个部分组成,主要包括以下字段:
- HTTP请求的组成部分:
请求行:
请求方法(Method):如GET、POST、PUT、DELETE等,指示客户端希望执行的操作。
URL(Uniform Resource Locator):指定要访问的资源的地址。
HTTP版本(HTTP Version):如HTTP/1.1或HTTP/2,表示使用的HTTP协议版本。
请求头(Request Headers):
包含多个键值对,提供关于请求的额外信息,如用户代理、接受的媒体类型、请求的编码、授权信息等。
空行:
一个空行分隔请求头和请求体。
请求体(Request Body):
对于POST、PUT等方法,请求体包含要发送到服务器的数据,如表单数据、JSON对象等。
- HTTP响应的组成部分:
状态行:
HTTP版本:同请求,表示响应使用的HTTP协议版本。
状态码(Status Code):三位数字,表示请求的处理结果,如200表示成功,404表示未找到,500表示服务器错误等。
状态消息(Reason Phrase):对状态码的可读性描述。
响应头(Response Headers):
类似于请求头,包含服务器返回的附加信息,如服务器类型、内容长度、缓存控制、响应编码等。
空行:
分隔响应头和响应体。
响应体(Response Body):
包含服务器返回的实际内容,如HTML页面、JSON数据、图像或其他资源。
每个部分都有其特定的作用,共同构成了完整的HTTP交互。
创建对象的方式
1.字面量创建对象 (每个对象都是独立的)用于创建单个对象
2.内置构造函数创建对象
3.工厂函数创建对象 (可以根据需要传不同参数 创建多个对象)
4.自定义构造函数创建对象 (通过new关键字简化步骤和代码 和工厂函数作用相同)
5.class类创建对象 (代码结构清晰)
原型和原型链
反转链表
面试官很好,问了我怎么解决跨域问题,有哪些拷贝,哪些继承,都有什么优缺点
js面向对象的特性
- 封装可以隐藏实现细节,使得代码模块化;
- 继承可以扩展已存在的代码模块(类),它们的目的都是为了——代码重用。
- 多态就是相同的事物,调用其相同的方法,参数也相同时,但表现的行为却不同。多态分为两种,一种是行为多态与对象的多态
你和其他人相比有什么优势
你为什么来应聘
简历上的……你获得了什么
你是怎么解决遇到的困难的
社招前端二面面试题 附答案,不是北森云的 cloud.tencent.com/developer/a…
北森云计算前端实习面试(一面)附答案 blog.csdn.net/qq_35493664…
第一次前端面试复盘(北森一面凉经) blog.csdn.net/lijunyan5/a…
react冒泡机制,我没答上来
react的合成事件?没答上来,我好菜.jpg
div盒子绝对居中的方式?我说了六种
基础类型都有哪些?我一开始少说了个null,于是他之后都在追问我null和undefined的问题
null和undefined的含义?null和undefine的区别?怎么区分null和undefined?答的还行
说说跨域是咋回事?如何解决跨域问题?经典八股文,回到了我的主场,答的挺好
算法题,实现转置矩阵?追问:为什么要预先fill(0)?提前规定好所需空间,防止系统分配过多空间造成浪费。追问:时间复杂度?O(mn)。空间复杂度?O(1)
localstorage与sessionstorage以及cookie的区别。
主要区别
生命周期:
localStorage:数据是持久化的,除非手动清除或通过代码删除。
sessionStorage:数据仅在当前会话期间有效,关闭浏览器标签页或窗口后数据会被清除。
cookie:可以设置过期时间,如果没有设置过期时间,cookie 会在浏览器关闭时被清除。
数据容量:
localStorage 和 sessionStorage:每个域名大约可以存储 5MB 的数据。
cookie:每个域名最多可以存储 4KB 的数据。
安全性:
localStorage 和 sessionStorage:数据存储在客户端,可以通过 JavaScript 访问。
cookie:可以通过 HttpOnly 标志防止通过 JavaScript 访问 cookie,增加安全性。
用途:
localStorage:适用于需要长期保存的数据,如用户偏好设置、缓存数据等。
sessionStorage:适用于临时数据,如购物车、表单数据等。
cookie:适用于需要在服务器和客户端之间传递的数据,如会话管理、用户认证等。
跨域怎么携带cookie
- 服务器端配置 服务器端需要设置响应头以允许跨域请求携带 cookie。具体来说,需要设置 Access-Control-Allow-Origin 和 Access-Control-Allow-Credentials 头
- 客户端配置 客户端在发送跨域请求时,需要设置 withCredentials 为 true,以便携带 cookie。
new的过程中发生了什么。
创建新对象:new 关键字会创建一个全新的空对象。
设置原型:新对象的 [[Prototype]] 会被设置为构造函数的 prototype 属性。
调用构造函数:构造函数会被调用,并且 this 被绑定到新创建的对象上。
返回新对象:如果构造函数没有显式返回一个对象,那么 new 表达式会返回新创建的对象;如果构造函数显式返回了一个对象,则返回该对象。
function myNew(Constructor, ...args) {
// 1. 创建一个新对象
const newObj = {};
// 2. 设置新对象的原型
newObj.__proto__ = Constructor.prototype;
// 3. 调用构造函数
const result = Constructor.apply(newObj, args);
// 4. 返回新对象
if (typeof result === 'object' && result !== null) {
return result;
} else {
return newObj;
}
}
// 使用自定义的 myNew 函数
const bob = myNew(Person, 'Bob', 25);
console.log(bob.name); // 输出: Bob
console.log(bob.age); // 输出: 25
bob.greet(); // 输出: Hello, my name is Bob and I am 25 years old.
数组扁平化实现一下。
常见的状态码有哪些?
HTTP 状态码是服务器在响应客户端请求时返回的一个数字代码,用于表示请求的处理结果。这些状态码分为五个类别,每个类别都有其特定的含义。以下是一些常见的 HTTP 状态码及其含义:
- 信息性状态码(1xx) 这些状态码表示请求已被接收,继续处理。 100 Continue:客户端应继续其请求。 101 Switching Protocols:服务器已理解客户端的请求,并将通过升级协议来进行处理。
- 成功状态码(2xx) 这些状态码表示请求已成功被服务器接收、理解,并接受。 200 OK:请求成功,响应体通常包含请求的资源。 201 Created:请求成功并且服务器创建了一个新的资源。 202 Accepted:请求已接受,但尚未处理完成。 204 No Content:请求成功,但响应体为空。
- 重定向状态码(3xx) 这些状态码表示客户端需要采取进一步的操作才能完成请求。 301 Moved Permanently:请求的资源已永久移动到新位置。 302 Found:请求的资源临时从不同的 URI 响应请求。 303 See Other:请求的资源可以被另一个 URI 获取。 304 Not Modified:资源未被修改,客户端可以使用缓存的版本。 307 Temporary Redirect:请求的资源临时从不同的 URI 响应请求,但请求方法不应改变。 308 Permanent Redirect:请求的资源已永久移动到新位置,但请求方法不应改变。
- 客户端错误状态码(4xx) 这些状态码表示客户端可能发生了错误,妨碍了服务器的处理。 400 Bad Request:请求无效或无法被服务器理解。 401 Unauthorized:请求要求用户的身份认证。 403 Forbidden:服务器理解请求,但拒绝执行。 404 Not Found:请求的资源不存在。 405 Method Not Allowed:请求方法对资源不适用。 408 Request Timeout:服务器等待请求超时。 429 Too Many Requests:客户端在给定时间内发送了太多请求。
- 服务器错误状态码(5xx) 这些状态码表示服务器在处理请求时发生了错误。 500 Internal Server Error:服务器遇到了未知的错误。 501 Not Implemented:服务器不支持请求的方法。 502 Bad Gateway:服务器作为网关或代理,从上游服务器收到了无效的响应。 503 Service Unavailable:服务器暂时无法处理请求,通常是由于过载或维护。 504 Gateway Timeout:服务器作为网关或代理,未能及时从上游服务器获取响应。 505 HTTP Version Not Supported:服务器不支持请求中使用的 HTTP 协议版本。
定义对象的几种方法.
垂直居中你说出marign auto position left:0 right 0 bottom 0 top 0 。那你能讲解一下这么做的原理吗?
你提到了object.defineporperty 详细说一下。有什么缺点?
proxy说一下为什么能替代defineporperty?
谈谈XSS,怎么预防
- 输入验证和过滤
- 输出编码.在将用户输入插入到 HTML 中时,对其进行 HTML 转义。
- 使用内容安全策略(CSP).通过设置 Content-Security-Policy HTTP 头,限制页面可以加载的资源和脚本来源。
- 在设置 cookie 时,使用 HttpOnly 标志,防止通过 JavaScript 访问 cookie。