网络知识

预检请求

预检请求的主要目的是什么?【单选】

A. 验证客户端的身份

B. 提前获取服务器的响应时间

C. 确定服务器是否在线

D. 检查服务器是否支持特定的 HTTP 方法 ✅

题目解析
  1. 预检请求(Preflight Request)的主要目的是在跨域请求中,检查服务器是否允许该请求的 HTTP 方法和请求头。浏览器会在实际请求前,通过 OPTIONS 请求方式发送预检请求,以确定目标服务器是否支持该特定的 HTTP 方法(如 PUTDELETE)及自定义的请求头,确保安全。
  2. 通过预检请求,浏览器可以确认服务器是否允许当前的跨域请求,从而保护资源安全
  3. 当请求使用了非简单 HTTP 方法(如 PUTDELETE)或带有自定义请求头时,浏览器会自动发起预检请求。

CSRF

以下哪个特征最能表明可能遭受了 CSRF 攻击?【单选】

A. 用户在未进行操作的情况下,发现自己的账户进行了一些敏感操作 ✅

B. 网站响应速度突然变慢

C. 网站出现排版错误

D. 用户收到大量垃圾邮件

题目解析

CSRF(跨站请求伪造)攻击的典型特征是攻击者通过伪造用户的身份,执行用户未授权的敏感操作。这通常在用户已登录某网站的情况下发生。例如,用户点击了一个恶意链接或访问了一个恶意网页,这些页面在用户不知情的情况下向目标网站发送请求,以用户的身份进行操作。

前端代码

display: none

执行以下代码,如果为 box1 盒子设置 display: none,则下列说法中,正确的是?【多选】

1
2
3
4
5
6
7
8
9
<style>
.box1 {
width: 200px;
height: 200px;
}
</style>
<div class="box1">
<div class="box2">这是子盒子</div>
</div>

A. 即使为 box1 盒子绑定事件,该事件也一定不会被触发

B. box1 不会占据页面的任何空间,但是 box2 仍占据页面的一定空间

C. 如果为 box2 盒子绑定事件,该事件有可能会被触发 ✅

D. box1 和 box2 将不会占据页面的任何空间 ✅

题目解析

A. 错误。根据下述第 4 点,尽管用户无法通过交互触发事件,但可以通过 JavaScript 的 dispatchEvent 手动触发事件,因此说事件“一定”不会被触发是错误的。

B. 错误。根据第 1、2 和 3 点,display: none 会将元素及其所有子元素(如 box2)从文档流中移除,并且这些元素不会占据页面的任何空间,因此 box2 也不会占据空间。

C. 正确。和 A 选项类似,虽然 box2 不可见且无法通过用户交互触发事件,但 JavaScript 依然可以手动触发绑定在 box2 上的事件。

D. 正确。根据第 1、2 和 3 点,box1box2 都不会显示在页面上,也不会占据任何空间,因此该选项是正确的。

display: none 的具体效果

display: none 是 CSS 中的一种样式规则,用于完全隐藏一个元素。其具体效果如下:

  1. 元素不可见:设置 display: none 后,元素不会在页面上渲染。
  2. 元素完全从文档流中移除:元素不会占据页面的任何空间,页面布局将忽略该元素的存在。
  3. 所有后代元素也不可见:不仅元素本身不可见,所有子元素也会被隐藏,并且不会占据任何空间。
  4. 用户无法与元素进行交互:无法通过点击、悬停、聚焦等方式与该元素交互,因此不会触发 clickhoverfocus 等事件。但是,可以通过 JavaScript 的 dispatchEvent 方法手动触发事件
  5. 元素仍然存在于 DOM 中:虽然不可见,元素依然存在于 DOM 结构中,因此仍然可以使用 JavaScript 对其进行操作,如修改样式、事件绑定或改变其可见性。

JavaScript 中的类

关于 JavaScript 中的类,下列说法正确的有?【多选】

A. 可以在类中定义静态方法,通过类名直接调用。 ✅

B. 类中的方法默认是公有的。 ✅

C. 类中的构造函数用于初始化实例的属性。 ✅

D. 使用 class 关键字定义类后,可以通过 new 关键字创建类的实例。 ✅

题目解析

A. 正确。 JavaScript 支持在类中定义静态方法,使用 static 关键字定义。静态方法只能通过类名直接调用,而不是通过类的实例调用。

B. 正确。 在 JavaScript 的类中,类的方法默认是公有的(public),可以在实例外部直接访问。如果要定义私有方法,可以使用 # 作为前缀。

C. 正确。 constructor 是类的构造函数,用于在创建实例时初始化实例的属性。它在实例化类时自动调用。

D. 正确。 在 JavaScript 中,使用 class 关键字定义类后,可以通过 new 关键字来创建该类的实例。

flex

以下关于 flex 属性说法正确的是?【多选】

A. flex: 0 0 200px 表示不允许项目放大和缩小,初始宽度为 200px。 ✅

B. flex: 1 1 auto 等同于 flex-grow:1; flex-shrink:1; flex-basis:auto。 ✅

C. flex 属性只能应用于块级元素。

D. flex 属性是 flex-growflex-shrinkflex-basis 的简写。 ✅

题目解析

C. 错误。 flex 属性不仅可以应用于块级元素,也可以应用于行内元素,只要它们的父元素是一个弹性容器(display: flexdisplay: inline-flex)。

ABD. 正确。 根据下述对 flex 属性的分析可知。

flex 属性

