网络知识

常见协议

在访问 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 七层网络模型职责简述即题目解析
  1. 物理层:负责原始比特流的传输,定义硬件层面的电气、光学、机械和功能接口标准。它只关心比特如何从一端传输到另一端。B 选项对应物理层的任务。
  2. 数据链路层:主要负责相邻节点之间的可靠数据传输,解决数据帧错误检测纠正流量控制、以及在共享信道中协调多设备的访问。数据链路层还提供物理地址(MAC地址),用于在本地网络中标识设备。A、C 选项对应数据链路层的任务。
  3. 网络层:负责端到端的路由与寻址,决定数据报如何从源节点到达目标节点。网络层通过IP地址确定目标设备所在的网络,并选择最佳路由。D 选项对应网络层的任务。
  4. 传输层:负责端到端的可靠数据传输,包括流量控制、错误检测与恢复、数据分段及重组,确保数据从源设备正确送达目标设备。
  5. 会话层:负责建立、管理和终止通信会话
  6. 表示层:负责数据的格式转换和加密解密,以确保不同系统之间的兼容性。
  7. 应用层:直接面向用户,提供网络服务的接口,比如文件传输、邮件等。

常见算法

KMP

已知字符串 S='hhhhpppgphghhhgphphh',模式串 t='hghhhg', 当第一次出现“失配”(s[i]≠t[j])时,i=j=1。下次开始匹配时,ij 的值分别是?【单选】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 指向字符串 sj 指向模式串 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,部分匹配表记录了每个字符(包含)之前的子串中,前缀和后缀的最长共有元素的长度。构建该表的时间复杂度为 O(m)O(m),其中 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 的使用

ReferenceHistory - Web API | MDN

history 是浏览器提供的基于(stack)的,用于管理会话历史记录(session history)的 API。

属性

  1. window.history.length 【只读】当前会话中的历史记录数量(包含当前页面)

  2. window.history.scrollRestoration 用户导航时是否应该自动恢复页面的滚动位置

    • auto 默认行为,表示恢复到用户离开页面时的滚动位置

    • manual 表示恢复到当前页面顶部

  3. window.history.state 【只读】与当前的历史记录(栈顶记录)关联的状态信息(state)。该属性是在 HTML5 中引入的,当使用 pushStatereplaceState 更改或添加新的历史记录时,可以附加一些状态信息,即传递一个对象作为状态对象,其可以包含任何类型的数据(除循环引用对象或 DOM 节点)。需要注意的是,如果没有使用 pushStatereplaceState 更改或添加历史记录,则当前的历史记录对应的状态信息 state 的取值将会是 null

方法

  1. window.history.back() 导航到当前会话上一个历史记录。相当于点击浏览器的”后退“按钮,等价于调用 window.history.go(-1)。如果没有上一页,则该方法调用不执行任何操作。

  2. window.history.forward() 导航到当前会话下一个历史记录。相当于点击浏览器的”前进“按钮,等价于调用 window.history.go(1)。如果没有下一页,则该方法调用不执行任何操作。

  3. window.history.go([delta]) 导航到当前会话指定历史记录。delta > 0,表示导航到前 n 个历史记录;delta < 0,表示导航到后 n 个历史记录;delta = 0 或不指定 delta,表示重载当前页面,即等价于调用 location.reload()

  4. window.history.pushState(state, unused[, url])当前会话添加一条历史记录,同时会更改 URL不刷新页面。注意:当用户导航到带有状态信息的历史记录时,会触发 popstate 事件,此时可以通过 event.state 访问到与该历史记录相关联的状态信息的副本

    • state:与新添加的历史记录相关联的状态信息,是一个 JavaScript 对象。注意:state 对象可以是任何可序列化的对象,FireFox 将其保存在用户磁盘,并限制其序列化后的大小不超过 16MB。

    • unused:因历史原因而存在的参数,已不推荐使用,可以传递一个空字符串以确保安全。

    • url新添加的历史记录对应的 URL,如不提供,则默认为当前 URL。url 可以是相对路径或绝对路径,当 url 是相对路径时,其将相对于当前 URL 进行解析,同时,新 URL 与旧 URL 必须同源

  5. window.history.replace(state, unused[, url]) 更新当前会话中最新的历史记录,其他与 pushState 方法类似。

ES6 类定义

以下关于 ES6 中类说法不正确的是?【单选】

A. 类可以重复声明 ✅

B. 必须在访问前对类进行定义,否则就会报错

C. 类让对象原型的写法更加清晰、更像面向对象编程的语法

D. ES6 中通过 class 关键字定义类,本质是 function

题目解析

A. ES6 中,类的声明与 letconst 类似,不能重复声明

B. 在 ES6 中,类不存在变量提升(hoisting),必须在使用之前定义类

C. ES6 类的语法让原型继承的写法更加直观,符合面向对象编程的习惯。

