2023.08.20 - 2023.08.25 更新前端面试问题总结(18道题)
获取更多面试相关问题可以访问
github 地址: github.com/pro-collect…
gitee 地址: gitee.com/yanleweb/in…
目录:
-
中级开发者相关问题【共计 12 道题】
- 517.html rel 属性 的参数 preload和prefetch 的作用是什么【热度: 1,057】【浏览器】【出题公司: 小米】
- 518.HTML 属性 crossorigin 的作用是什么【热度: 267】【浏览器】【出题公司: 小米】
- 519.[代码执行] 关于 this 的指向问题:下面代码执行结果是什么, 原因?【JavaScript】【出题公司: 百度】
- 520.for...of、for...in、for 循环, 三者有什么区别【热度: 1,652】【JavaScript】【出题公司: 腾讯】
- 521.实现一个可以用for...of遍历的对象【热度: 653】【JavaScript】【出题公司: 腾讯】
- 525.[Vue] 异常处理机制有哪些【热度: 435】【web框架】【出题公司: 腾讯】
- 526.html 标签属性 src 和 href 有什么区别【热度: 1,134】【浏览器】【出题公司: PDD】
- 527.常见的请求头和响应头【网络】【出题公司: 京东】
- 528.HTTP/1.0、HTTP/1.1、HTTP/2和HTTP/3之间的主要区别【热度: 1,447】【网络】【出题公司: 腾讯】
- 530.http 常见状态码有哪些【热度: 1,410】【网络】【出题公司: 百度】
- 531.Http 状态码 301 和 302 的应用场景分别是什么【网络】【出题公司: 百度】
- 532.什么是 JWT【热度: 428】【网络】【出题公司: 小米】
-
高级开发者相关问题【共计 6 道题】
- 516.[性能] 常见性能指标获取方式?【热度: 954】【工程化】【出题公司: 美团】
- 522.前端模块化的演进过程【热度: 798】【工程化】【出题公司: 京东】
- 523.webpack tree-shaking 在什么情况下会失效?【热度: 171】【工程化】【出题公司: 阿里巴巴】
- 524.[Vue] vue3 的 diff 算法是什么,简单介绍一下【热度: 693】【web框架】【出题公司: 腾讯】
- 529.http2 多路复用是什么, 原理是什么【热度: 353】【网络】【出题公司: 腾讯】
- 533.单点登录是什么, 具体流程是什么【热度: 1,168】【web应用场景】【出题公司: 小米】
中级开发者相关问题【共计 12 道题】
517.html rel 属性 的参数 preload和prefetch 的作用是什么【热度: 1,057】【浏览器】【出题公司: 小米】
关键词:rel preload 作用、rel prefetch 作用、rel defer 作用、rel prefetch
rel 属性定义了所链接的资源与当前文档的关系,在 <a>、<area> 和 <link> 元素上有效。支持的值取决于拥有该属性的元素。
preload和prefetch是浏览器提供的两种对静态资源预下载的方式,对于优化页面的渲染速度是很有作用的。
preload - 立即下载
preload针对的是当前页面需要加载的资源,使用preload加载的资源会提前下载,但是并不会立即执行,而且等到使用的时候才会执行。
preload 使用方式
preload是元素中rel属性的一个值,所以需要使用link标签来实现资源的预加载
<link rel="preload" as="script" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.min.js">
对于预加载的资源来说,一般需要设置以下三个属性:
rel: preload或者prefetch,表示预加载的方式。必填(rel的值很多,这里只考虑预加载的情况)as: 表示预加载资源的类型。必填href: 表示预加载资源的地址。必填
当预加载的是字体资源时,必须加上crossorigin属性
preload的好处
- 能够分离资源的下载和执行
- 能够提高资源的下载优先级
- 能够支持多种资源的预下载,比如脚本,样式,图片等等
preload VS defer
和preload一样,defer的script资源也会将下载和执行过程分离。不同的是,preload的资源是由开发者来确定何时执行,defer的script资源是由浏览器来决定何时执行。
除此之外,defer和preload相比还有以下缺点:
只能支持script资源
preload VS 预解析操作
在分析页面渲染流程的时候我们提到过浏览器的一个优化操作,就是预解析操作。当浏览器获取到HTML文件之后,会分析其中依赖哪些外部资源,并提前下载这些外部资源。
看上去这个功能和preload基本上是一样的,可以达到同样的效果。
但是浏览器预解析操作有一个缺陷:就是只能预下载HTML文件中引入的静态资源,对于当前页面动态加载的资源是无能为力的。但是preload可以解析这个问题。
prefetch - 有空才下载
prefetch针对的资源是用户下个浏览的页面需要的资源,可以在当前页面开始预下载,提高下个页面渲染的速度。
在使用上,prefetch和preload基本是一致的。
preload VS prefetch
preload 和 prefetch在使用上是有很大的不同的。
- preload针对的资源是当前页面需要的资源,下载的优先级很高
- prefetch针对的资源是下个页面需要的资源,下载的优先级很低,有空的时候才下载
所以开发者是使用的时候需要区分场景,避免浪费用户的带宽资源。
518.HTML 属性 crossorigin 的作用是什么【热度: 267】【浏览器】【出题公司: 小米】
关键词:crossorigin 属性、crossorigin 作用、crossorigin 资源错误处理
crossorigin 属性在 <audio>、<img>、<link>、<script> 和 <video> 元素中有效,它们提供对 CORS 的支持,定义该元素如何处理跨源请求,从而实现对该元素获取数据的 CORS 请求的配置。根据元素的不同,该属性可以是一个 CORS 设置属性。
属性值
crossorigin 属性有以下几个取值选项,每个选项的作用如下:
anonymous:表示跨域请求不发送凭证信息(如 cookie、HTTP 认证信息)。这是默认值,适用于无需发送凭证的跨域请求,可提高安全性。use-credentials:表示跨域请求发送凭证信息。适用于需要发送凭证的跨域请求,但需要服务器配置支持,并且需要设置Access-Control-Allow-Credentials头为true。null:表示不返回跨域资源,并在控制台中报告错误,而不加载跨域资源。适用于跨域资源加载失败时的错误处理。
这些取值选项用来在 HTML 中指定跨域资源请求的行为,通过设置不同的取值选项,可以控制是否发送凭证、如何处理跨域资源加载失败等。
作用
crossorigin 属性是 HTML 中用来控制跨域资源请求行为的属性。它用于指定浏览器在加载跨域资源时如何处理跨域请求。
主要作用有以下几点:
- 跨域资源请求:当在 HTML 中引用跨域的资源(如图片、音频、视频、脚本、样式表等)时,浏览器会发送跨域请求。
crossorigin属性可以控制这些跨域请求的行为。 - 控制凭证的发送:默认情况下,跨域请求会发送用户凭证(如 cookie、HTTP 认证信息)。通过
crossorigin属性,可以控制资源请求时是否发送凭证信息。 - 防止资源污染:当加载跨域的脚本文件时,如果不使用
crossorigin属性,可能会导致脚本文件被污染从而引发安全问题。使用crossorigin属性可以确保加载的脚本是可信任的。 - 错误处理:
crossorigin属性还可以用来处理跨域请求中可能发生的错误。通过设置不同的取值选项,可以在跨域请求出现错误时进行相应的处理。
资源加载错误处理方式
crossorigin 属性在错误处理方面有不同的行为,取决于属性的取值选项:
- 当
crossorigin属性值为anonymous或未设置时,如果跨域资源加载失败,浏览器会忽略加载失败,不会报告任何错误,也不会影响页面的正常渲染。 - 当
crossorigin属性值为use-credentials时,如果跨域资源加载失败,浏览器会在控制台报告错误,并且不会加载跨域资源。这样可以确保在有凭证的情况下,不加载错误的或未授权的跨域资源。 - 当
crossorigin属性值为null时,如果跨域资源加载失败,浏览器会在控制台报告错误,并且不加载跨域资源。这种设置适用于当跨域资源加载失败时要显示错误信息,并且不加载其他资源。
总之,通过设置 crossorigin 属性,可以控制跨域资源加载失败时的错误处理行为,从而在不同的情况下选择合适的错误处理方式。
519.[代码执行] 关于 this 的指向问题:下面代码执行结果是什么, 原因?【JavaScript】【出题公司: 百度】
代码如下,请问执行结果是多少?
let obj = {
name: "yanle",
age: 20,
getName: () => {
const _getName = () => {
console.log("this.getName", this.name);
};
_getName();
},
getAge: function() {
const _getAge = () => {
console.log("this.getAge", this.age);
};
_getAge();
},
extend: {
name: "le",
age: 20,
getName: function() {
console.log("name: ", this.name);
},
getAge: () => {
console.log("age: ", this.age);
},
},
};
obj.getName();
obj.getAge();
obj.extend.getName();
obj.extend.getAge();
obj.extend.getName.bind(obj)();
obj.extend.getAge.bind(obj)();
执行结果
this.getName undefined
this.getAge 20
name: le
age: undefined
name: yanle
age: undefined
解释如下:
obj.getName():在箭头函数getName中,this指向的是全局对象(在浏览器中是window对象,Node.js 中是Global对象)。因此this.getName输出undefined。obj.getAge():在普通函数getAge中,this指向的是obj对象。因此this.getAge输出20。obj.extend.getName():在普通函数getName中,this指向的是obj.extend对象。因此this.name输出le。obj.extend.getAge():在箭头函数getAge中,this指向的是全局对象(在浏览器中是window对象,Node.js 中是Global对象)。因此this.age输出undefined。obj.extend.getName.bind(obj)():通过bind方法将getName函数绑定到obj对象上,并立即调用绑定后的函数。在绑定后调用时,this指向的是obj对象。因此this.name输出yanle。obj.extend.getAge.bind(obj)():在箭头函数 getAge 中,this 是在函数定义时绑定的,而不是在函数调用时绑定的。在这种情况下,箭头函数的 this 指向的是外层作用域的 this,即全局对象(在浏览器中是 window 对象,Node.js 中是 Global 对象)。因此,在 obj.extend.getAge.bind(obj)() 中,this.age 输出的是全局对象的 age,而全局对象中并没有定义 age 属性,所以结果是 undefined。
520.for...of、for...in、for 循环, 三者有什么区别【热度: 1,652】【JavaScript】【出题公司: 腾讯】
关键词:for...in遍历、for...of遍历
以下是 for...of、for...in 和 for 循环的区别对比表格:
| 特性 | for...of 循环 | for...in 循环 | for 循环 |
|---|---|---|---|
| 遍历对象类型 | 可以遍历可迭代对象(如数组、字符串、Set、Map、Generator 等) | 可以遍历对象的可枚举属性 | 不适用于直接遍历对象,适用于遍历数组或固定个数的循环 |
| 遍历数组 | 遍历数组的元素 | 遍历数组的索引 | 遍历数组的索引或值 |
| 遍历字符串 | 遍历字符串的字符 | 遍历字符串的索引 | 遍历字符串的索引或字符 |
| 遍历 Set | 遍历 Set 的值 | 不适用 | 不适用 |
| 遍历 Map | 遍历 Map 的键值对 | 不适用 | 不适用 |
| 遍历对象 | 不适用 | 遍历对象的可枚举属性及其对应的值 | 不适用 |
| 遍历 Generator | 遍历 Generator 生成的值 | 不适用 | 不适用 |
| 遍历可迭代对象 | 遍历可迭代对象的元素 | 不适用 | 不适用 |
| 适用范围 | 适用于需要遍历可迭代对象的场景 | 适用于需要遍历对象的可枚举属性的场景 | 适用于需要手动控制循环次数的场景 |
| 遍历顺序 | 按照可迭代对象的顺序进行遍历 | 不保证顺序 | 按照循环次数进行遍历 |
需要注意的是,for...of 循环只能用于可迭代对象,并且会遍历对象的迭代器方法(即 Symbol.iterator),而 for...in 循环会遍历对象的所有可枚举属性,包括原型链上的属性。
对于遍历数组的场景,可以使用 for...of 循环遍历数组的元素,也可以使用 for 循环遍历数组的索引或值。具体选择哪种方式取决于遍历的目的和需求。
以下是一个使用不同循环方式遍历数组的示例:
var arr = [1, 2, 3];
console.log("for...of 循环:");
for (var element of arr) {
console.log(element);
}
console.log("for...in 循环:");
for (var index in arr) {
console.log(arr[index]);
}
console.log("for 循环:");
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
输出结果为:
for...of 循环:
1
2
3
for...in 循环:
1
2
3
for 循环:
1
2
3
521.实现一个可以用for...of遍历的对象【热度: 653】【JavaScript】【出题公司: 腾讯】
关键词:for...in遍历、for...of遍历
普通对象因为没有迭代器,所以无法使用for...of遍历,一般使用for...in或者Object.keys()来遍历
但是如果我们手动给对象设置一个迭代器,对象也是可以使用for...of来遍历的;
var obj = {
a: 1,
b: 2,
c: 3
}
obj.__proto__[Symbol.iterator] = function* objectIterator() {
for (let key in this) {
if (obj.hasOwnProperty(key)) {
yield [key, this[key]]
}
}
}
for (let v of obj) {
console.log(v);
}
//['a',1]
//['b',2]
//['c',3]
525.[Vue] 异常处理机制有哪些【热度: 435】【web框架】【出题公司: 腾讯】
关键词:vue错误捕获、vue错误边界、vue异常处理、vue errorHandler
Vue的错误处理机制主要包括以下几个方面:
Error Capturing(错误捕获):Vue提供了全局错误处理的钩子函数errorCaptured,可以在组件层级中捕获子组件产生的错误。通过在父组件中使用errorCaptured钩子函数,可以捕获子组件中的错误,并对其进行处理或展示错误信息。Error Boundary(错误边界):Vue 2.x中没有内置的错误边界机制,但你可以通过自定义组件来实现。错误边界是一种特殊的组件,它可以捕获并处理其子组件中的错误。错误边界组件使用errorCaptured钩子函数来捕获子组件中的错误,并使用v-if或v-show等指令来显示错误信息或替代内容。
3. 异常处理:在Vue组件的生命周期钩子函数中,可以使用try-catch语句捕获并处理可能出现的异常。例如,在mounted钩子函数中进行接口请求,可以使用try-catch来捕获请求过程中的异常,并进行相应的处理。
错误提示和日志记录:在开发环境中,Vue会在浏览器的控制台中输出错误信息,以方便开发者进行调试。在生产环境中,可以使用日志记录工具(如Sentry)来记录错误信息,以便及时发现和解决问题。
代码举例
以下是使用代码举例说明以上四种Vue错误处理方式的示例:
- Error Capturing(错误捕获):
// ParentComponent.vue
<template>
<div>
<ChildComponent/>
<div v-if="error">{{ error }}</div>
</div>
</template>
<script>
export default {
data() {
return {
error: null
};
},
errorCaptured(err, vm, info) {
this.error = err.toString(); // 将错误信息存储在父组件的data中
return false; // 阻止错误继续向上传播
}
};
</script>
- Error Boundary(错误边界):
// ErrorBoundary.vue
<template>
<div v-if="hasError">
Oops, something went wrong.
<button
@click="resetError">Retry
</button>
</div>
<div v-else>
<slot></slot>
</div>
</template>
<script>
export default {
data() {
return {
hasError: false
};
},
errorCaptured() {
this.hasError = true;
return false;
},
methods: {
resetError() {
this.hasError = false;
}
}
};
</script>
// ParentComponent.vue
<template>
<div>
<ErrorBoundary>
<ChildComponent/>
</ErrorBoundary>
</div>
</template>
- 异常处理:
// ChildComponent.vue
<template>
<div>{{ data }}</div>
</template>
<script>
export default {
data() {
return {
data: null
};
},
mounted() {
try {
// 模拟接口请求
const response = await fetch('/api/data');
this.data = await response.json();
} catch (error) {
console.error(error); // 处理异常,输出错误信息
}
}
};
</script>
- 错误提示和日志记录:
// main.js
import Vue from 'vue';
import * as Sentry from '@sentry/browser';
Vue.config.errorHandler = (err) => {
console.error(err); // 错误提示
Sentry.captureException(err); // 错误日志记录
};
new Vue({
// ...
}).$mount('#app');
上述代码中,Error Capturing通过在父组件中的errorCaptured钩子函数中捕获子组件的错误,并展示在父组件中。Error Boundary 通过自定义错误边界组件,在子组件发生错误时展示错误信息或替代内容。异常处理通过在子组件的生命周期钩子函数中使用try-catch语句来捕获异常并进行处理。错误提示和日志记录通过在Vue.config.errorHandler 中定义全局的错误处理函数,将错误信息输出到控制台,并使用Sentry等工具记录错误日志。
这些示例展示了不同的错误处理方式,可以根据实际需求选择合适的方式来处理Vue应用中的错误。
526.html 标签属性 src 和 href 有什么区别【热度: 1,134】【浏览器】【出题公司: PDD】
关键词:src和href 有什么区别
下面是一个表格,展示了src和href属性之间的异同点:
| 特点 | src属性 | href属性 |
|---|---|---|
| 适用标签 | <script>、<img>、<audio>等 | <a>、<link>等 |
| 加载时间 | 标签加载时立即执行 | 用户与链接交互时加载 |
| 对页面功能的影响 | 对页面功能至关重要,不能加载或加载错误会影响页面 | 不会直接影响页面功能,无法加载或加载错误时链接无效 |
| 资源类型 | 脚本文件、图像文件、音频文件等 | HTML文件、CSS文件、图像文件、音频文件等 |
| 是否必须有效链接 | 是 | 否 |
| 作用 | 嵌入外部资源 | 指向其他页面或资源 |
请注意,这些是src和href属性的一般规则,但某些特定标签可能会有不同的行为。
527.常见的请求头和响应头【网络】【出题公司: 京东】
通用头部字段
指的是在请求头和响应头中都可以使用的字段
| 通用字段 | 作用 |
|---|---|
| Date | 表示报文创建的时间 |
| Connection | 表示内部使用的TCP连接类型,keep-alive / close |
| Cache-Control | 控制http缓存的行为 |
| Transfer-Encoding | 传输报文时候的编码方式 |
| Upgrade | 要求客户端升级协议 |
请求头字段
| 请求头字段 | 作用 |
|---|---|
| Accept | 能正确接收的媒体类型 |
| Accept-Charset | 能正确接收的字符集 |
| Accept-Encoding | 能正确接收的编码格式列表。比如:gzip deflate |
| Accept-Language | 能正确接收的语言列表 |
| Host | 表示服务器的域名 |
| if-Match | 比较两端资源的ETag,只有相等的时候才能正常完成请求 |
| If-Modified-Since | 客户端记录的最后一次修改资源的时间,如果小于服务端最后一次修改的时间,则会返回200;否则返回304,去缓存中获取。 |
| if-None-Match | 客户端记录的当前资源的Etag,如果和服务端不匹配,说明有了新的修改,返回200;否则返回304 |
| User-Agent | 客户端的信息 |
| Range | 片段请求中,表示请求资源中的某一个部分 |
| Referer | 表示当前是在哪个地址上请求这个资源 |
| Cookie | 一些存在前端的信心,比如用户登录的信息。每次请求的时候都会带到后端 |
响应头字段
| 字段 | 作用 |
|---|---|
| Content-Type | 表示内容的媒体类型,text/html;charset=UTF-8 |
| Content-Encoding | 告诉客户端内容的编码格式 |
| Content-Language | 表示返回内容使用的语言 |
| Content-Length | 表示响应体的长度 |
| Content-Range | 表示返回的实体的片段范围 |
| Content-Location | 表示返回数据的备用地址 |
| Location | 表示资源重定向之后的地址 |
| Expires | 表示强缓存资源的过期时间 |
| Last-Modified | 服务端记录的资源修改的最后时间,和If-Modified-Since配合使用 |
| ETag | 服务端记录的资源标识,和if-None-Match配合使用 |
| Allow | 当前资源允许的请求方法 |
| Access-Control-Allow-Origin | 表示哪些网站可以跨域访问当前的资源,CORS |
| Access-Control-Allow-Methods | 表示允许使用的方法 |
| Access-Control-Allow-Credentials | 表示CORS请求中是否可以带Cookie |
528.HTTP/1.0、HTTP/1.1、HTTP/2和HTTP/3之间的主要区别【热度: 1,447】【网络】【出题公司: 腾讯】
关键词:HTTP各版本之间区别
下面是一个表格,展示了HTTP/1.0、HTTP/1.1、HTTP/2和HTTP/3之间的主要区别:
| 特点 | HTTP/1.0 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|---|
| 并发请求 | 不支持并发请求 | 支持有限的并发请求 | 引入多路复用(Multiplexing),支持更高级别的并发请求 | 引入QUIC协议,通过多路复用和UDP传输支持更高级别的并发请求 |
| 请求头压缩 | 不支持 | 不支持 | 引入HPACK算法对请求头进行压缩 | 引入QPACK算法对请求头进行压缩 |
| 二进制传输 | 不支持 | 不支持 | 使用二进制格式传输数据 | 使用二进制格式传输数据 |
| 流控制 | 不支持 | 不支持 | 支持流控制,可以控制每个流的数据传输速率 | 支持流控制,可以控制每个流的数据传输速率 |
| 服务器推送 | 不支持 | 不支持 | 引入服务器推送机制,服务器可以主动推送资源给客户端 | 引入服务器推送机制,服务器可以主动推送资源给客户端 |
| 连接复用 | 不支持 | 支持持久连接 | 支持多路复用,多个请求可以通过单个连接并行处理 | 支持多路复用,多个请求可以通过单个连接并行处理 |
| 安全性 | 不支持 | 引入HTTPS协议,支持加密传输 | 引入HTTPS协议,支持加密传输 | 引入HTTPS协议,支持加密传输 |
| 可靠性 | 不支持 | 不支持 | 支持头部压缩、流控制和服务器推送,提升传输的可靠性 | 引入QUIC协议,通过UDP传输提升传输的可靠性 |
| 开发复杂性 | 简单 | 对开发者较友好 | 引入了新的概念和协议,对开发者相对复杂 | 依赖QUIC协议,对开发者相对复杂 |
| 缓存机制 | 支持简单的请求响应缓存 | 引入了更强大的缓存控制机制,如ETag、Cache-Control等 | 引入了新的缓存机制,如Server Push、Priority等 | 类似HTTP/2,但通过QUIC对底层的传输进行了优化 |
| 底层协议 | 基于TCP | 基于TCP | 基于TCP或基于TLS的加密传输 | 基于QUIC(Quick UDP Internet Connections) |
| 连接管理 | 每个请求/响应都需要建立和关闭连接 | 引入了持久连接,通过keep-alive头部保持连接 | 通过单个连接并行处理多个请求/响应 | 通过QUIC的连接复用和多路复用进行处理 |
| 传输效率 | 每个请求/响应都需要耗费时间来建立和关闭连接,浪费带宽 | 连接复用有助于减少建立连接的开销,并提高传输效率 | 通过多路复用、头部压缩等机制提高传输效率 | 通过QUIC的特性如连接复用、多路复用等提高传输效率 |
| 对丢包和延迟的影响 | 对丢包和延迟的恢复较慢。一个请求阻塞可能导致后续请求也受到影响 | 对丢包和延迟的恢复较快。使用流的方式可以并行处理请求 | 对丢包和延迟的恢复较快。使用流的方式可以并行处理请求 | 对丢包和延迟的恢复较快,QUIC通过UDP传输有利于降低延迟和丢包的影响 |
| 适用场景 | 简单的Web页面和静态资源 | 大多数Web应用程序 | 复杂的Web应用程序,需要更高的传输效率 | 复杂的Web应用程序,需要更高的传输效率和减少延迟 |
530.http 常见状态码有哪些【热度: 1,410】【网络】【出题公司: 百度】
关键词:http常见状态码
HTTP(超文本传输协议)中常见的状态码包括:
1xx(信息性状态码):表示请求已被接收并正在处理。
- 100(Continue):请求已接收,客户端应继续发送请求的剩余部分。
- 101(Switching Protocols):服务器要求客户端切换协议。
2xx(成功状态码):表示请求已成功处理。
- 200(OK):请求成功。
- 201(Created):请求已成功并创建新的资源。
- 202(Accepted):请求已接受,但尚未处理完成。
- 204(No Content):服务器已成功处理请求,但无返回内容。
3xx(重定向状态码):表示需要进一步操作才能完成请求。
- 301(Moved Permanently):请求的资源已永久移动到新位置。
- 302(Found):请求的资源临时移动到不同的位置。
- 304(Not Modified):资源未被修改,可使用缓存版本。
4xx(客户端错误状态码):表示客户端发生错误。
- 400(Bad Request):无效的请求。
- 401(Unauthorized):请求需要身份验证或凭证无效。
- 403(Forbidden):服务器拒绝请求。
- 404(Not Found):请求的资源不存在。
5xx(服务器错误状态码):表示服务器发生错误。
- 500(Internal Server Error):服务器遇到了错误,无法完成请求。
- 502(Bad Gateway):服务器作为网关或代理,从上游服务器收到无效响应。
- 503(Service Unavailable):服务器无法处理请求,通常是因为过载或停机维护。
以上是常见的 HTTP 状态码,每个状态码都有特定的含义,用于指示请求的处理结果。
531.Http 状态码 301 和 302 的应用场景分别是什么【网络】【出题公司: 百度】
HTTP状态码301和302都是重定向状态码,用于将客户端请求重定向到另一个URL。
301(Moved Permanently):表示请求的资源已永久移动到新位置。服务器发送301状态码时,还会在响应头中包含一个Location字段,指示新的资源位置。客户端接收到301响应后,会自动重定向到新的URL,并且搜索引擎也会更新索引将原来的URL替换为新的URL。301常见的应用场景包括网站改版、域名更换等需要永久重定向的情况。302(Found):表示请求的资源临时移动到不同的位置。与301不同,302状态码表示请求的资源只是暂时移动,将来可能还会回到原来的位置。服务器发送302状态码时,同样会在响应头中包含一个Location字段,指示暂时移动的位置。客户端接收到302响应后,也会自动重定向到新的URL,但搜索引擎通常不会更新索引,而是继续保留原来的URL。302常见的应用场景包括临时的维护页面、流量调度等需要临时重定向的情况。
总结
- HTTP状态码301是永久重定向,表示请求的资源已永久移动到新位置,客户端会自动重定向到新的URL,搜索引擎会更新索引。
- HTTP状态码302是临时重定向,表示请求的资源暂时移动到不同的位置,客户端会自动重定向到新的URL,但搜索引擎会保留原来的索引。
- 301适用于网站改版、域名更换等需要永久重定向的情况。
- 302适用于临时的维护页面、流量调度等需要临时重定向的情况。
532.什么是 JWT【热度: 428】【网络】【出题公司: 小米】
关键词:jwt 基本概念
JWT是JSON Web Token的缩写,是一种用于在不同系统之间安全传输信息的开放标准。JWT通常用于身份验证和授权,它由三部分组成:头部(header)、载荷(payload)和签名(signature)。
头部包含了关于令牌的元数据和算法信息,通常包括令牌的类型(例如JWT)、使用的加密算法(例如HMAC SHA256或RSA)等。
载荷包含了要传输的数据,可以是用户的身份信息、权限、角色等。载荷可以自定义,但常见的标准字段有iss(令牌的签发者)、exp(令牌的过期时间)、sub(令牌的主题)等。
签名是使用头部和载荷中的数据以及秘密密钥生成的,用于验证令牌的真实性和完整性。接收方可以使用相同的密钥对收到的令牌进行验证。
JWT的优点包括可扩展性、易于使用和跨平台支持,它可以在各种语言和框架中使用。由于JWT是基于标准的JSON格式,因此它易于解析和处理。
高级开发者相关问题【共计 6 道题】
516.[性能] 常见性能指标获取方式?【热度: 954】【工程化】【出题公司: 美团】
关键词:web性能指标获取
常见性能指标获取方式
相关性能指标问题, 可以看这个文章:#515
指标所反映的用户体验 下表概述了我们的性能指标如何对应到我们的问题之上:
开始了吗?
- 首次绘制、首次内容绘制 First Paint (FP) / First Contentful Paint (FCP)
有用吗?
- 首次有效绘制、主要元素时间点 First Meaningful Paint (FMP) / Hero Element Timing
能用吗?
- 可交互时间点 Time to Interactive (TTI)
好用吗?
- 慢会话 Long Tasks (从技术上来讲应该是:没有慢会话)
页面何时开始渲染 - FP & FCP
这两个指标,我们可以通过 performance.getEntry、performance.getEntriesByName、performanceObserver 来获取。
performance.getEntries().filter(item => item.name === 'first-paint')[0]; // 获取 FP 时间
performance.getEntries().filter(item => item.name === 'first-contentful-paint')[0]; // 获取 FCP 时间
performance.getEntriesByName('first-paint'); // 获取 FP 时间
performance.getEntriesByName('first-contentful-paint'); // 获取 FCP 时间
// 也可以通过 performanceObserver 的方式获取
var observer = new PerformanceObserver(function(list, obj) {
var entries = list.getEntries();
entries.forEach(item => {
if (item.name === 'first-paint') {
...
}
if (item.name === 'first-contentful-paint') {
...
}
})
});
observer.observe({ type: 'paint' });
页面何时渲染主要内容 - FMP & SI & LCP
FMP, 是一个已经废弃的性能指标。在实践过程中,由于 FMP 对页面加载的微小差异过于敏感,经常会出现结果不一致的情况。此外,该指标的定义依赖于特定于浏览器的实现细节,这意味着它不能标准化,也不能在所有 Web 浏览器中实现。目前,官方并没有提供有效的获取 FMP 的接口,因此性能分析的时候不再使用这个指标。
SI 和 FMP 一样,官方也没有提供有效的获取接口,只能通过 lighthouse 面板来查看,不作为 Sentry 等工具做性能分析的指标。
LCP,和 FMP 类似,但只聚焦页面首次加载时最大元素的绘制时间点,计算相对简单一些。通过 performanceObserver 这个接口,我们可以获取到 LCP 指标数据。
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log('LCP candidate:', entry.startTime, entry);
}
}).observe({ type: 'largest-contentful-paint', buffered: true });
何时可以交互 - TTI & TBT
TTI, time to ineractive, 可交互时间, lighthouse 面板中的六大指标之一, 用于测量页面从开始加载到主要资源完成渲染,并能够快速、可靠地响应用户输入所需的时间, 值越小约好。 官方资料: TTI 。
和 FMP、SI 一样,官方并没有提供获取 TTI 的有效接口,只能通过 lighthouse 面板来查看。
计算方式人如下:
- 先进行 First Contentful Paint 首次内容绘制;
- 沿时间轴正向搜索时长至少为 5 秒的安静窗口,其中,安静窗口的定义为:没有长任务且不超过 2 个正在处理的网络请求;
- 沿时间轴反向搜索安静窗口之前的最后一个长任务,如果没有找到长任务,则在 FCP 步骤停止执行。
- TTI 是安静窗口之前最后一个长任务的结束时间(如果没有找到长任务,则与 FCP 值相同)。
TTI 表示的是完全可交互的时间, 每个 web 系统, 对 TTI 时间定义可能并不一定相同, 上面只是提供一个计算较为通用的 TTI 的一个方式。
交互是否有延迟 - FID & MPFID & Long Task
FID 和 MPFID 可用来衡量用户首次交互延迟的情况,Long Task 用来衡量用户在使用应用的过程中遇到的延迟、阻塞情况。
FID,first input delay, 首次输入延迟,测量从用户第一次与页面交互(例如当他们单击链接、点按按钮或使用由 JavaScript 驱动的自定义控件)直到浏览器对交互作出响应,并实际能够开始处理事件处理程序所经过的时间。官方资料: FID。 FID 指标的值越小约好。通过 performanceObserver,我们可以获取到 FID 指标数据。
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
const delay = entry.processingStart - entry.startTime;
console.log('FID candidate:', delay, entry);
}
}).observe({ type: 'first-input', buffered: true });
MPFID, Max Potential First Input Delay,最大潜在首次输入延迟,用于测量用户可能遇到的最坏情况的首次输入延迟。和 FMP 一样,这个指标已经被废弃不再使用。
Long Task,衡量用户在使用过程中遇到的交互延迟、阻塞情况。这个指标,可以告诉我们哪些任务执行耗费了 50ms 或更多时间。
new PerformanceObserver(function(list) {
var perfEntries = list.getEntries();
for (var i = 0; i < perfEntries.length; i++) {
...
}
}).observe({ type: 'longtask' });
页面视觉是否有稳定 - CLS
CLS, Cumulative Layout Shift, 累积布局偏移,用于测量整个页面生命周期内发生的所有意外布局偏移中最大一连串的布局偏移情况。官方资料: CLS。
CLS, 值越小,表示页面视觉越稳定。通过 performanceObserver,我们可以获取到 CLS 指标数据。
new PerformanceObserver(function(list) {
var perfEntries = list.getEntries();
for (var i = 0; i < perfEntries.length; i++) {
...
}
}).observe({ type: 'layout-shift', buffered: true });
522.前端模块化的演进过程【热度: 798】【工程化】【出题公司: 京东】
关键词:前端模块化演进
1 函数作为块
最开始的时候,是以函数为块来编程,因为函数有自己的作用域,相对比较独立
function add(a, b) {
// ...
}
function add1(a, b, c) {
// ...
}
这种形式中,add和add1都是定义在全局作用域中的,会造成很多问题:
- 污染全局作用域,容易造成命名冲突
- 定义在全局作用域,数据不安全
2 namespace模式
使用对象作为独立块编程
var myModule = {
a: 1,
b: 2,
add: function(m, n) {...
}
}
优点:减少了全局变量,有效解决了命名冲突
缺点:
- 没有私有变量,使用起来很繁琐
- 数据不安全,模块外面可以随意修改内部的数据
3 IIFE模式
使用立即执行函数来创建块,可以形成独立的作用域,外面无法访问,借助window对象来向外暴露接口
(function($) {
var a = 1;
var b = 2;
function add(m, n) {
...
}
$('#id').addClass('.hehe');
window.myModule = {
a: a,
b: b,
add: add
}
})()
优点:
- 减少了全局变量,解决了命名冲突
- 创建了独立的作用域,外部无法轻易修改内部数据
缺点:
如果多个模块分布在多个js文件中,那么在html文件中就需要引入多个js文件
- 会增加多个http请求,增加首屏的时候,降低用户体验
- 模块之间的引用关系很不明显,难以维护
4 CommonJS
最开始出现的模块化方案是在node.js中实现的。node中的模块化方案是根据CommonJS规范实现的。
CommonJS规定每个文件就是一个模块,以同步的方式引入其他模块
//a.js
function add(m, n) {
return m + n;
}
module.exports = { add: add }
//b.js
const { add } = require('./a.js');
console.log(add(1, 2)); // 3
这种方式是node端独有的,浏览器端如果想要使用,需要使用 Browserify 工具来解析。
5 AMD和Require.js
CommonJS模块之前是同步引入的,这在服务端是没有什么问题的,因为文件都是保存在硬盘中,读取文件的速度是非常快的,同步加载带来的阻塞基本可以忽略不计。
但是如果在浏览器中使用CommonJS的话,因为js文件是存在服务端需要请求获取,所以同步的方式加载会极大的阻塞页面,显然是不可取的。
于是诞生了AMD(Asynchronous Module Definition)规范,一种异步加载的模块方案,使用回调函数来实现。require.js实现了AMD的规范。
//定义没有依赖的模块
//a.js
define(function() {
function add(m, n) {
return m + n;
}
return { add: add }
})
//定义有依赖的模块
//b.js
define(['a'], function(a) {
const sum = a.add(1, 2);
return { sum: sum }
})
//引用模块
require(['b'], function(b) {
console.log(b.sum); //3
})
由上面代码分析Require.js的特点
- 依赖模块的代码都是放在回调函数中,等待模块都加载完成才执行这个回调函数,执行顺序可以保证
- 内部加载其他模块的时候,使用的是动态添加script标签的方式来实现动态加载
- 内部需要缓存模块暴露出来的接口,避免多次执行
AMD推崇的是依赖前置,提前执行。
从上面代码可以看出,**在声明一个模块的时候,会在第一时间就将其依赖模块的内部代码执行完毕。而不是在真正使用的地方再去执行。**因此会带来一些资源浪费
define(['a', 'b'], function(a, b) {
let sum = a.add(1, 2);
if (false) {
sum = b.add(1, 2); //b模块是没有被使用的,应该是不需要执行模块内部代码的
}
return sum;
})
6 CMD和Sea.js
由于require.js自身的一些问题存在,所以后来在国内(玉伯)诞生了CMD(Common Module Definition)和Sea.js。
CMD结合了CommonJS和AMD的特点,也是一种异步模块的方案,提倡就近依赖,延迟执行。
需要用到某个模块的时候,才用require引入,模块内部的代码也是在被引入的时候才会执行,声明的时候并没有执行。
语法设计上比较像CommonJS
//定义模块 math.js
define(function(require, exports, module) {
var a = require('./a.js'); // 引入模块
function add(m, n) {
return m + n;
}
module.exports = {
add: add
}
});
//加载模块
seajs.use(['math.js'], function(math) {
var sum = math.add(1, 2);
})
看上面的代码可能会有疑问,模块是异步加载的,但是使用的时候require是同步使用的,没有回调函数,如何能够保证执行的顺序呢?这就不得不提sea.js中的静态依赖分析机制了。
6.1 Sea.js中的静态依赖分析机制
Sea.js中模块加载的入口方法是use()方法,执行这个方法会开始加载所有的依赖模块。然后sea.js中是就近依赖的,它是如何获取依赖模块的呢?
在define的方法中,如果传入的参数factory是一个函数,内部会执行函数的toString方法,转化成字符串,然后通过正则表达式分析字符串,获取require方法中的参数,通过路径分析去加载依赖的模块 。以此链式分析下去,边分析边加载模块,等待所有的依赖都加载完成之后,才开始调用use的回调函数,正式执行模块内代码。
所以在require方法执行之前,对应的模块已经加载完成了,所以可以直接传入参数,执行模块函数体。
6.2 Sea.js的特点
- 就近依赖,延时执行
- 内部拥有静态依赖分析机制,保证require之前,模块已经加载完毕,但是函数还没有执行
- 也是一种异步的模块化方案
- 内部也有缓存机制,缓存模块暴露的接口
- 内部加载模块的时候,和require.js一样,也是通过动态增加script标签来完成的
7 ES Module
ES6开始,在语法标准上实现了模块化功能。简称ES Module
ES Module是一种静态依赖的模块化方案,模块与模块之间的依赖关系是在编译期完成连接的。
前面所说的三种方案都是动态模块化方案,依赖模块都是动态引入的,而且模块都是一个对象。而ES Module中,模块不是一个对象,模块与模块之间也不是动态引入的,而且编译期间静态引入的,所以无法实现条件加载
//a.js
function add(m, n) {
return m + n;
}
export { add };
// b.js
import { add } from './a.js';
console.log(add(1, 2)); //3
参考文档
523.webpack tree-shaking 在什么情况下会失效?【热度: 171】【工程化】【出题公司: 阿里巴巴】
关键词:tree shaking 失效
在以下情况下,webpack 的 tree-shaking 可能会失效:
- 使用了
sideEffects属性:在 webpack 的配置文件中,如果设置了sideEffects: false,则 webpack 会假设所有模块都没有副作用,因此不会进行 tree-shaking。这通常用于避免某些模块被误标记为无用代码而被删除。 - 动态导入:如果你使用了动态导入(例如使用了
import()或require.ensure()),webpack 无法静态分析模块的导入和导出,因此无法进行 tree-shaking。 - 使用了
commonjs模块语法:如果你的代码中使用了commonjs模块语法(例如使用了require()或module.exports),webpack 无法进行静态分析,因此无法进行 tree-shaking。 - 未使用 ES6 模块语法:tree-shaking 只能对 ES6 模块语法进行优化,如果你的代码中没有使用 ES6 模块语法,webpack 将无法进行 tree-shaking。
- 模块被动态引用或条件引用:如果模块的引用方式是动态的(例如在循环或条件语句中引用),或者通过字符串拼接来引用模块,webpack 无法确定哪些模块会被引用,因此无法进行 tree-shaking。
- 使用了副作用的代码:如果你的代码中包含有副作用的代码(例如在模块的顶级作用域中执行了一些操作),webpack 无法确定哪些代码是无用的,因此无法进行 tree-shaking。
需要注意的是,即使 tree-shaking 可能会失效,webpack 仍然会进行其他优化,例如代码压缩和代码分割等。同时,你可以通过设置 mode 为 production,来启用 webpack 的优化功能,包括 tree-shaking。
524.[Vue] vue3 的 diff 算法是什么,简单介绍一下【热度: 693】【web框架】【出题公司: 腾讯】
关键词:vue3 diff 算法、逐层比较和双端比较
Vue3的diff算法是一种用于比较虚拟DOM树之间差异的算法。它用于确定需要更新的部分,以便最小化对实际DOM的操作,从而提高性能。
Vue3的diff算法采用了一种称为"逐层比较"的策略,即从根节点开始逐层比较虚拟DOM树的节点。具体的比较过程如下:
- 对比两棵虚拟DOM树的根节点,判断它们是否相同。如果不相同,则直接替换整个根节点及其子节点,无需进一步比较。
- 如果根节点相同,则对比它们的子节点。这里采用了一种称为"双端比较"的策略,即同时从两棵树的头部和尾部开始比较子节点。
- 从头部开始,依次对比两棵树的相同位置的子节点。如果两个子节点相同,则继续比较它们的子节点。
- 如果两个子节点不同,根据一些启发式规则(如节点类型、key值等),判断是否需要替换、删除或插入子节点。
- 继续比较下一个位置的子节点,直到两棵树的所有子节点都被比较完。
通过逐层比较和双端比较的策略,Vue3的diff算法能够高效地找到虚拟DOM树之间的差异,并只对需要更新的部分进行操作,从而减少了对实际DOM的操作次数,提高了性能。
值得注意的是,Vue3还引入了一种称为"静态标记"的优化策略,用于在编译阶段将一些静态节点标记出来,从而在diff算法中更快地跳过这些静态节点的比较,进一步提升性能。这一优化策略在处理大型列表、静态内容等场景下特别有效。
529.http2 多路复用是什么, 原理是什么【热度: 353】【网络】【出题公司: 腾讯】
关键词:http2多路复用、http2多路复用原理、http2帧和流、http2流的优先级、http2头部压缩
多路复用是指在HTTP/2中,多个请求/响应可以同时在同一个TCP连接上进行传输和处理的机制。
在HTTP/1.1中,每个请求都需要建立一个独立的TCP连接,导致连接的建立和关闭开销很大。而在HTTP/2中,多个请求可以通过同一个TCP连接同时进行,避免了建立和关闭连接的开销。
多路复用的实现原理主要包括以下几个方面:
- 帧和流 :在HTTP/2中,通信的最小单位是帧(frames),每个帧包含了一个特定类型的数据,例如请求头、响应头、请求体、响应体等。帧属于一个或多个流(stream),每个流都有唯一的标识符。多个流可以同时在同一个TCP连接上进行传输。
- 流的优先级:在HTTP/2中,每个流都可以设置优先级,用于指定处理请求的顺序。服务器可以根据流的优先级来决定响应的优先级,从而更好地利用带宽资源。
- 头部压缩 :为了减少头部信息的传输开销,HTTP/2使用了一种称为HPACK的压缩算法。HPACK对头部信息进行压缩,并在通信双方之间维护一个共享的头部表,用于存储已经发送或接收过的头部信息。这样就可以减少重复的头部信息传输,提高传输效率。
通过上述机制,HTTP/2实现了多路复用。多个请求/响应可以同时在同一个TCP连接上进行传输,提高了传输效率,减少了连接建立和关闭的开销。
533.单点登录是什么, 具体流程是什么【热度: 1,168】【web应用场景】【出题公司: 小米】
关键词:单点登录流程
SSO 一般都需要一个独立的认证中心(passport),子系统的登录均得通过 passport,子系统本身将不参与登录操作,当一个系统成功登录以后,passport 将会颁发一个令牌给各个子系统,子系统可以拿着令牌会获取各自的受保护资源,为了减少频繁认证,各个子系统在被 passport 授权以后,会建立一个局部会话,在一定时间内可以无需再次向 passport 发起认证。
具体流程是:
- 用户访问系统 1 的受保护资源,系统 1 发现用户未登录,跳转至 sso 认证中心,并将自己的地址作为参数
- sso 认证中心发现用户未登录,将用户引导至登录页面
- 用户输入用户名密码提交登录申请
- sso 认证中心校验用户信息,创建用户与 sso 认证中心之间的会话,称为全局会话,同时创建授权令牌
- sso 认证中心带着令牌跳转会最初的请求地址(系统 1)
- 系统 1 拿到令牌,去 sso 认证中心校验令牌是否有效
- sso 认证中心校验令牌,返回有效,注册系统 1
- 系统 1 使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
- 用户访问系统 2 的受保护资源
- 系统 2 发现用户未登录,跳转至 sso 认证中心,并将自己的地址作为参数
- sso 认证中心发现用户已登录,跳转回系统 2 的地址,并附上令牌
- 系统 2 拿到令牌,去 sso 认证中心校验令牌是否有效
- sso 认证中心校验令牌,返回有效,注册系统 2
- 系统 2 使用该令牌创建与用户的局部会话,返回受保护资源