flex 属性是 CSS 中用于控制弹性盒子中的 伸缩项目 如何 分配空间 的复合属性。它结合了三个子属性:flex-growflex-shrinkflex-basis,用于定义 伸缩项目 在弹性盒子中的 放大缩小 行为以及 基准大小

  • flex-basis:定义了伸缩项目在主轴方向上的基准大小。浏览器通过该属性计算主轴上是否还有多余空间,从而对伸缩项目进行放大和缩小。若 flex-basis 设置为 auto,则基准长度默认为伸缩项目的内容宽度(当主轴横向时)或高度(当主轴纵向时)。

  • flex-grow:控制伸缩项目在主轴方向上的放大行为。假设主轴 剩余 空间为 restrest,第 ii 个伸缩项目的 flex-grow 值为 gig_i,所有伸缩项目的 flex-grow 之和为 GG,则第 ii 个伸缩项目的拉伸空间为:

    rest×giGrest \times \frac{g_i}{G}

  • flex-shrink:控制伸缩项目在主轴方向上的缩小行为。假设主轴 超出 空间为 shortageshortage,第 ii 个伸缩项目的 flex-shrink 值为 sis_i,其 flex-basis 值为 bib_i,所有伸缩项目的 flex-shrinkflex-basis 的乘积之和为 SBSB,则第 ii 个项目的收缩空间为:

    shortage×sibiSBshortage \times \frac{s_i \cdot b_i}{SB}

flex 属性的常用简写如下,

简写 全写 含义 特点
initial 0 1 auto 只允许压缩,并以伸缩项目的宽高作为基准大小 只能压缩
auto 1 1 auto 允许拉伸和压缩,并以伸缩项目的宽高作为基准大小 标准弹性
none 0 0 auto 不允许拉伸和压缩 失去弹性
1 1 1 0 允许拉伸和压缩,但是基准大小为零
(即仅按照 flex-shrink 的值成比例进行压缩,
每个伸缩项目的收缩空间为 shortage×sisishortage \times \frac{s_i}{\sum s_i}
比例弹性

注意:基准大小 flex-basis 只影响压缩,不影响拉伸。

font

执行以下代码,下列选项的属性,在 div 中没有定义的是?【多选】 vertical-align text-indent font-size line-height

1
2
3
4
5
6
<style>
div {
font: italic 20px/1.5 arial;
}
</style>
<div>这是 div 盒子</div>
font 属性

font 属性是一个用于设置字体的多个相关样式的复合属性,其完整写法为,

1
font: font-style? font-varaiant? font-weight? font-size/line-height? font-family
  • font-style 字体样式,可取值 normalitalicoblique
  • font-variant 字体变体,可取值 normalsmall-caps
  • font-weight 字体粗细,可取值 normalboldbolderlighter、数值
  • font-size 字体大小,可取数值
  • line-height 行高,可取值 normal、数值
  • font-family 字体族

font 属性中的各个值必须按照特定的顺序出现,其中 font-sizefont-family必须的,对于省略了的某些属性,将使用其默认值。

作用域

执行以下程序,输出结果为?【单选】 undefined 2 抛出异常 1

1
2
3
4
5
6
let a = 1;
function fn(f = () => a) {
let a = 2;
console.log(f());
}
fn();
题目解析

箭头函数 f 在定义时捕获了全局变量 a,即值为 1。因此,调用 f() 时输出结果是 1,与 fn 内部的局部变量 a = 2 无关。

width 的特殊取值

执行以下代码,如果想让 div 元素的宽度由文本撑起,并且文本只在一行显示,哪怕最终 div 宽度溢出外部容器,则 ① 式应为?【单选】 max-content min-content fit-content stretch

1
2
3
4
5
6
7
8
9
10
11
12
<style>
section {
width: 200px;
height: 200px;
}
div {
width:①;
}
</style>
<section>
<div>这是一段很长很长很长的div文本</div>
</section>
width 的特殊取值
width 属性的取值 含义
max-content 元素宽度设置为内容宽度的最大值,亦即内容全部在一行时的宽度
min-content 元素宽度设置为内容的最小不可换行宽度,通常为最长单词或不可分割内联元素的宽度
fit-content 元素宽度设置为**max-content 和容器宽度的自适应**,亦即二者取小
stretch 元素宽度设置为容器宽度
image-20241112164945876

parseInt

以下代码的运行结果是?【单选】 2 10 15 25

1
console.log(parseInt("10+15",2));
题目解析

parseInt 是 JavaScript 的一个内置函数,用于将给定的字符串解析为指定基数(即进制)的整数。其语法为,

1
parseInt(string[, radix]);
  • string 必须,表示要解析的字符串。解析字符串时会忽略前导空格,直到遇到无法转换为数字的字符为止。如果字符串无法被解析为一个有效的整数,则返回 NaN
  • radix 可选,表示要转换的基数(即进制),可取值 2 到 36,默认是 10。

因此,parseInt("10+15", 2) 以二进制解析字符串 "10+15",根据所描述的解析规则,可知解析结果为二进制数 10,打印出来的也就是十进制的 2

this

观察以下代码,推测输出

(1) 处执行输出:vscode:10 undefined;Chrome:10 10

(2) 处执行输出:vscode:10 undefined;Chrome:10 10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a = 10; // 1.
function myFunc() {
this.a = 10; // 2.
setTimeout(() => {
console.log(this.a); // 3.
setTimeout(function () {
console.log(this.a); // 4.
}, 100);
}, 100);
}

myFunc(); // (1)

var fun1 = new myFunc(); // (2)
题目解析
  1. this 在不同运行环境中的行为

    • 浏览器环境:在非严格模式下,this 指向 window 对象。

    • Node.js 环境:在模块作用域中,this 指向 module.exports。但在函数内部,this 在非严格模式下指向 globalThis,也就是 global 对象。

  2. this 在不同调用方式下的表现

    • 普通函数调用this 指向 window(浏览器)或 globalThis(Node.js)。

    • 构造函数调用this 指向新创建的实例对象,不依赖环境。

  3. 浏览器中的执行情况

    • (1) myFunc() 作为普通函数调用:
      • this 指向 window,因此 this.a = 10a 设置为全局对象的属性。
      • 第一层 setTimeout 的箭头函数(3. 处)中的 this.a 指向 window.a,输出 10
      • 第二层 setTimeout 的普通函数(4. 处)中的 this 指向 window,再次输出 window.a 的值 10
    • (2) var fun1 = new myFunc(); 作为构造函数调用:
      • thismyFunc 中指向新创建的实例对象 fun1
      • 第一层 setTimeout 的箭头函数继承了 this,指向 fun1,因此 console.log(this.a) 输出 10
      • 第二层 setTimeout 的普通函数(4. 处)中的 this 指向 window,输出 window.a 的值 10
  4. Node.js 中的执行情况

    • 在 Node.js 中,由于 var 声明的 a 不会自动成为 globalThis 的属性,因此 globalThis.a 未定义:

    • (1) myFunc() 作为普通函数调用:

      • this.a = 10 并未将 a 添加到 globalThis
      • 第一次 setTimeout 中的箭头函数输出 this.a,因为此时 this 指向 globalThisglobalThis.aundefined
      • 第二层 setTimeout 中的普通函数 this 指向 globalThis,因此输出 undefined
    • (2) new myFunc() 构造调用:

      • 第一层 setTimeout 中的 this 指向新创建的实例对象 fun1,因此 console.log(this.a) 输出 10
      • 第二层 setTimeout 的普通函数 this 指向 globalThis,因此 globalThis.aundefined

事件循环

观察以下代码,推测输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let promise2 = new Promise((resolve) => {
resolve("promise2.then");
console.log("promise2");
});

setTimeout(() => {
console.log("setTimeout");
});

promise2.then((res) => {
Promise.resolve().then(() => {
console.log("promise3");
});
console.log(res);
});

console.log("script end");
输出结果

promise2
script end
promise2.then
promise3
setTimeout

嵌套代码

观察以下代码,推测输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function fun(n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m, n);
},
};
}

