📝前端秋招八股-I
网络知识
常见协议
在访问 xiaohongshu.com 网站,下面哪个协议最不可能被使用?【单选】ARP DNS FTP TCP HTTP
题目解析
Reference:互联网协议套件、TCP/IP协议各层详解
ARP(Address Resolution Protocol,地址解析协议):位于数据链路层,用于将网络层的 IP 地址映射为物理层的 MAC 地址。
DNS(Domain Name System,域名系统):位于应用层,用于将域名解析为 IP 地址。
FTP(File Transfer Protocol,文件传输协议):位于应用层,用于文件传输,如上传或下载文件到服务器,通常与网站访问无关。
TCP(Transmission Control Protocol,传输控制协议):位于传输层,用于确保可靠的数据传输。
HTTP(Hypertext Transfer Protocol):位于应用层,是访问网站的核心协议,浏览器通过 HTTP 请求来获取网页的内容。
ISO 七层模型(数据链路层)
以下叙述中,哪些是数据链路层应该解决的问题?【多选】
A. 避免一个快速发送方用数据“淹没”一个慢速接收方 ✅
B. 在两个相邻系统之间唯一地标识数据电路
C. 在共享网络中,需要解决信道共享问题 ✅
D. 解决数据报的路由问题
OSI 七层网络模型职责简述即题目解析
- 物理层:负责原始比特流的传输,定义硬件层面的电气、光学、机械和功能接口标准。它只关心比特如何从一端传输到另一端。B 选项对应物理层的任务。
- 数据链路层:主要负责相邻节点之间的可靠数据传输,解决数据帧的错误检测、纠正、流量控制、以及在共享信道中协调多设备的访问。数据链路层还提供物理地址(MAC地址),用于在本地网络中标识设备。A、C 选项对应数据链路层的任务。
- 网络层:负责端到端的路由与寻址,决定数据报如何从源节点到达目标节点。网络层通过IP地址确定目标设备所在的网络,并选择最佳路由。D 选项对应网络层的任务。
- 传输层:负责端到端的可靠数据传输,包括流量控制、错误检测与恢复、数据分段及重组,确保数据从源设备正确送达目标设备。
- 会话层:负责建立、管理和终止通信会话。
- 表示层:负责数据的格式转换和加密解密,以确保不同系统之间的兼容性。
- 应用层:直接面向用户,提供网络服务的接口,比如文件传输、邮件等。
常见算法
KMP
已知字符串 S='hhhhpppgphghhhgphphh'
,模式串 t='hghhhg'
, 当第一次出现“失配”(s[i]≠t[j]
)时,i=j=1
。下次开始匹配时,i
和 j
的值分别是?【单选】i=1, j=0 i=10, j=1 i=2, j=1 i=6, j=0
题目解析
基于下述对 KMP 算法的介绍,现对问题进行求解如下,
- 构建模式串
t
的部分匹配表为next = [0,0,1,1,1,2]
,同时令i
指向字符串s
,j
指向模式串t
,初始情况下i = j = 0
。 (s[0] = "h") === (t[0] = "h")
匹配,则继续i++, j++
(s[1] = "h") ≠ (t[1] = "g")
失配,则i
指向不变,模板串前进1 - next[0] = 1
位,即j = next[0] = 0
,此时i = 1, j = 0
,对应答案 A
KMP 算法
Reference:字符串匹配的 KMP 算法
KMP(Knuth-Morris-Pratt)算法是一种高效的字符串匹配算法,用于在一个字符串中查找另一个子串。它的核心思想是通过构建一个部分匹配表(又称前缀表或 next 数组),来确定当发生不匹配时,模式串应该跳转到哪里继续匹配,而不是从头开始。
KMP 算法步骤如下,
-
构建部分匹配表(next 数组):对于模式串
t
,部分匹配表记录了每个字符(包含)之前的子串中,前缀和后缀的最长共有元素的长度。构建该表的时间复杂度为 ,其中m
是模式串t
的长度。举例:对于"abcdab"
,前缀为["a", "ab", "abc", "abcd", "abcda"]
,后缀为["bcdab", "cdab", "dab", "ab", "b"]
,前后缀的共有元素为"ab"
,所以"abcdab"
的匹配值为2
。 -
利用部分匹配表进行匹配:在匹配过程中,① 如果字符匹配,继续前进(指向字符串的指针
i++
,指向模模式串的指针j++
);② 如果失配且j > 0
,则根据部分匹配表决定模式串需要跳到的位置(指向字符串的指针i
不变,指向模式串的指针j = next[j-1]
);③ 如果失配且j = 0
,则仅需移动字符串的指针i++
,指向模式串的指针j
不变。假设在模式串的第k
个字符处(0-based
)失配,则使用以下公式确定模式串的移动位数:移动位数 =k
-next[k - 1]
其中,
j
表示已匹配的字符数,next[k-1]
是模式串第k-1
位的部分匹配值。这样可以避免从头重新匹配。
性能优化
文件合并
文件合并是前端性能优化常见的手段,请问下列关于前端文件合并说法正确的是?【多选】
A. 为避免缓存失效的问题,一般会将不经常更新的公共库与业务分开合并 ✅
B. 文件合并容易导致 JS 等文件过大,从而使网页首屏展示延迟 ✅
C. 文件合并有利于减少 HTTP 请求数,提高传输效率 ✅
D. 文件合并后更加容易产生丢包
题目解析
A. 浏览器会缓存静态资源,为减少缓存失效导致的重复加载,通常将不常更新的第三方库(如 React、Vue 等)与更新频繁的业务代码分开打包。这样可以:① 公共库长期缓存,不因业务代码更新而重新加载,提高用户体验;② 业务代码更新时只加载变化部分,避免重复加载所有代码。因此,分开打包能更好地控制缓存和资源加载频率,优化性能。
B. 文件合并旨在减少 HTTP 请求数,但合并后的文件过大会增加加载时间,特别是在低速网络下,可能导致首屏展示延迟,影响用户体验。为解决此问题,通常采用以下方法:① 合理拆分代码,避免过度合并(如代码分割、懒加载);② 压缩合并后的文件,减小体积;③ 使用关键路径资源加载策略,优先加载首屏所需资源。
C. 在 HTTP/1.x 协议下,每个 HTTP 请求都会产生开销,特别是请求多个小文件时。开销包括连接建立、服务器处理和数据传输时间。将多个小文件合并可以减少请求数量,从而降低开销、提高效率。这在 HTTP/1.x 中尤为重要,虽然 HTTP/2 引入了多路复用,缓解了这个问题,但减少请求数量在某些场景下仍有助于提升性能。
D. 文件合并不会直接导致网络传输中丢包率的增加。丢包问题通常与网络质量、信号强度、服务器性能等网络环境因素有关,而不是因为文件的大小或合并所引起的。
前端代码
window.history
假设在 https://www.xiaohongshu.com/ 页面的控制台中输入以下 JS 代码,那么以下有关说法错误的是?【多选】
1 | history.pushState({flag: 'another'}, "page2", "another.html"); |
A. 此时修改 URL 访问之后,点击浏览器的后退按钮将加载 another.html 页面 ✅
B. 浏览器立即加载 another.html 页面 ✅
C. 此时页面的 URL 显示为 https://www.xiaohongshu.com/another.html
D. 如果 another.html 页面不存在会抛出异常 ✅
window.history 的使用
Reference:History - Web API | MDN
history
是浏览器提供的基于栈(stack)的,用于管理会话历史记录(session history)的 API。
属性
-
window.history.length
【只读】当前会话中的历史记录数量(包含当前页面) -
window.history.scrollRestoration
用户导航时是否应该自动恢复到页面的滚动位置-
auto
默认行为,表示恢复到用户离开页面时的滚动位置 -
manual
表示恢复到当前页面顶部
-
-
window.history.state
【只读】与当前的历史记录(栈顶记录)关联的状态信息(state)。该属性是在 HTML5 中引入的,当使用pushState
或replaceState
更改或添加新的历史记录时,可以附加一些状态信息,即传递一个对象作为状态对象,其可以包含任何类型的数据(除循环引用对象或 DOM 节点)。需要注意的是,如果没有使用pushState
或replaceState
更改或添加历史记录,则当前的历史记录对应的状态信息state
的取值将会是null
。
方法
-
window.history.back()
导航到当前会话的上一个历史记录。相当于点击浏览器的”后退“按钮,等价于调用window.history.go(-1)
。如果没有上一页,则该方法调用不执行任何操作。 -
window.history.forward()
导航到当前会话的下一个历史记录。相当于点击浏览器的”前进“按钮,等价于调用window.history.go(1)
。如果没有下一页,则该方法调用不执行任何操作。 -
window.history.go([delta])
导航到当前会话的指定历史记录。delta > 0
,表示导航到前n
个历史记录;delta < 0
,表示导航到后n
个历史记录;delta = 0
或不指定delta
,表示重载当前页面,即等价于调用location.reload()
。 -
window.history.pushState(state, unused[, url])
向当前会话中添加一条历史记录,同时会更改 URL 但不刷新页面。注意:当用户导航到带有状态信息的历史记录时,会触发popstate
事件,此时可以通过event.state
访问到与该历史记录相关联的状态信息的副本。-
state
:与新添加的历史记录相关联的状态信息,是一个 JavaScript 对象。注意:state
对象可以是任何可序列化的对象,FireFox 将其保存在用户磁盘,并限制其序列化后的大小不超过 16MB。 -
unused
:因历史原因而存在的参数,已不推荐使用,可以传递一个空字符串以确保安全。 -
url
:新添加的历史记录对应的 URL,如不提供,则默认为当前 URL。url
可以是相对路径或绝对路径,当url
是相对路径时,其将相对于当前 URL 进行解析,同时,新 URL 与旧 URL 必须同源。
-
-
window.history.replace(state, unused[, url])
更新当前会话中最新的历史记录,其他与pushState
方法类似。
ES6 类定义
以下关于 ES6 中类说法不正确的是?【单选】
A. 类可以重复声明 ✅
B. 必须在访问前对类进行定义,否则就会报错
C. 类让对象原型的写法更加清晰、更像面向对象编程的语法
D. ES6 中通过 class 关键字定义类,本质是 function
题目解析
A. ES6 中,类的声明与 let
和 const
类似,不能重复声明。
B. 在 ES6 中,类不存在变量提升(hoisting),必须在使用之前定义类。
C. ES6 类的语法让原型继承的写法更加直观,符合面向对象编程的习惯。
D. ES6 中的类实际上是语法糖,本质上依然是函数,类的构造器函数会作为对象的构造函数。
代码输出推测(提升)
下面代码执行后的结果为:undefined
1 | var a = 2; |
题目解析
此题考察的是 JavaScript 中的变量提升和函数提升,基于下述对提升的介绍,可以分析本题代码的等价形式为,
1 | var a = 2; |
变量提升和函数提升
-
变量提升:在 JavaScript 中,变量声明会被提升到当前作用域的顶部,但仅仅是声明被提升,赋值操作不会被提升。
-
对于
var
声明的变量,在提升时会初始化为undefined
。1
2
3console.log(x); // undefined
var x = 5;
console.log(x); // 5等价于
1
2
3
4var x; // 声明提升
console.log(x); // undefined
x = 5; // 赋值操作在原位置
console.log(x); // 5 -
对于
let
和const
声明的变量,在提升时不会初始化。直接访问未赋值的变量会导致ReferenceError
,称之为暂时性死区(Temporal Dead Zone)。1
2console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
-
-
函数提升:在 JavaScript 中,函数声明也会被提升到当前作用域的顶部,但与变量提升不同的是,函数的**整个定义(包括函数体)**都会被提升。
1
2
3
4greet(); // 输出: Hello!
function greet() {
console.log("Hello!");
}等价于
1
2
3
4function greet() { // 声明和定义都提升到顶部
console.log("Hello!");
}
greet(); // 输出: Hello!
浏览器拖放
当用户开始拖拽一个元素时,会触发该元素的哪个事件?【单选】ondrop ondragover ondragstart ondrag
浏览器的原生拖放实现
HTML 的拖放(Drag and Drop)接口使得应用程序能够在浏览器中实现拖放功能。通常情况下,用户可以选中一个可拖拽(draggable)的元素,将其拖动到一个可放置(droppable)的目标元素上,然后释放鼠标。对于支持该功能的浏览器,DOM 元素有一个 draggable
布尔属性,用于标识该元素是否可以被拖拽。常见的 <a>
、<img>
等元素的 draggable
属性默认为 true
,而 <div>
、<p>
等元素的 draggable
属性默认是 false
,但可以通过将其设置为 true
来实现拖拽功能。
拖放过程中可能涉及到的事件(DragEvent
)如下,
事件名 | 触发对象 | 触发时机 |
---|---|---|
dragstart |
可拖拽元素 | 开始拖拽元素时 |
drag |
可拖拽元素 | 拖拽元素时(频繁触发) |
dragend |
可拖拽元素 | 结束拖拽元素时 |
dragenter |
可放置元素 | 拖拽元素进入指定元素上时 |
dragover |
可放置元素 | 拖拽元素在指定元素上时(频繁触发) |
dragleave |
可放置元素 | 拖拽元素离开指定元素上时 |
drop |
可放置元素 | 拖拽元素放置指定元素上时 |
DragEvent
事件对象包含一个 dataTransfer
属性,它是一个 DataTransfer
对象,用于存储和传递拖放操作中的数据。
正则表达式
下面代码执行后的输出结果为:['12abcd2024eg']
1 | var str="12abcd2024eg".match(/\d+\w*/g) |
题目解析
本题考查的是正则表达式以及 JavaScript 中使用正则表达式的常见 API,基于下述分析可知
str.match(regex)
表示从字符串str
中提取出匹配正则表达式regex
的所有(这里正则表达式是全局匹配)内容。/\d+\w*/g
表示匹配至少一个数字和任意数量的字母、数字或下划线,即\d
匹配12
,\w*
匹配abcd2024eg
。
JavaScript 中的正则表达式
-
正则表达式(Regular Expression)是一种针对字符串定义的模式规则,用于检查字符串是否符合特定的规则,或从字符串中提取符合该规则的内容。
-
JavaScript 创建正则表达式
- 通过构造函数:
const regex = new RegExp(pattern, flags);
- 通过字面量:
const regex = /pattern/flags;
- 通过构造函数:
-
pattern
参数的语法-
普通字符:匹配字符串中是否存在对应字符。
1
2console.log(/ab/.test("bbabaaa")); // true
console.log(/ab/.test("bbaaaaa")); // false -
特殊字符
特殊字符 匹配内容 .
任一字符(除了空白字符) \d
任一数字 \w
任一字母、数字、下划线 \s
任一空白字符(如空格、制表符等) ^
字符串的开始 $
字符串的结束 *
前面的字符 0 次或多次(任意次数) +
前面的字符 1 次或多次(至少一次) ?
前面的字符 0 次或 1 次 {n}
前面的字符 正好n 次 {n,}
前面的字符至少 n 次 {n,m}
前面的字符至少 n 次,至多 m 次 []
定义或字符集,如 [a-z]
表示匹配任意小写字母[A-Z]
表示匹配任意大写字母[a-zA-Z]
表示匹配任意字母[0-9]
表示匹配任意数字[aeiou]
表示匹配任意元音字母[^]
定义非字符集,如 [^a-z]
表示匹配除了小写字母以外的字符[^x]
表示匹配除了x
以外的字符` ` ()
定义捕获组,用于将括号中的部分匹配单独提取出来,以供后续使用或引用
-
-
flags
参数的语法参数值 匹配内容 g
全局匹配,即找到所有匹配而非第一个 i
忽略大小写 m
多行模式,即使用 ^
和$
匹配每行的开头和结尾 -
正则表达式的常用 API
-
regex.test(str): boolean
测试字符串str
是否匹配正则regex
。 -
regex.exec(str): array
从字符串str
中提取出匹配正则表达式regex
的结果数组。数组的第一个元素是整个匹配的字符串,后续元素是各个捕获组的内容。数组还包括两个属性:index
表示匹配结果在原始字符串中的起始位置,input
表示被匹配的原始字符串。如果没有匹配项,返回null
。通常exec
只匹配一个结果,若正则有全局标志g
,可以通过while
循环多次调用来获取所有匹配项。1
2
3
4
5
6
7const regex = /\d+/g;
const str = "The numbers are 123, 456, and 789";
let result;
while ((result = regex.exec(str)) !== null) {
console.log(`Found ${result[0]} at index ${result.index}`);
}
-
-
字符串中使用到正则表达式的 API
str.split(regex): array
使用正则表达式regex
分割字符串str
。str.search(regex): number
返回正则表达式regex
在字符串str
中首次匹配的位置,未找到则返回-1
。str.replace(regex, alternateStr): string
使用alternateStr
替换regex
在字符串str
中首次匹配的内容。若regex
是全局匹配,则替换所有匹配项。str.match(regex): array
提取regex
在字符串str
中首次匹配的内容为数组,未找到返回null
。若regex
是全局匹配,返回所有匹配项数组。str.matchAll(regex): iterator
返回正则表达式regex
匹配字符串str
所有结果的迭代器,内容类似于每次调用regex.exec()
返回的结果。正则表达式需为全局匹配。
操作系统
死锁
下列关于死锁避免的叙述错误的是?【单选】
A. 当一个进程难以确切知道它所需要的最大资源需求量时,很难采用银行家算法避免死锁
B. 死锁避免并不会严格限制死锁产生的必要条件的存在
C. 银行家算法可以避免死锁,算法需要考虑每个进程对各类资源的申请情况,采用这种算法分配资源时,资源的利用率较高 ✅
D. 死锁产生的四个必要条件成立,系统未必会出现死锁
题目解析
A. 银行家算法要求每个进程在运行前声明其最大资源需求。如果进程无法准确估计其最大资源需求,就很难使用银行家算法避免死锁。
B. 死锁避免策略不严格要求破坏死锁的四个必要条件,而是通过动态检测资源分配是否会导致死锁,来避免进入死锁状态。
C. 虽然银行家算法能够避免死锁,但它的资源利用率通常较低。因为银行家算法需要确保系统始终处于“安全状态”,所以会预留更多的资源,导致资源利用率不高。
D. 死锁产生的四个必要条件成立是死锁发生的前提,但并不意味着死锁一定发生。只有在系统进入不安全状态时,才可能出现死锁。
死锁及死锁产生的四个必要条件
死锁(deadlock)是指多个进程因互相持有彼此所需的资源而无法继续运行,陷入永远等待的状态。
死锁产生的四个必要条件为,
- 互斥条件(Mutual Exclusion):进程对资源的独占使用,某个资源一次只能被一个进程占用。如果其他进程请求该资源,则必须等待直到该资源被释放。
- 请求与保持条件(Hold and Wait):进程已经获得了一部分资源,在等待其他资源时继续持有已经获得的资源。
- 不剥夺条件(No Preemption):资源不能被强制从一个进程中剥夺,只有当进程自愿释放资源时,资源才会被释放。
- 循环等待条件(Circular Wait):存在一个进程链,进程 A 等待进程 B 持有的资源,进程 B 等待进程 C 持有的资源,直到某个进程等待链中的最后一个进程又等待进程 A 的资源,形成了一个循环等待。
银行家算法
银行家算法是一种用于避免死锁的资源分配算法。它的核心思想是:在进行资源分配时,系统通过模拟判断是否能够保证在未来的一定时刻,所有进程都能完成资源申请和释放。如果可以保证系统的安全状态,资源才会被分配;否则,系统会拒绝当前资源申请,避免进入不安全状态。
银行家算法的基本步骤为,
- 声明最大需求:每个进程在开始时必须声明自己可能需要的最大资源数量。
- 资源分配检查:每当一个进程请求资源时,系统会检查当前资源的分配状态,并预测在分配资源之后,系统是否仍然处于安全状态。如果分配资源后仍然安全,资源将被分配;如果分配资源后进入不安全状态,则暂时拒绝该请求。
- 安全状态判断:系统通过“安全状态”的概念判断是否可以分配资源。安全状态指的是,即使当前资源分配给某个进程,系统仍然能保证满足所有进程的资源需求,不会进入死锁。
银行家算法的特点为,
- 资源利用率较低:为了保证系统的安全,银行家算法会保留一些资源,避免进入不安全状态,这可能导致系统资源的利用率不高。
- 需要最大需求声明:每个进程在执行前必须声明最大资源需求,如果进程无法准确估计其需求,银行家算法无法生效。