vue前端面试题——记录一次面试当中遇到的题(7)
目录
一、前端基础
1.CSS伪类和伪元素有哪些,它们的区别和实际应用
2.BFC的布局规则,实现原理,可以解决的问题
3.src和href的区别
4.iframe有哪些优点和缺点?
iframe 的优点
iframe 的缺点
5.对HTML语义化的理解,写出常见的语义化标签
一、对 HTML 语义化的理解
二、常见的语义化标签
面试回答技巧
6.position的属性有哪些,区别是什么
7.JavaScript有哪些数据类型
核心区别:原始类型 vs. 引用类型
一、原始类型(基本类型)
二、引用类型(对象类型)
8.Vue的性能优化有哪些?
9.Vue中的Key的作用是什么?
10.GET和POST请求的区别
核心区别总结表
详细解析
面试回答技巧
11.HTTP常见状态码及含义
🟢 2xx 成功
🟡 3xx 重定向
🔴 4xx 客户端错误
🟠 5xx 服务器错误
面试回答技巧
12.promise 有哪些状态
二、代码题
1.交换a,b的值,不能用临时变量
方法一:算术运算(加减法)
方法二:算术运算(乘除法)
方法三:位运算(异或操作)⭐ - 最优雅
方法四:ES6 解构赋值(严格来说用了临时变量,但语法简洁)
各种方法的比较
2.有个数组a=[3,6,9,10,15,......],其中包含了很多正整数,请编程找出其中最大的数字。
方法一:使用 Math.max() 和扩展运算符(推荐)
方法二:使用 Math.max() 和 apply()
方法三:使用 reduce() 方法
方法四:使用 for 循环(传统方法)
方法五:使用 for...of 循环
3.下列代码输出的结果是?
4.下面代码输出的结果是?
5.对下列数组去重
方法一:使用 Set(ES6,最简洁)⭐
方法二:使用 filter() + indexOf()
方法三:使用 reduce()
方法四:使用 for 循环 + includes()
方法五:使用 Map(ES6)
性能比较和选择建议
6.不使用sort方法对下列数组排序(从小到大)
方法一:冒泡排序
方法二:选择排序
方法三:插入排序
方法四:快速排序(递归)
方法五:使用 Math.min() 和循环
7.在字符串的原型链上添加一个方法(reverse),实现字符串反转;
方法一:使用数组的 reverse() 方法(推荐)
方法二:使用扩展运算符
方法三:使用 for 循环(传统方法)
方法四:使用递归
方法五:使用 reduce()
一、前端基础
1.CSS伪类和伪元素有哪些,它们的区别和实际应用
根本区别:
-
伪类:选择元素的特定状态(如:hover, :focus)
-
伪元素:选择元素的特定部分(如::before, ::after)
语法区别:
-
伪类:单冒号
:
-
伪元素:双冒号
::
(CSS3规范)
功能区别:
-
伪类:描述元素的状态,不创建新内容
-
伪元素:可以创建虚拟元素和内容
常用分类:
伪类:
-
状态伪类::hover, :active, :focus, :visited
-
结构伪类::nth-child(), :first-child, :last-child
-
表单伪类::checked, :disabled, :valid
-
其他伪类::not(), :target, :root
伪元素:
-
内容生成:::before, ::after
-
文本选择:::first-letter, ::first-line, ::selection
-
其他:::placeholder, ::marker
实际应用原则:
-
交互反馈:使用:hover, :focus提供视觉反馈
-
内容装饰:使用::before, ::after添加装饰性内容
-
文本美化:使用::first-letter, ::first-line增强排版
-
表单增强:使用:checked, :valid美化表单元素
-
布局辅助:使用:empty, :first-child处理边界情况
2.BFC的布局规则,实现原理,可以解决的问题
BFC的布局规则:
-
垂直排列:内部元素在垂直方向依次排列
-
margin重叠:同一BFC内相邻元素的垂直margin会重叠
-
左边缘接触:元素左边缘与包含块左边缘接触
-
不与浮动重叠:BFC区域不会与浮动元素重叠
-
独立容器:BFC内外互不影响
-
包含浮动:计算高度时包含浮动元素
创建BFC的方法:
-
根元素:
<html>
-
浮动:
float: left/right
-
定位:
position: absolute/fixed
-
display:
inline-block
、table-cell
、flow-root
等 -
overflow:
overflow: hidden/auto/scroll
-
其他:
contain: layout
、多列布局等
BFC解决的问题:
-
margin重叠:通过创建BFC隔离margin
-
高度塌陷:清除浮动,父元素包含浮动子元素
-
浮动覆盖:阻止元素被浮动元素覆盖
-
自适应布局:实现两栏自适应布局
-
文字环绕:控制文字与浮动元素的关系
最佳实践:
-
优先使用
display: flow-root
(无副作用) -
兼容性考虑使用
overflow: hidden
-
只在需要时创建BFC,避免过度使用
-
理解不同创建方法的副作用
现代CSS的替代方案:
-
Flexbox:更适合一维布局
-
Grid:更适合二维布局
-
display: flow-root
:专门用于创建BFC
3.src和href的区别
语义区别:
-
src
:源资源,用于替换当前元素的内容 -
href
:超文本引用,用于建立关联关系
加载行为区别:
-
src
:同步加载,会阻塞文档解析(如script) -
href
:异步加载,通常不阻塞文档解析(如CSS)
使用场景区别:
-
src 用于:
-
嵌入可执行代码:
<script src>
-
嵌入媒体资源:
<img src>
、<video src>
-
嵌入框架内容:
<iframe src>
-
-
href 用于:
-
链接样式表:
<link href rel="stylesheet">
-
超链接导航:
<a href>
-
资源预加载:
<link href rel="preload">
-
浏览器处理区别:
-
src
:浏览器会下载并处理资源,替换当前元素 -
href
:浏览器建立关联,在需要时加载资源
性能影响:
-
src
:可能阻塞渲染,需要优化(async/defer) -
href
:通常不阻塞,但CSS会阻塞渲染
现代开发最佳实践:
-
使用
async
/defer
优化 script src -
使用
loading="lazy"
优化图片加载 -
使用
preload
/prefetch
优化资源加载 -
合理使用模块化加载(type="module")
4.iframe有哪些优点和缺点?
iframe 的优点
-
隔离性(沙箱机制)
-
安全隔离:iframe 拥有独立的浏览上下文,这意味着它的 JavaScript、CSS 和 DOM 与父页面是隔离的。这可以有效防止恶意脚本攻击父页面(例如,使用
sandbox
属性可以进一步限制其能力)。 -
样式隔离:iframe 内的样式不会影响父页面,父页面的样式也不会影响 iframe(除非特别指定)。这对于嵌入第三方组件(如在线编辑器、地图)非常有用,可以避免 CSS 污染和冲突。
-
-
嵌入第三方内容
-
跨域资源嵌入:这是 iframe 最核心的用途之一。你可以嵌入来自其他域名的页面,例如 YouTube 视频、Google 地图、在线支付页面、广告等。这是实现跨域数据展示和功能集成的常用手段。
-
历史记录与导航:每个 iframe 都有自己的会话历史记录和
window.location
对象。这在嵌入单页面应用或需要独立导航逻辑的场景下很有用。
-
-
并行加载
-
iframe 可以和父页面并行加载,浏览器会为它分配独立的连接和资源加载线程,在某些情况下可能不会阻塞父页面的渲染。
-
-
保活机制
-
在一些复杂的前端应用中,可以用 iframe 来“保活”父页面的某些状态(例如登录状态),防止因页面跳转或刷新而丢失。
-
-
文件上传
-
在过去,通过 iframe 可以实现“无刷新”的文件上传体验(配合
form
的target
属性),虽然现在已被FormData
和fetch/XMLHttpRequest
取代,但在一些老项目中仍然可见。
-
iframe 的缺点
-
性能开销大
-
资源消耗:每个 iframe 都是一个完整的文档环境,需要创建额外的 DOM 树、样式上下文、JavaScript 环境等,会消耗更多的内存和 CPU 资源。
-
阻塞父页面加载:虽然可以并行,但 iframe 的
onload
事件会阻塞父窗口的onload
事件。如果 iframe 加载缓慢,会导致整个页面被认为“未加载完成”。 -
连接数限制:浏览器对同一域名的并发 HTTP 请求数有限制。iframe 内的资源请求会占用这些连接,可能影响父页面其他重要资源的加载。
-
-
SEO(搜索引擎优化)问题
-
大多数搜索引擎爬虫对 iframe 内的内容权重较低,甚至可能忽略。嵌入在 iframe 中的重要内容很难被搜索引擎索引,不利于 SEO。
-
-
可访问性差
-
屏幕阅读器等辅助技术可能难以理解和导航 iframe 内的内容,尤其是动态加载的 iframe。
-
-
跨域通信复杂
-
虽然 iframe 可以嵌入跨域内容,但父子页面之间的 JavaScript 直接通信受到同源策略的严格限制。必须使用
postMessage
API 进行通信,这增加了开发的复杂性。
-
-
布局和响应式设计困难
-
iframe 的高度需要手动计算和调整(例如通过
postMessage
通信获取内容高度),难以实现完美的自适应。在移动端,处理 iframe 的视口和缩放也是一个挑战。
-
-
安全性风险
-
点击劫持:如果没有适当的防护(如使用
X-Frame-Options
或Content-Security-Policy
响应头),你的网站可能被恶意网站通过 iframe 嵌入,并被用于欺骗用户进行操作(点击劫持攻击)。 -
脚本风险:如果嵌入的第三方内容被黑,可能会对用户造成影响(尽管有沙箱隔离,但仍可能进行钓鱼攻击)。
-
-
管理复杂度高
-
页面中存在多个 iframe 时,状态管理、事件通信、生命周期管理都会变得非常复杂。
-
5.对HTML语义化的理解,写出常见的语义化标签
一、对 HTML 语义化的理解
核心思想: 用正确的 HTML 标签来表达正确的内容结构,让标签本身具有含义。
通俗解释: 不仅仅是为了让页面展示出来,更是为了让机器(如搜索引擎、屏幕阅读器等)能够“读懂”你的页面结构。看到 <nav>
就知道这是导航,看到 <article>
就知道这是一篇独立的文章内容。
为什么要进行语义化?(优点)
-
对 SEO(搜索引擎优化)友好
-
搜索引擎的爬虫依赖于标签来确定内容的上下文和权重。使用
<h1>
、<article>
、<strong>
等标签可以帮助爬虫更好地理解页面结构,抓取重要内容,从而提升网页在搜索结果中的排名。
-
-
提升可访问性
-
屏幕阅读器等辅助技术可以为视障用户朗读网页。当它们遇到
<nav>
时,会告知用户“导航区域”;遇到<button>
时,会明确这是一个可点击的按钮。这极大地提升了残障用户的访问体验。
-
-
便于团队开发和维护
-
语义化的代码结构清晰,像一份文档大纲。新成员接手项目或自己日后维护时,能快速理解页面结构,降低理解和维护成本。
-
例子: 看到
<div id="nav">
和<nav>
,后者显然更直观。
-
-
页面结构更清晰
-
在没有 CSS 的情况下,语义化良好的页面也能以一种文档格式清晰地显示出内容结构。
-
-
有利于跨平台数据交换
-
移动设备、智能设备等可以更具语义地渲染网页,提供更好的体验。
-
二、常见的语义化标签
可以将这些标签分为几大类来记忆:
1. 布局结构类(替代无语义的 <div>
)
这些标签构成了页面的宏观骨架。
标签 | 描述 | 说明 |
---|---|---|
<header> | 页眉 | 通常包含网站的 Logo、标题、导航等。一个页面可以有多个,例如文章页面的文章头部也可以用它。 |
<nav> | 导航 | 定义页面的主要导航链接区域。 |
<main> | 主内容 | 定义文档的唯一主要内容。一个页面只应有一个 <main> 。 |
<article> | 文章/独立内容 | 定义一块独立、可重复使用的内容,如博客文章、论坛帖子、新闻稿件、评论等。 |
<section> | 章节/区域 | 定义文档中的一个主题内容分组,通常会有自己的标题。可以理解为内容的分段。 |
<aside> | 侧边栏/附属信息 | 定义与主内容间接相关的部分,如侧边栏、引用、广告、文章摘要等。 |
<footer> | 页脚 | 通常包含版权信息、联系方式、相关链接等。和 <header> 一样,一个页面可以有多个。 |
<figure> | 独立流内容 | 表示一段独立的内容,通常与 <figcaption> 配合使用,如图片、代码、图表等。 |
<figcaption> | 图注 | 为 <figure> 元素定义标题。 |
2. 文本内容类(替代无语义的 <span>
)
这些标签用于标记行内文本的语义。
标签 | 描述 | 说明 |
---|---|---|
<h1> - <h6> | 标题 | 定义标题级别,<h1> 最高,<h6> 最低。应根据层次结构使用,而非仅仅为了加粗或改变字体大小。 |
<p> | 段落 | 定义段落。 |
<ul> / <ol> / <li> | 列表 | 定义无序列表、有序列表和列表项。 |
<blockquote> | 块引用 | 定义一段长引用。 |
<q> | 短引用 | 定义一段短的行内引用。 |
<strong> | 重要文本 | 表示内容重要性,通常呈现为粗体。(有语义) |
<em> | 强调文本 | 表示内容需要强调,通常呈现为斜体。(有语义) |
<cite> | 引用来源 | 表示对某个作品(如书籍、文章)的引用。 |
<time> | 时间/日期 | 定义一个人类可读的日期或时间。 |
<mark> | 标记/高亮 | 表示由于相关性而需要突出显示或标记的文本。 |
<code> | 代码片段 | 表示一小段计算机代码。 |
<pre> | 预格式化文本 | 保留文本中的空格和换行,常用于显示代码块。 |
面试回答技巧
问题: “谈谈你对 HTML 语义化的理解。”
回答结构建议:
-
下定义: “HTML 语义化指的是在编写 HTML 时,选择最恰当的、具有明确含义的标签来构建页面结构,让标签本身就能传达内容的信息。”
-
讲目的/优点: “这么做的目的主要有三点:第一,对 SEO 友好,帮助搜索引擎理解页面内容;第二,提升可访问性,方便屏幕阅读器等辅助工具解析;第三,代码结构清晰,易于开发和维护。”
-
举例子: “比如,我们不应该用一堆
<div>
和<span>
来搭建整个页面,而应该使用<header>
、<nav>
、<main>
、<article>
这样的标签来划分结构。对于文本强调,应该用<strong>
和<em>
而不是<b>
和<i>
,因为后者没有语义,只是表示样式。” -
做总结: “总之,语义化是编写高质量、可访问、易维护的 HTML 代码的基础。”
6.position的属性有哪些,区别是什么
CSS 的 position
属性用于指定一个元素在文档中的定位方式
总结表格
属性值 | 定位基准点 | 是否脱离文档流 | 特点与应用场景 |
---|---|---|---|
static | 正常文档流位置 | 否 | 默认值,无法使用偏移属性。 |
relative | 自身原位置 | 否(保留原空间) | 1. 微调元素位置。 2. 作为 absolute 的定位父级。 |
absolute | 最近的非static祖先 | 是(不占空间) | 1. 精确布局,创建浮动层。 2. 需要父级有 relative 等定位。 |
fixed | 浏览器视口 | 是(不占空间) | 固定在屏幕某个位置,不随滚动移动。用于固定栏、弹窗。 |
sticky | 视口(达到阈值后) | 否(但会浮动) | 滚动时达到阈值后变为固定定位。用于吸顶、吸底效果。 |
7.JavaScript有哪些数据类型
JavaScript的数据类型可以分为两大类:原始类型(基本类型) 和引用类型(对象类型)。
核心区别:原始类型 vs. 引用类型
特征 | 原始类型 | 引用类型 |
---|---|---|
存储方式 | 值直接存储在栈中 | 对象本身存储在堆中,变量在栈中存储的是内存地址(引用) |
可变性 | 不可变。修改一个变量只会创建一个新值,不会改变原始值。 | 可变。可以修改对象的属性,而引用地址不变。 |
比较方式 | 按值比较。比较两个变量时,比较的是它们的实际值。 | 按引用比较。比较两个变量时,比较的是它们的内存地址,而不是对象的内容。 |
赋值与拷贝 | 赋值是值的拷贝。创建一个新的独立副本。 | 赋值是引用的拷贝。两个变量指向同一个对象。 |
一、原始类型(基本类型)
原始类型的值直接存储在变量访问的位置(栈内存),它们是不可变的。有7种原始类型:
-
number
(数字)-
用于整数和浮点数。
-
还包括一些特殊值:
Infinity
(无穷大)、-Infinity
(负无穷大)和NaN
(不是一个数字)。 -
例子:
42
,3.14
,NaN
,Infinity
-
-
string
(字符串)-
用于表示文本数据。可以使用单引号
''
、双引号""
或反引号``
(模板字符串)创建。 -
例子:
"Hello"
,'World'
,`My name is ${name}`
-
-
boolean
(布尔值)-
只有两个值:
true
和false
。 -
例子:
true
,false
-
-
undefined
(未定义)-
表示一个变量已被声明但尚未被赋值。
-
函数没有明确返回值时,默认返回
undefined
。 -
例子:
let a; console.log(a); // undefined
-
-
null
(空值)-
表示一个“空”或“不存在”的值。它是一个特殊的关键字。
-
注意:
typeof null
返回"object"
,这是JavaScript的一个历史悠久的bug,但本身是原始值。
-
-
symbol
(符号,ES6新增)-
表示唯一的、不可变的值,主要用于对象的唯一属性名,以避免属性名冲突。
-
例子:
let id = Symbol("id");
-
-
bigint
(大整数,ES2020新增)-
用于表示任意长度的整数。通过在整数末尾加
n
来创建。 -
用于解决
number
类型无法安全地表示大于2^53 - 1
的整数的问题。 -
例子:
const bigNumber = 123456789012345678901234567890n;
-
二、引用类型(对象类型)
引用类型的值是对象,存储在堆内存中。变量中存储的实际上是指向该对象内存地址的指针(引用)。
-
object
(对象)-
用于存储各种键值对和更复杂的实体。
-
可以使用花括号
{}
创建。 -
例子:
javascript
let person = {name: "Alice",age: 30 };
-
-
array
(数组)-
用于存储有序的数据集合。它是一种特殊的对象,索引是属性名。
-
可以使用方括号
[]
创建。 -
注意:
typeof []
返回"object"
。可以使用Array.isArray([])
来专门检测。 -
例子:
let fruits = ["apple", "banana"];
-
-
function
(函数)-
函数也是对象,是一种“可调用对象”。
-
注意:
typeof function() {}
返回"function"
,但它本质上属于对象类型。 -
例子:
javascript
function greet(name) {return `Hello, ${name}!`; }
-
-
其他内置对象
-
Date
:日期和时间 -
RegExp
:正则表达式 -
Map
,Set
,WeakMap
,WeakSet
(ES6新增) -
Error
:错误对象 -
等等。
-
8.Vue的性能优化有哪些?
编码优化:
- 不要把所有数据都放在data中
- v-for时给每个元素绑定事件用事件代替
- keep-alive缓存组件
- 尽可能拆分组件,提高复用性、维护性
- key值要保证唯一
- 合理使用路由懒加载、异步组件
- 数据持久化存储的使用尽量用防抖、节流优化
加载优化
- 按需加载
- 内容懒加载
- 图片懒加载
用户体验
- 骨架屏
SEO优化
- 预渲染
- 服务端渲染ssr
打包优化
- CDN形式加载第三方模块
- 多线程打包
- 抽离公共文件
缓存和压缩
- 客户端缓存、服务端缓存
- 服务端Gzip压缩
9.Vue中的Key的作用是什么?
key
的主要作用是给 Vue 的虚拟 DOM 节点提供一个唯一的身份标识,以便它能够高效、准确地跟踪每个节点的身份,从而重用和重新排序现有元素,而不是随意地销毁和创建它们。
10.GET和POST请求的区别
核心区别总结表
特性 | GET | POST |
---|---|---|
语义/用途 | 获取数据(幂等、安全) | 提交/创建数据(非幂等、不安全) |
参数位置 | URL 的查询字符串 | 请求正文 |
数据长度限制 | 有(因浏览器和服务器对 URL 长度限制) | 理论上无限制 |
安全性 | 参数在 URL 中可见,不安全 | 参数在正文中,相对安全,但仍需加密 |
缓存 | 会被浏览器主动缓存 | 默认不会缓存 |
幂等性 | 幂等(多次执行效果相同) | 非幂等(多次执行可能产生不同结果) |
后退/刷新 | 无害 | 浏览器会提示重新提交 |
可见性 | 参数直接暴露在 URL 中 | 参数对用户不可见(但在开发者工具中可见) |
书签/分享 | 可被收藏为书签或分享 | 不可 |
详细解析
1. 语义和用途(最根本的区别)
-
GET:用于请求资源,强调“读”的操作。它不应该对服务器上的数据产生任何副作用(如修改、删除)。它是安全且幂等的。
-
安全:意味着操作不会修改服务器数据。
-
幂等:意味着执行一次和执行 N 次,对资源的状态是相同的。就像你刷新页面,多次 GET 同一个 URL,得到的数据应该是一样的。
-
场景:获取网页、搜索、查询商品列表。
-
-
POST:用于提交数据,请求服务器处理一个实体(如创建新资源)。它通常会对服务器数据产生副作用。
-
不安全:操作会修改服务器数据。
-
非幂等:多次提交相同的数据可能会产生不同的结果(例如,重复提交订单会创建多个订单)。
-
场景:用户登录、提交表单、上传文件、支付。
-
2. 参数位置和长度限制
-
GET:
-
参数通过 URL 的查询字符串传递,格式为
?key1=value1&key2=value2
。 -
由于 URL 长度有限制(不同浏览器和服务器限制不同,通常在 2KB 到 8KB 之间),所以 GET 请求传递的数据量较小。
-
-
POST:
-
参数放在 HTTP 请求的正文中。
-
数据长度理论上无限制,因为请求体的大小可以由服务器配置。适合传输大量数据,如文件上传、长文本等。
-
需要设置
Content-Type
请求头来指明正文的格式,如application/x-www-form-urlencoded
(表单格式)、multipart/form-data
(文件上传)、application/json
(JSON 数据)。
-
3. 安全性与可见性
-
GET:非常不安全。所有参数都明文展示在 URL 中,会出现在:
-
浏览器地址栏
-
浏览器历史记录
-
服务器日志
-
Referer 头(如果从该页面跳转)
-
绝对不能用 GET 请求传输密码等敏感信息!
-
-
POST:相对安全。参数在请求体中,不会直接暴露在 URL 中。但这不等于加密!数据在传输过程中仍然是明文的,除非使用 HTTPS。在浏览器开发者工具的 Network 面板中依然可以查看。
4. 缓存、书签与历史记录
-
GET:
-
可以被浏览器、代理服务器缓存。
-
可以收藏为书签。
-
后退、刷新操作是无害的,浏览器会直接从缓存加载或重新发起一个相同的 GET 请求。
-
-
POST:
-
默认不会被缓存。
-
无法收藏为书签(因为书签只保存 URL,不保存请求体)。
-
后退或刷新时,浏览器会提示“确认重新提交表单”,因为可能会重复执行非幂等的操作。
-
常见误区与深度辨析
误区一:GET 只能获取数据,POST 只能创建数据?
不对。 从技术上讲,你完全可以用 GET 请求通过 URL 参数去“删除”一条数据,也可以用 POST 请求去“查询”一个复杂条件的数据(例如,查询接口要求传入一个巨大的 JSON 对象)。
但是,这是违反 HTTP 协议语义的糟糕实践。一个好的 RESTful API 设计会严格遵守这些语义:
-
GET /users
- 获取用户列表 -
POST /users
- 创建一个新用户 -
GET /users/1
- 获取 ID 为 1 的用户 -
PUT /users/1
- 更新 ID 为 1 的用户 -
DELETE /users/1
- 删除 ID 为 1 的用户
误区二:POST 比 GET “更安全”?
不完全对。 如上所述,POST 只是“眼不见为净”,数据没有直接暴露在 URL 中。但如果不用 HTTPS,两者在网络传输中都是裸奔的,都能被中间人窃取。真正的安全必须依靠 HTTPS。
误区三:GET 产生一个 TCP 数据包,POST 产生两个?
这是一个流传很广但不完全准确的说法。其逻辑是:GET 请求的 Header 和 Data 可以一起发送,而 POST 需要先发 Header,服务器返回 100 Continue 后再发 Data。
事实是:
-
现代浏览器对 GET 请求,如果 URL 和 Header 长度超过一个 TCP 包的容量,也会分成多个包。
-
对于 POST 请求,大多数浏览器(如 Firefox)会直接将 Header 和 Body 一起发送,而不是分两步。
所以,不能简单地用数据包数量来区分它们。
面试回答技巧
问题: “说说 GET 和 POST 请求的区别。”
回答结构建议:
-
总起:“GET 和 POST 是 HTTP 协议中最基础的两种请求方法,它们的核心区别在于语义不同。”
-
阐述核心区别:
-
“GET 的语义是获取数据,它是安全和幂等的,通常用于查询操作。它的参数会附加在 URL 的查询字符串中,所以有长度限制,且不安全。”
-
“POST 的语义是提交数据,它是非安全和非幂等的,通常用于创建或更新资源。它的参数放在请求体中,没有长度限制,相对安全一些。”
-
-
补充其他重要区别:“除此之外,它们在缓存行为上也有很大不同:GET 请求会被浏览器主动缓存,可以收藏为书签;而 POST 默认不会。另外,在浏览器中后退或刷新时,GET 是无害的,而 POST 会提示重新提交。”
-
总结与最佳实践:“因此,在实际开发中,我们应该遵循 HTTP 协议的语义来使用它们,而不是混用。并且要记住,无论是 GET 还是 POST,传输敏感信息时都必须使用 HTTPS 来保证真正的安全。”
11.HTTP常见状态码及含义
🟢 2xx 成功
状态码 | 含义 | 场景 |
---|---|---|
200 OK | 请求成功 | 这是最常見的成功状态。GET 请求成功获取资源,POST 请求成功提交数据后返回此状态。 |
201 Created | 已创建 | 请求成功,并且服务器创建了新的资源(例如,通过 POST 或 PUT 请求创建了新用户)。通常配合 Location header 返回新资源的地址。 |
204 No Content | 无内容 | 服务器成功处理了请求,但不需要返回任何实体内容。(响应体为空) |
206 Partial Content | 部分内容 | 客户端进行了范围请求(如断点续传),服务器成功返回了部分资源。 |
🟡 3xx 重定向
状态码 | 含义 | 场景与区别 |
---|---|---|
301 Moved Permanently | 永久重定向 | 请求的资源已被永久移动到新位置。浏览器和搜索引擎会缓存这个重定向,下次直接访问新地址。 |
302 Found | 临时重定向 | 请求的资源临时从不同的 URI 响应。浏览器不会缓存,下次仍访问原地址。 这是最常见的临时重定向。 |
304 Not Modified | 未修改 | 用于缓存控制。当客户端发送带条件的 GET 请求(如 If-Modified-Since )时,如果资源未改变,服务器会返回 304,告诉客户端直接使用本地缓存。(重要性能优化) |
面试常问:301 和 302 的区别?
核心区别:缓存和行为。
301 是永久搬家,告诉浏览器和搜索引擎:“以后请直接去新地址找我。” 会更新书签和搜索引擎索引。
302 是临时出差,告诉浏览器:“这次请去另一个地址拿东西,但下次来原地址找我。” 不会更新书签和索引。
🔴 4xx 客户端错误
状态码 | 含义 | 场景与排查方向 |
---|---|---|
400 Bad Request | 错误请求 | 服务器无法理解请求的格式,通常是请求参数有误、JSON 格式错误、缺少必要参数。(前端需要检查发送的数据) |
401 Unauthorized | 未授权 | 请求要求身份认证。(用户未登录或 Token 无效) 通常需要弹出登录框。 |
403 Forbidden | 禁止访问 | 服务器理解请求,但拒绝执行。(用户已登录,但权限不足) |
404 Not Found | 未找到 | 服务器找不到请求的资源。(检查请求的 URL 路径是否正确,资源是否已删除) |
405 Method Not Allowed | 方法不允许 | 请求行中指定的请求方法不能被用于请求相应的资源。(例如,接口只支持 POST,但你用了 GET) |
面试常问:401 和 403 的区别?
核心区别:身份认证 vs 权限控制。
401 Unauthorized:意思是“你是谁?”。问题出在身份未验证,服务器不知道你的身份。
403 Forbidden:意思是“我知道你是谁,但你不被允许做这个操作”。问题出在权限不足,服务器知道你的身份,但你的权限不够。
🟠 5xx 服务器错误
状态码 | 含义 | 场景与排查方向 |
---|---|---|
500 Internal Server Error | 服务器内部错误 | 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。(一个通用的、笼统的错误,后端代码 Bug、数据库连接失败等都可能导致) |
502 Bad Gateway | 坏网关 | 服务器作为网关或代理,从上游服务器收到无效响应。(常见于 Nginx 反向代理后端的服务挂掉或无法连接) |
503 Service Unavailable | 服务不可用 | 服务器当前无法处理请求(由于超载或系统维护)。(通常意味着服务暂时不可用,可能伴随着 Retry-After 头告诉客户端何时重试) |
504 Gateway Timeout | 网关超时 | 服务器作为网关或代理,没有及时从上游服务器收到请求。(常见于 Nginx 等待后端服务响应超时) |
面试常问:502、503、504 的区别?
502:网关本身是好的,但后面的服务挂了或返回了无法解析的响应。
503:服务是存在的,但现在因为负载过高或维护而暂时无法处理任何请求。
504:网关后面的服务还在运行,但处理请求太慢,超过了网关的等待时间。
面试回答技巧
问题: “请说一下你了解的 HTTP 状态码。”
回答结构建议:
-
总起分类: “HTTP 状态码用第一位数字表示了响应的类型,总共分为 5 类:1xx 表示信息,2xx 表示成功,3xx 表示重定向,4xx 表示客户端错误,5xx 表示服务器错误。”
-
列举核心状态码(按类别说):
-
“在 2xx 中,最常见的是
200 OK
表示成功,201 Created
表示创建成功,204 No Content
表示成功但无返回内容。” -
“在 3xx 中,
301
是永久重定向,302
是临时重定向,304
是缓存相关,表示资源未修改。” -
“在 4xx 中,
400
是请求参数错误,401
是未登录,403
是权限不足,404
是资源未找到。” -
“在 5xx 中,
500
是服务器内部错误,502
是网关错误,503
是服务不可用。”
-
12.promise 有哪些状态
-
“Promise 有三种状态:
pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。” -
“其中
pending
是初始状态。状态一旦从pending
变为fulfilled
或rejected
,就不可逆,会一直保持这个结果。” -
“当我们在 Promise 执行器内部调用
resolve
时,状态会变为fulfilled
;调用reject
或发生异常时,状态会变为rejected
。” -
“我们通过
.then()
方法来监听fulfilled
状态并获取结果值,通过.catch()
方法来监听rejected
状态并捕获失败的原因。”
二、代码题
1.交换a,b的值,不能用临时变量
var a=100 ,b=200
答案:
方法一:算术运算(加减法)
原理:
-
先将两数之和存入
a
-
用和减去
b
得到原来的a
,存入b
-
用和减去新的
b
(即原来的a
)得到原来的b
,存入a
var a = 100, b = 200;a = a + b; // a = 300 (100 + 200)
b = a - b; // b = 100 (300 - 200)
a = a - b; // a = 200 (300 - 100)console.log(a, b); // 输出: 200 100
方法二:算术运算(乘除法)
var a = 100, b = 200;a = a * b; // a = 20000 (100 × 200)
b = a / b; // b = 100 (20000 ÷ 200)
a = a / b; // a = 200 (20000 ÷ 100)console.log(a, b); // 输出: 200 100
注意:这种方法要确保值不为 0,否则会出现除零错误。
方法三:位运算(异或操作)⭐ - 最优雅
原理(异或运算的特性):
-
x ^ x = 0
-
x ^ 0 = x
-
异或满足交换律和结合律
var a = 100, b = 200;a = a ^ b; // a = 100 ^ 200
b = a ^ b; // b = (100 ^ 200) ^ 200 = 100
a = a ^ b; // a = (100 ^ 200) ^ 100 = 200console.log(a, b); // 输出: 200 100
方法四:ES6 解构赋值(严格来说用了临时变量,但语法简洁)
var a = 100, b = 200;[a, b] = [b, a];console.log(a, b); // 输出: 200 100
各种方法的比较
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
加减法 | 直观易懂 | 可能溢出(数字很大时) | 一般数值交换 |
乘除法 | 直观 | 不能处理0值,可能精度丢失 | 非零数值 |
异或运算 | 不会溢出,效率高 | 对非整数可能不适用 | 最佳通用方案 |
解构赋值 | 代码简洁 | 底层仍用临时变量 | ES6+ 环境 |
2.有个数组a=[3,6,9,10,15,......],其中包含了很多正整数,请编程找出其中最大的数字。
答案:
方法一:使用 Math.max() 和扩展运算符(推荐)
const a = [3, 6, 9, 10, 15, 8, 20, 5];
const max = Math.max(...a);
console.log(max); // 输出: 20
方法二:使用 Math.max() 和 apply()
const a = [3, 6, 9, 10, 15, 8, 20, 5];
const max = Math.max.apply(null, a);
console.log(max); // 输出: 20
方法三:使用 reduce() 方法
const max = a.reduce((a, b) => Math.max(a, b));
方法四:使用 for 循环(传统方法)
const a = [3, 6, 9, 10, 15, 8, 20, 5];
let max = a[0]; // 假设第一个元素是最大的for (let i = 1; i < a.length; i++) {if (a[i] > max) {max = a[i];}
}console.log(max); // 输出: 20
方法五:使用 for...of 循环
const a = [3, 6, 9, 10, 15, 8, 20, 5];
let max = a[0];for (const num of a) {if (num > max) {max = num;}
}console.log(max); // 输出: 20
3.下列代码输出的结果是?
const promise =new Promise((resolve,reject)=>{console.log(1);console.log(2);
});
promise.then(()=>{console.log(3);
});
console.log(4);
答案:1,2,4
4.下面代码输出的结果是?
const promise =new Promise((resolve,reject)=>{resolve('success1');reject('error');resolve('success2')
});
promise.then((res)=>{console.log('then:',res);
}).catch((err)=>{console.log('catch:',err);
})
答案:then: success1
5.对下列数组去重
const array=[1 , 2 , 3, 5, 1, 5, 9, 1, 2, 8 ]
答案:
方法一:使用 Set(ES6,最简洁)⭐
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // [1, 2, 3, 5, 9, 8]
或者
const uniqueArray = Array.from(new Set(array));
方法二:使用 filter() + indexOf()
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
const uniqueArray = array.filter((item, index) => {return array.indexOf(item) === index;
});
console.log(uniqueArray); // [1, 2, 3, 5, 9, 8]
方法三:使用 reduce()
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
const uniqueArray = array.reduce((acc, current) => {if (!acc.includes(current)) {acc.push(current);}return acc;
}, []);
console.log(uniqueArray); // [1, 2, 3, 5, 9, 8]
方法四:使用 for 循环 + includes()
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
const uniqueArray = [];for (let i = 0; i < array.length; i++) {if (!uniqueArray.includes(array[i])) {uniqueArray.push(array[i]);}
}
console.log(uniqueArray); // [1, 2, 3, 5, 9, 8]
方法五:使用 Map(ES6)
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
const uniqueArray = [...new Map(array.map(item => [item, item])).values()];
console.log(uniqueArray); // [1, 2, 3, 5, 9, 8]
性能比较和选择建议
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Set | ⭐ 最简洁,性能好 | ES6+ 环境 | 现代项目首选 |
filter + indexOf | 代码清晰 | 性能较差(O(n²)) | 小数组,兼容性好 |
reduce | 函数式编程 | 代码稍复杂 | 函数式编程偏好 |
for 循环 | 兼容性好,可控性强 | 代码较长 | 需要兼容老浏览器 |
Map | 性能好 | 代码较难理解 | 需要保持顺序的复杂数据 |
6.不使用sort方法对下列数组排序(从小到大)
const array=[ 1, 5, 3, 4, 9, 0, -9, 6, 7, 8]
答案:
方法一:冒泡排序
const array = [1, 5, 3, 4, 9, 0, -9, 6, 7, 8];function bubbleSort(arr) {const result = [...arr]; // 创建副本,不修改原数组const n = result.length;for (let i = 0; i < n - 1; i++) {for (let j = 0; j < n - i - 1; j++) {if (result[j] > result[j + 1]) {// 交换元素[result[j], result[j + 1]] = [result[j + 1], result[j]];}}}return result;
}const sortedArray = bubbleSort(array);
console.log(sortedArray); // [-9, 0, 1, 3, 4, 5, 6, 7, 8, 9]
方法二:选择排序
const array = [1, 5, 3, 4, 9, 0, -9, 6, 7, 8];function selectionSort(arr) {const result = [...arr];const n = result.length;for (let i = 0; i < n - 1; i++) {let minIndex = i;// 找到未排序部分的最小元素for (let j = i + 1; j < n; j++) {if (result[j] < result[minIndex]) {minIndex = j;}}// 将最小元素交换到已排序部分的末尾if (minIndex !== i) {[result[i], result[minIndex]] = [result[minIndex], result[i]];}}return result;
}const sortedArray = selectionSort(array);
console.log(sortedArray); // [-9, 0, 1, 3, 4, 5, 6, 7, 8, 9]
方法三:插入排序
const array = [1, 5, 3, 4, 9, 0, -9, 6, 7, 8];function insertionSort(arr) {const result = [...arr];const n = result.length;for (let i = 1; i < n; i++) {const current = result[i];let j = i - 1;// 将当前元素插入到已排序部分的正确位置while (j >= 0 && result[j] > current) {result[j + 1] = result[j];j--;}result[j + 1] = current;}return result;
}const sortedArray = insertionSort(array);
console.log(sortedArray); // [-9, 0, 1, 3, 4, 5, 6, 7, 8, 9]
方法四:快速排序(递归)
const array = [1, 5, 3, 4, 9, 0, -9, 6, 7, 8];function quickSort(arr) {if (arr.length <= 1) {return arr;}const pivot = arr[Math.floor(arr.length / 2)];const left = [];const right = [];const equal = [];for (const element of arr) {if (element < pivot) {left.push(element);} else if (element > pivot) {right.push(element);} else {equal.push(element);}}return [...quickSort(left), ...equal, ...quickSort(right)];
}const sortedArray = quickSort(array);
console.log(sortedArray); // [-9, 0, 1, 3, 4, 5, 6, 7, 8, 9]
方法五:使用 Math.min() 和循环
const array = [1, 5, 3, 4, 9, 0, -9, 6, 7, 8];function manualSort(arr) {const result = [];const temp = [...arr];while (temp.length > 0) {// 找到当前数组中的最小值let minIndex = 0;for (let i = 1; i < temp.length; i++) {if (temp[i] < temp[minIndex]) {minIndex = i;}}// 将最小值添加到结果数组,并从临时数组中移除result.push(temp[minIndex]);temp.splice(minIndex, 1);}return result;
}const sortedArray = manualSort(array);
console.log(sortedArray); // [-9, 0, 1, 3, 4, 5, 6, 7, 8, 9]
7.在字符串的原型链上添加一个方法(reverse),实现字符串反转;
方法一:使用数组的 reverse() 方法(推荐)
// 在 String 原型上添加 reverse 方法
String.prototype.reverse = function() {// 将字符串分割成数组,反转数组,再合并回字符串return this.split('').reverse().join('');
};// 测试
const str = "Hello World";
console.log(str.reverse()); // 输出: "dlroW olleH"const chineseStr = "你好世界";
console.log(chineseStr.reverse()); // 输出: "界世好你"
方法二:使用扩展运算符
String.prototype.reverse = function() {return [...this].reverse().join('');
};// 测试
console.log("JavaScript".reverse()); // 输出: "tpircSavaJ"
方法三:使用 for 循环(传统方法)
String.prototype.reverse = function() {let reversed = '';for (let i = this.length - 1; i >= 0; i--) {reversed += this[i];}return reversed;
};// 测试
console.log("ABCDE".reverse()); // 输出: "EDCBA"
方法四:使用递归
String.prototype.reverse = function() {if (this.length <= 1) {return this.toString();}return this.substring(1).reverse() + this[0];
};// 测试
console.log("Recursion".reverse()); // 输出: "noisruceR"
方法五:使用 reduce()
String.prototype.reverse = function() {return [...this].reduce((reversed, char) => char + reversed, '');
};// 测试
console.log("Reduce".reverse()); // 输出: "ecudeR"