// (1)
var a = fun(0);
a.fun(1);
a.fun(2);
a.fun(3);

// (2)
var b = fun(0).fun(1);
b.fun(2);
b.fun(3);
输出结果

undefined
0
0
0
undefined
0
1
1

具名函数表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 
以下这种定义函数的方式称之为具名函数表达式:
- 函数有名字 test,但这个名字只能在函数体内部可见,不会被提升到外部作用域。
- 函数外部只能通过变量名 func 来访问该函数,而无法通过 test 访问。
- typeof 操作符在处理未定义的变量时,返回 "undefined",而不会抛出错误。
*/
var func = function test() {
console.log(123);
};

// 由于 test 在函数体外部不可见,typeof test 会返回 "undefined"。
console.log(typeof test); // undefined

// 尝试调用 test 会导致 ReferenceError,因为 test 在当前作用域中未定义。
console.log(typeof test()); // ReferenceError: test is not defined

delete

1
2
3
4
5
6
7
8
9
/* 
delete 操作符在 JavaScript 中的主要作用是删除对象的属性或数组的元素。
- 它不能删除通过 var、let、const 声明的变量。
- 在删除操作成功时,delete 返回 true;否则返回 false。
*/
(function (x) {
delete x;
console.log(typeof x); // number
})(1);

提升

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 
JavaScript 中的函数声明会被提升到当前作用域的顶部,但变量的赋值不会被提升,只会提升变量的声明。

以下代码相当于,
var func; // 变量提升
function func(){} // 函数提升

func = 1; // 变量赋值
console.log(func + func);
*/
var func = 1;
function func() {}
console.info(func + func); // 2

数组加法

1
2
3
const res = [1, 0] + [0, 1];
console.log(res); // 1,00,1
console.log(typeof res); // string

属性描述符、Symbol、与 Object 静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* 
Object.create(proto[, propertiesObject]) 该静态方法以一个现有对象作为原型,创建一个新对象。
- proto 新创建对象的原型对象。
- properties 定义新对象的属性及其特性。
(1) 该参数的取值规则等同于 Object.defineProperties 中的第二个参数
(2) 字面量初始化对象是 Object.create(Object.prototype) 的一个语法糖
(3) 该参数为新对象中的每个属性指定详细的配置(称之为属性描述符),可以用于控制每个属性是否可修改、是否可枚举、是否可删除

属性描述符语法 Property Descriptor
- 属性描述符是一个对象,其用于描述对象属性的具体行为。
- 属性描述符分为:数据描述符和访问器描述符 ---> 对应数据属性和访问器属性
(1) 数据描述符 ---> 对应数据属性,即普通属性(默认值是 undefined,且不可修改)
value 属性值,可以是任何有效的 JavaScript 值;默认值为 undefined
writable 表示属性是否可以使用赋值运算符更改;boolean;默认值为 false
(2) 访问器描述 ---> 对应访问器属性,即通过 getter / setter 获取和设置的属性
get 属性的 getter 函数,函数的返回值被用作属性的值;默认为 undefined
set 属性的 setter 函数,函数接收一个参数,即分配给属性的新值
(3) 数据描述符和访问器描述符共有的字段
enumerable 表示属性是否可以被枚举(即通过 for-in 或 Object.keys() 访问);boolean;默认值为 false
configurable 表示属性是否可以被删除,以及属性的属性描述符是否可以修改(即通过 Object.defineProperties 修改);boolean;默认值为 false
- 一个属性描述符必须是数据描述符或访问器描述符中的任何一种,否则会报错