D. ES6 中的类实际上是语法糖,本质上依然是函数,类的构造器函数会作为对象的构造函数。

代码输出推测(提升)

下面代码执行后的结果为:undefined

1
2
3
4
5
6
7
8
9
10
11
12
var a = 2;
function outer() {
inner();
return;

var a = 1;
function inner() {
console.log(a);
}
}

outer();
题目解析

此题考察的是 JavaScript 中的变量提升和函数提升,基于下述对提升的介绍,可以分析本题代码的等价形式为,

1
2
3
4
5
6
7
8
9
10
11
12
var a = 2;
function outer() {
var a; // 变量声明提升,var 声明的变量自动初始化为 undefined
function inner() {
console.log(a);
}

inner(); // 由于此时 a 没有被赋值,a = undefined
return;

a = 1; // 变量赋值操作
}
变量提升和函数提升
  1. 变量提升:在 JavaScript 中,变量声明会被提升到当前作用域的顶部,但仅仅是声明被提升,赋值操作不会被提升

    • 对于 var 声明的变量,在提升时初始化为 undefined

      1
      2
      3
      console.log(x); // undefined
      var x = 5;
      console.log(x); // 5

      等价于

      1
      2
      3
      4
      var x; // 声明提升
      console.log(x); // undefined
      x = 5; // 赋值操作在原位置
      console.log(x); // 5
    • 对于 letconst 声明的变量,在提升时不会初始化。直接访问未赋值的变量会导致 ReferenceError,称之为暂时性死区(Temporal Dead Zone)。

      1
      2
      console.log(y); // ReferenceError: Cannot access 'y' before initialization
      let y = 10;
  2. 函数提升:在 JavaScript 中,函数声明也会被提升到当前作用域的顶部,但与变量提升不同的是,函数的**整个定义(包括函数体)**都会被提升。

    1
    2
    3
    4
    greet();  // 输出: Hello!
    function greet() {
    console.log("Hello!");
    }

    等价于

    1
    2
    3
    4
    function 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
2
var str="12abcd2024eg".match(/\d+\w*/g)
console.log(str);
题目解析

本题考查的是正则表达式以及 JavaScript 中使用正则表达式的常见 API,基于下述分析可知

  • str.match(regex) 表示从字符串 str提取出匹配正则表达式 regex所有(这里正则表达式是全局匹配)内容。
  • /\d+\w*/g 表示匹配至少一个数字任意数量的字母、数字或下划线,即 \d 匹配 12\w* 匹配 abcd2024eg
JavaScript 中的正则表达式
  1. 正则表达式(Regular Expression)是一种针对字符串定义的模式规则,用于检查字符串是否符合特定的规则,或从字符串中提取符合该规则的内容。

  2. JavaScript 创建正则表达式

    • 通过构造函数const regex = new RegExp(pattern, flags);
    • 通过字面量const regex = /pattern/flags;
  3. pattern 参数的语法

    • 普通字符:匹配字符串中是否存在对应字符。

      1
      2
      console.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 以外的字符
      ` `
      () 定义捕获组,用于将括号中的部分匹配单独提取出来,以供后续使用或引用
  4. flags 参数的语法

    参数值 匹配内容
    g 全局匹配,即找到所有匹配而非第一个
    i 忽略大小写
    m 多行模式,即使用 ^$ 匹配每行的开头和结尾
  5. 正则表达式的常用 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
      7
      const 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}`);
      }
  6. 字符串中使用到正则表达式的 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 的资源,形成了一个循环等待。
银行家算法

银行家算法是一种用于避免死锁的资源分配算法。它的核心思想是:在进行资源分配时,系统通过模拟判断是否能够保证在未来的一定时刻,所有进程都能完成资源申请和释放。如果可以保证系统的安全状态,资源才会被分配;否则,系统会拒绝当前资源申请,避免进入不安全状态。

银行家算法的基本步骤为,

  • 声明最大需求:每个进程在开始时必须声明自己可能需要的最大资源数量。
  • 资源分配检查:每当一个进程请求资源时,系统会检查当前资源的分配状态,并预测在分配资源之后,系统是否仍然处于安全状态。如果分配资源后仍然安全,资源将被分配;如果分配资源后进入不安全状态,则暂时拒绝该请求。
  • 安全状态判断:系统通过“安全状态”的概念判断是否可以分配资源。安全状态指的是,即使当前资源分配给某个进程,系统仍然能保证满足所有进程的资源需求,不会进入死锁。

银行家算法的特点为,

  • 资源利用率较低:为了保证系统的安全,银行家算法会保留一些资源,避免进入不安全状态,这可能导致系统资源的利用率不高。
  • 需要最大需求声明:每个进程在执行前必须声明最大资源需求,如果进程无法准确估计其需求,银行家算法无法生效。