Symbol
- Symbol 是 JavaScript 中的一种原始数据类型,表示唯一且不可变的值,常用于创建对象的唯一属性键(该数据类型仅有的目的)
- 可以通过 Symbol([description]) 的方式创建一个 Symbol 值,且每个 Symbol 值都是唯一的,即使其描述相同
- Symbols 在 for...in 迭代中不可枚举。另外,Object.getOwnPropertyNames() 不会返回 symbol 对象的属性,但是你能使用 Object.getOwnPropertySymbols() 得到它们。

Object.getOwnPropertyNames(obj)
- Object.getOwnPropertyNames() 静态方法返回一个数组,其包含给定对象中所有自有属性(包括不可枚举属性,但不包括使用 symbol 值作为名称的属性)。
*/
const obj = Object.create(
{ num: 117 },
{
str: {
value: "jack",
enumerable: false,
},
[Symbol()]: {
value: 935,
enumerable: true,
},
}
);
console.log(Object.getOwnPropertyNames(obj)); // [ 'str' ]

flex-shrink

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>flex</title>
<!--
flex-shrink 属性
- flex-shrink 是一个 CSS 属性,用于控制弹性盒子(flexbox)布局中的子元素在父容器空间不足时的缩小比例。
- flex-shrink 的默认值是 1,表示元素会根据需要缩小。
- 如果设置为 0,元素将不会缩小,即使父容器的空间不足以容纳所有子元素,它的宽度(或高度)也保持不变。
- 如果设置为大于 1 的值,元素会按照这个比例优先于其他元素缩小。例如,flex-shrink: 2 的元素会缩小两倍于 flex-shrink: 1 的元素。
当父容器的空间不足时,flex-shrink 属性会计算每个元素应当缩小的宽度或高度。计算公式大致如下:
- 总需要缩小的空间 = 子元素宽度总和 - 父容器的宽度。
- 每个元素的缩小值 = 需要缩小的空间 × (元素的 flex-shrink 值) / (所有子元素的 flex-shrink 值总和)。
-->
<!--
Step1. 初始宽度:每个 span 元素的宽度:80px。

Step2. 计算剩余空间:
父容器宽度:200px。
子元素初始宽度总和:240px。
超出的宽度:240px - 200px = 40px。

Step3. flex-shrink 计算:
span:first-child 的 flex-shrink 为 0,意味着它不会缩小,保持 80px 宽度。
span:last-child 的 flex-shrink 为 2,表示它的收缩比例是 2 倍于 span 中间那个默认 flex-shrink 为 1 的元素。

Step4. 计算缩小比例:
span:first-child:宽度不变,80px。
剩余元素(span:nth-child(2) 和 span:last-child)的总收缩比例为 1 + 2 = 3,因此每个单位收缩的宽度为 40px / 3 ≈ 13.33px。

Step5. 最终宽度:
span:first-child:80px(未缩小)。
span:nth-child(2):80px - 13.33px ≈ 66.67px。
span:last-child:80px - 26.67px ≈ 53.33px。
-->
<style>
div {
display: flex;
width: 200px;
height: 200px;

background-color: aliceblue;
}

span {
width: 80px;
height: 200px;
}

span:first-child {
flex-shrink: 0;
}

span:last-child {
flex-shrink: 2;
}
</style>
</head>

<body>
<div>
<span style="background-color: palegoldenrod;"></span>
<span style="background-color: aquamarine;"></span>
<span style="background-color: darkcyan;"></span>
</div>
</body>
</html>

前端知识

javascript 是否会阻塞页面渲染

从事件循环的角度来看,JavaScript 的执行模型是单线程的,这意味着 JavaScript 代码的执行是按顺序进行的,而浏览器的渲染也是由同一个主线程负责的。因此,当 JavaScript 代码在执行时,它会阻塞主线程,导致浏览器无法进行页面的解析和渲染。解决方式如下

  • script 标签使用 defer 属性

    • 浏览器会在解析完整个 HTML 文档后,按照顺序异步加载和执行这些脚本。

    • 脚本会在 DOM 解析完成后执行,但在 DOMContentLoaded 事件之前。

      DOMContentLoaded 是浏览器中一个非常重要的事件,它在 HTML 文档被完全加载和解析完成后触发,而不必等待样式表、图片和子框架的完全加载。

  • script 标签使用 async 属性

    • 浏览器会异步加载脚本并在加载完成后立即执行,不会等待其他脚本的加载或执行。

      defer 的区别

      • 不保证 script 的执行顺序
      • script 会在下载完成后立即执行,可能会阻塞渲染,但是不会阻塞后序资源的加载
  • <script> 标签放在 <body> 标签的末尾:这样浏览器可以先解析和渲染页面的主体内容,再加载和执行脚本,从而避免页面加载时的白屏或卡顿现象。

浏览器的缓存机制

  1. 强缓存:强缓存策略通过 expirescache-control 响应头进行控制。

    • expires:指定了资源过期的绝对时间。在此之前,浏览器可以直接从缓存中读取资源,而不需要向服务器发送请求。

    • cache-control:可以通过 max-age=xxx 指定资源过期的相对时间;通过 publicprivate 指定缓存对象,前者表示客户端和代理服务器都可以缓存,后者表示只有客户端才能缓存;通过 no-store 指定不缓存任何内容;通过 no-cache 表示每次使用缓存都需要经过协商缓存来决定,即每次使用缓存都要验证,

      强缓存 expires cache-control
      http 版本 http/1.0 http/1.1
      优先级
      时间值 绝对 相对
  2. 协商缓存:当浏览器请求强制缓存中的缓存数据发现失效时,会使用协商缓存向服务器确认缓存数据是否仍然有效。协商缓存通过 last-modified 响应头 & if-modified-since 请求头或 etag 响应头 & if-not-match 请求头进行控制。

    • last-modified/if-modified-since:服务器在响应头中返回资源的最后修改时间 Last-Modified,浏览器在后续请求中通过 If-Modified-Since 发送上次获取的时间,服务器根据资源是否修改决定返回 304 Not Modified 或更新的资源。

    • etag/if-not-match:服务器为每个资源生成一个唯一的标识符 ETag,浏览器在请求时通过 If-None-Match 发送该标识符,服务器比较标识符决定返回 304 Not Modified 或新的资源。

      强缓存 last-modified/if-modified-since etag/if-not-match
      优先级
  3. 缓存的存放位置

    缓存位置 内存缓存 memory 磁盘缓存 disk
    资源类型 短期的、会频繁访问的 长期的、不频繁变化的
    资源举例 页面脚本、样式 图片、文件
    读取速度
    关闭/刷新浏览器是否可保留

http 常见状态码

  1. 状态码分类

    状态码 含义
    1xx 信息响应
    2xx 成功响应
    3xx 重定向响应
    4xx 客户端错误响应
    5xx 服务器错误响应
  2. 信息响应

    状态码 状态描述 含义
    100 Continue 表示客户端应继续请求,如果已完成请求则忽略。
    101 Switching Protocols 服务器理解并同意客户端的协议切换请求。
  3. 成功响应

    状态码 状态描述 含义
    200 OK 请求成功,并返回所请求的资源(如网页、文件)。
    201 Created 请求成功,并且在服务器上创建了新的资源。
    202 Accepted 请求已接收,但尚未处理。
    204 No Content 请求成功,但不返回任何内容。
  4. 重定向响应

    状态码 状态描述 含义
    301 Moved Permanently 请求的资源已被永久移动到新位置,客户端应使用新的 URL 进行访问。
    302 Found 请求的资源临时移动到新位置,客户端应继续使用原有的 URL 进行访问。
    304 Not Modified 资源未被修改,客户端可以使用缓存版本
  5. 客户端错误响应

    状态码 状态描述 含义
    400 Bad Request 请求有误,服务器无法理解请求。
    401 Unauthorized 请求未授权,客户端需要提供认证信息。
    403 Forbidden 服务器拒绝请求,即使认证成功也不允许访问。
    404 Not Found 请求的资源在服务器上未找到。
    405 Method Not Allowed 请求的方法(如 GET, POST)不被允许。
    429 Too Many Requests 客户端发送的请求过多,触发了速率限制。
  6. 服务端错误响应

    状态码 状态描述 含义
    500 Internal Server Error 服务器遇到错误,无法完成请求。
    501 Not Implemented 服务器不支持请求的功能。
    502 Bad Gateway 服务器作为网关或代理时,从上游服务器接收到的响应无效。
    503 Service Unavailable 服务器当前无法处理请求,通常是因为过载或维护。
    504 Gateway Timeout 服务器作为网关或代理时,未及时从上游服务器获得响应。

如何判断数组数据结构

  1. instanceof

    instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

    1
    2
    3
    /* object instanceof constructor */
    const arr = [1, 2, 3];
    console.log(arr instanceof Array); // true

    使用 instanceof 检查某个对象是不是数组的方式并不安全,原因如下,

    • 跨窗口或跨框架环境instanceof 检查对象的原型链,验证对象是否继承自 Array.prototype。然而,在不同的 JavaScript 执行环境(如不同的浏览器窗口或框架)之间,Array 构造函数是不同的。

      1
      2
      3
      4
      let iframe = document.createElement('iframe');
      document.body.appendChild(iframe);
      let arr = new iframe.contentWindow.Array(); // 从 iframe 创建的数组
      console.log(arr instanceof Array); // false
    • instanceof 依赖于原型链:如果你改变了对象的原型链,instanceof 也会受到影响。

      1
      2
      3
      let arr = [];
      Object.setPrototypeOf(arr, Object.prototype);
      console.log(arr instanceof Array); // false
  2. constructor

    因为实例的 constructor 属性指向其构造函数,那么可以通过判断该属性是否等于 Array 来判断实例是否为数组对象。

    1
    2
    const arr = [1, 2, 3];
    console.log(arr.constructor === Array); // true

    但是这种方式也不安全,原因同 instanceof

  3. Object.prototype.toString.call

    Object.prototype.toString.call(对象) 方法返回一个表示该对象的字符串。

    1
    2
    const arr = [1, 2, 3];
    console.log(Object.prototype.toString.call(arr) === "[object Array]"); // true
  4. Array.isArray

    Array.isArray(对象) 方法判断一个对象是不是数组对象。(最推荐)

    1
    2
    const arr = [1, 2, 3];
    console.log(Array.isArray(arr)); // true

浏览器端的事件循环机制,常见的宏任务与微任务,代码输出推测

  1. 执行栈 & 任务队列

    • 执行

      • JavaScript 是单线程语言,所有的同步任务都会在主线程上的执行栈依次执行。

      • 当一个函数被调用时,它会被压入执行栈中,函数执行完毕后,会从栈中弹出

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        function foo(b) {
        let a = 10;
        return a + b + 11;
        }

        function bar(x) {
        let y = 3;
        return foo(x * y);
        }

        console.log(bar(7));
        • 调用 bar 时,第一个帧被创建并压入栈中,帧中包含了 bar 的参数和局部变量。
        • bar 调用 foo 时,第二个帧被创建并被压入栈中,放在第一个帧之上,帧中包含 foo 的参数和局部变量。
        • foo 执行完毕然后返回时,第二个帧就被弹出栈(剩下 bar 函数的调用帧)。
        • bar 也执行完毕然后返回时,第一个帧也被弹出,栈就被清空了。
    • 任务队列

      • 异步任务(如事件监听器、定时器、HTTP 请求等)的回调函数会被放入相应的任务队列中,分为 “宏任务队列” 和 “微任务队列”。

      • 宏任务(Macro Task)包括 scriptsetTimeoutsetIntervalsetImmediate(Node.js)I/O 操作、用户交互(如移动鼠标)、UI 渲染等。

      • 微任务(Micro Task)包括 Promise.thenMutationObserverprocess.nextTick(Node.js) 等。

        • 调用 Promise.resolve()Promise.resolve() 时会创建微任务

        • MutationObserver 可以用来监视 DOM 的变化,包括属性的变更、节点的增加、内容的改变等。

  2. 事件循环

    image-20240904103609402

    Step1. 执行同步代码,直到执行栈为空

    Loop:

    ​ Step2. 执行微任务队列中的所有微任务

    ​ Step3. 执行宏任务队列中的一个宏任务

    • 以上所有步骤都可能产生宏任务与微任务
    • 执行微任务时产生的微任务不会推迟到下一个循环执行,而是在当前循环中继续执行
    • 如果一项任务执行花费的时间过长,浏览器将无法执行其他任务,例如处理用户事件。因此,在一定时间后,浏览器会抛出一个如 “页面未响应” 之类的警报,建议你终止这个任务。这种情况常发生在有大量复杂的计算或导致死循环的程序错误时。
    • 函数 queueMicrotask(func) 用于对 func 进行排队,使其微任务队列中执行。
  3. 代码输出推测

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    console.log(1); // Step1. 同步代码

    new Promise((resolve, reject) => {
    console.log(2); // Step2. 同步代码

    setTimeout(() => { // Step4. 第一个宏任务
    console.log(3);
    resolve(false);
    }, 0);

    console.log(4); // Step3. 同步代码

    setTimeout(() => { // Step6. 第二个宏任务
    reject(true); // 什么也没做,因为第一个宏任务已经改变了 Promise 的状态
    console.log(5);
    }, 0);
    })
    .then((res) => console.log(6)) // Step5. 第一个宏任务执行时添加的微任务
    .catch((err) => console.log(7));

    setTimeout(() => { // Step7. 第三个宏任务
    console.log(8);
    }, 0);

Promise 的使用,优缺点

  1. Promise 是什么?

    • 从语法上说,Promise 是一个构造函数

    • 从功能上说,Promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值

  2. Promise 状态

    pending(初始状态)、fulfilled(操作成功)、rejected(操作失败)

    • 创建 Promise 实例时,PromiseState 为 pending,当调用 resolve() 后,变为 fulfilled,当调用 reject() 后,变为 rejected

    • PromiseState 只能由 pending 变为 fulfilled 或由 pending 变为 rejected 这两种类型

    • 一个 Promise 对象的状态只能改变一次(pending => fulfilled/rejected)

  3. Promise 构造函数

    const p = new Promise(executor)

    • executor 是一个回调函数,其结构为 (resolve, reject) => {}
    • executor 会在 Promise 内部立即 “同步调用”,而异步操作在执行器中执行
    • resolve 是一个在 executor 函数体中 “定义成功” 时调用的函数,结构为 value => {},用于将 promise 对象的状态设置为 “成功”
    • reject 是一个在 executor 函数体中 “定义失败” 时调用的函数,结构为 reason => {},用于将 promise 对象的状态设置为 “失败”
  4. Promise 实例方法

    • p.then(onResolved, onRejected))

      • onResolved 是一个函数,结构为 value => {},该函数在 promise 对象的状态为 ”成功“ 时调用

      • onRejected 是一个函数,结构为 reason => {},该函数在 promise 对象的状态为 ”失败“ 时调用

    • p.catch(onRejected)

      onRejected 是一个函数,结构为 reason => {},该函数在 promise 对象的状态为 ”失败“ 时调用

    • p.finally(callback)

      callback 回调函数,不接受任何参数,该函数在 promise 对象的状态 “兑现”(即非 pending)时调用

  5. Promise 静态方法

    注:这里的 promises 参数是指 Promise 对象构成的数组;同时静态方法的返回值也是一个 Promise 对象

    • Promise.all(promises)

      • 如果 Promise 数组中的每个 Promise 对象的状态为 fulfilled,则 (1) 返回的 Promise 对象的状态为 fulfilled (2)

        返回的 Promise 对象的结果为 Promise 数组中每个 Promise 对象成功结果构成的数组

      • 如果 Promise 数组中至少一个 Promise 对象的状态为 rejected,则 (1) 返回的 Promise 对象的状态为 rejected (2) 返回的 Promise 对象的结果为 Promise 数组中第一个失败的 Promise 对象的结果

    • Promise.race(promises)

      返回的 Promise 对象,其状态和结果由 Promise 数组中最先改变状态的 Promise 对象决定

    • Promise.allSettled(promises)

      返回的Promise 对象的状态永远不可能是失败(rejected),当 Promise 对象数组中的每一个 Promise 对象的异步操作都结束(不论成功还是失败),这个返回的 Promise 对象的状态变为成功(fulfilled),结果为 Promise 数组中每个 Promise 对象的状态和结果构成的对象构成的数组(如果数组元素 item.status === ‘fulfilled’,此时需要通过 item.value 访问结果;item.status === ‘rejected’,此时需要通过 item.reason 访问结果)

    • Promise.any(promises)

      • 只要 Promise 对象数组中任何一个 Promise 对象的状态变为成功(fulfilled),返回的 Promise 对象的状态为成功,并且结果为
      • 如果 Promise 对象数组中所有的 Promise 对象的状态都变为失败(rejected),返回的 Promise 对象的状态变为失败,并且结果为一个 AggregateError 实例,其中包含了 Promise 对象数组中所有失败的 Promise 对象的结果(reason)
    • Promise.resolve(value)

      • 如果传入的参数为非 Promise 对象,则返回的 Promise 对象的状态为 fulfilled,结果为参数值
      • 如果传入的参数为 Promise 对象,则返回的 Promise 对象的状态和结果与传入的 Promise 对象相同
    • Promise.resolve(value)

      将传入的参数转换为 Promise 对象,返回的 Promise 对象的状态始终为 rejected,结果为传入的参数

  6. Promise 的优缺点

    • 优点

      • 简化异步编程,避免回调地狱

      • 链式调用,允许多个异步操作按次序执行 .then

      • 异常穿透,允许对链式调用中的错误进行统一处理 .catch(更好的错误处理方式)

    • 缺点

      • 无法取消,一旦新建 Promise 就会立即执行,无法中途取消

      • 调试困难,错误堆栈不如同步代码直观,难以追踪问题的来源

Node.js 和浏览器 JavaScript 的区别

区别 Node.js 浏览器
运行环境 服务器端(访问底层操作系统的功能,
如网络、文件系统等)
客户端(操作 DOM、用户交互等)
⭐ 版本环境 开发者决定 访问者决定
⭐ 全局对象 global window
全局变量 process__dirname
module.exports
documentlocalStorage
window.location
⭐ 模块系统 CommonJs + ESM(ES6 模块系统) ESM(ES6 模块系统)
I/O 操作 通过文件系统 通过网络请求Web APIs(如 localStorage
事件循环 管理异步操作 管理异步操作、DOM 事件、用户交互
多线程 单线程运行,通过 work_threads 进行多线程 单线程运行,通过 web workers 进行多线程

关于在 node.js 环境下使用模块化的标注

  • .js 文件:如果 package.json 的 type 字段为 module,则使用 ESM,否则使用 CommonJs
  • .mjs 文件:使用 ESM
  • .cjs 文件:使用 CommonJs

行内元素、行内块元素、块级元素的区别

区别 行内元素 inline 行内块元素 块级元素 block
⭐ 独占一行 × ×
可包含内容 文本或行内元素 文本或行内元素 任意类型的元素
⭐ 可设置宽高 ×
⭐ 默认尺寸 宽高由内容决定 宽高由内容决定 默认占满父容器宽度,高由内容决定
⭐ 内/外边距是否有效 ×(仅水平方向有效)
常见元素 a、span、em、strong、
img、input、label
img、input、button div、p、h1-h6、ul、ol、li、
section、article、header、footer

a 元素是例外,其可以包含块级元素,但是不能再包含一个 a 元素

display: none 和 visibility: hidden 的区别

特性 display: none visibility: hidden
是否占据布局空间 ×(元素从页面布局中移除) √(元素占据原位置)
是否渲染在页面 ×(元素不被渲染) √(元素隐藏但保留位置)
是否影响文档流 √(移除元素会重新调整文档流,会触发重排) ×(文档流不变,元素依然占据空间)
事件是否响应 ×
是否存在 DOM 树
适用场景 完全移除元素,如条件渲染,性能优化 需要隐藏元素但保持布局,如遮挡效果或过渡

OSI 七层模型

OSI 将计算机网络分了七层,每一层抽象底层的内容,并遵守一定规则。基于OSI 网络模型,网络中的节点/物理设备得以进行通信。从顶层到底层,OSI 共规定了以下七层,

  1. 应用层 Application Layer:支持用户程序使用的服务。应用层包含若干协议:文件传输协议 FTP、安全壳协议 SSH、简单邮件传输协议 SMTP、因特网消息访问协议 IMAP、域名服务 DNS、超文本传输协议 HTTP
  2. 表示层 Presentation Layer:数据格式化(如字符编码控制)、数据加密(SSL/TLS)。
  3. 会话层 Session Layer:建立、维持和终止两个用户程序之间的连接。会话层以上的网络层关注:如何与用户应用程序建立连接,以及如何向用户展示数据
  4. 传输层 Transport Layer:通过 TCP 或 UDP 将数据发送给设备的特定端口,建立数据通信。传输层有两个重要协议,传输控制协议 TCP 和用户数据报协议 UDP。
    • 传输控制协议 TCP:面向连接的协议;优先保证数据质量而不是传输速度
    • 用户数据报协议 UDP:无连接的协议;优先保证传输速度而不是传输质量
  5. 网络层 Network Layer:通过路由器在网络间进行通信。
  6. 数据链路层 Data Link Layer:定义数据的传输格式,进行线路规划与流量控制,进行错误监测与校正。
  7. 物理层 Physical Layer:通过网线、电缆等方式将设备物理连接。

TCP/IP 四层模型

TCP/IP 四层模型可以看作是对 OSI 七层模型的简化,从顶层到底层,具体划分如下,

  1. 应用层:对应 OSI 的应用层、表示层、会话层。
  2. 传输层:对应 OSI 的传输层,通过 TCP/IP 协议实现端口到端口的通信(即两台主机间进程通信)
  3. 网络层:对应 OSI 的网络层,建立主机间的通信
  4. 数据链路层:对应 OSI 的数据链路层、物理层,通过物理手段连接设备

TCP 和 UDP 的区别

  1. TCP(Transmission Control Protocol,传输控制协议)是基于连接的,在传输数据时会建立接收端和发送端之间的连接,并在传输过程中始终保持这种连接。

    • TCP 在传输数据时会检查错误,以确保发送的数据完整到达目的地
    • TCP 会根据接收端的容量进行优化和调整传输数据的速度
    • TCP 会确认数据已到达目的地,如果第一次传输失败,会尝试重新传输
  2. UDP(User Datagram Protocol,用户数据报协议)是无连接的,在传输数据时不会在双方之间建立事先的连接

    • UDP 以较小负担发送给数据包,从而减少端到端的延迟
    • 传输过程中数据包丢失数据包,UDP 仍会传送数据,即数据包的丢失不会中断整个传输
    • UDP 可以通过广播和多播功能,同时发送数据给多个接收端
    • UDP 比 TCP 更快速、高效
  3. TCP 和 UDP 都是位于传输层的通信协议,其区别如下,

    区别 TCP UDP
    可靠性(按顺序、完整性、错误检测)
    连接性 面向连接 无连接
    速度 较低
  4. TCP 和 UDP 在应用层协议的使用场景

    TCP UDP
    SMTP 电子邮件 DNS 域名转换
    TELNET 远程终端接入 TFTP 文件传输
    HTTP 万维网 SNMP 网络管理
    FTP 文件传输 NFS 远程文件服务器

如何封装健壮性强的 react 组件

参考:可靠组件的七个属性(reliable react component)

  1. 特征:一个好的组件应该具有 ①高内聚、低耦合隐藏内部结构职责单一 这些特点。

    • 所谓的高内聚,就是将逻辑紧密相关的内容放在一个组件中;而低耦合,就是将不同组件之间的依赖关系尽量弱化
    • 组件的低耦合有利于修改、替换、复用、单元测试
    • 隐藏内部结构,就是要求仅通过一组 props 来控制组件行为,这也有利于减少组件对其他组件的依赖
    • 职责单一,就是要求一个组件只负责一件事情,这有利于组件的维护和复用
  2. 设计:封装一个组件需要思考 ①这个组件是干什么的?这个组件应该至少需要知道哪些信息?这个组件会反馈什么内容?,相较于实现需求,更重要的是对需求的抽象。

  3. 行为:父组件通过 props 的方式向封装好的子组件进行通信,可以使用 prop-types 库或 TypeScript 进行参数验证,检查传入组件的参数是否合法(数据类型、是否必传等)。

es6 新特性(ES2015)

  1. letconst 关键字

  2. 模板字符串

  3. 箭头函数

  4. 默认参数、可选参数(又叫其余参数)

    1
    2
    const func1 = (message = 'hello') => { /**/ } // 参数默认值
    const func2 = (...rest) => { /**/ } // 可选参数,rest 参数是一个数组
  5. 解构赋值(对象、数组)

  6. 展开运算符

  7. 类,通过 class 关键字定义

  8. 模块化,通过 importexport 实现

  9. Promise

  10. setweakSetmapweakMap

输入 url 到渲染页面的过程

Step1. 浏览器查找当前 URL 是否存在缓存,并比较缓存是否过期

这里的缓存机制包括强制缓存协商缓存

强制缓存:当浏览器向服务器发送请求时,服务器返回资源的同时,会使用 ExpiresCache-Control 响应头来控制是否缓存对应的资源。

协商缓存:当浏览器请求强制缓存中的缓存数据时,如果存在缓存数据及其标识,但缓存结果失效,此时浏览器无法确定该缓存数据是否仍然有效。在这种情况下,浏览器会携带对应的资源标识发送 HTTP 请求,以确认缓存的数据是否与服务器上的数据一致。如果数据一致,服务器会返回 304 状态码,告诉浏览器可以继续使用缓存中的数据;如果数据不一致,服务器会返回 200 状态码,并提供最新的数据。Last-Modified / Etag 响应头和 If-Modified-Since / If-None-Match 请求头用于控制协商缓存。

Step2. DNS 解析 URL 对应的 IP

Step3. 根据 IP 建立 TCP 连接 —— 三次握手

三次握手的大致流程为

  1. 客户端发送 syn 包,等待服务器确认。
  2. 服务器收到 syn 包,同时发送 syn + ack 包。
  3. 客户端收到 syn + ack 包,向服务器发送 ack 包。

发送完毕后,客户端和服务端进入 established 状态,完成三次握手。

Step4. 发送 HTTP 请求

Step5. 服务器处理请求,并发送 HTTP 响应

Step6. 浏览器解析响应报文渲染页面

解析报文渲染页面的大致流程为

  1. HTML -> DOM 树
  2. CSS -> CSSOM 树
  3. DOM 树 + CSSOM 树 -> Render 树
  4. 浏览器开始渲染并绘制页面(这里涉及到回流重绘两个概念:回流涉及到重新渲染,重绘涉及到样式的修改)

Step7. 关闭 TCP 连接 —— 四次挥手

四次挥手的大致流程为

  1. 客户端发送 fin 包,表示客户端不再发送数据,但可以接收数据
  2. 服务器收到 fin 包,发送 ack 包及序号,此时服务器处于半关闭状态
  3. 服务器发送 fin 包,表示关闭服务器到客户端的数据发送
  4. 客户端收到 fin 包,发送 ack 包并确认序号

经过一段时间后,客户端进入 closed 状态,服务端收到客户端的确认后立即进入 closed 状态并结束 TCP 连接。

怎么减少回流与重绘

  1. 回流 reflow:元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染。
  2. 重绘 repaint:元素样式的改变(但宽高、大小、位置等不变)。
  3. 回流 Vs. 重绘
    • 重绘不是很消耗性能,但是回流很消耗性能
    • 回流一定触发重绘,但重绘不一定会触发回流
  4. 减少回流或重绘的方式
    • 放弃手动操作 DOM,使用 vue 或 react 等框架,以数据驱动视图
    • 批量修改 DOM,减少回流重绘的次数
    • DOM 脱离文档流再进行处理
    • 合并对 DOM 样式的修改,采用 css class 来修改