农民 发表于 2024-10-15 03:01:29

前端杂学录(八)

1.HTTP哀求报文和相应报文的具体组成,能明白常见哀求头的寄义,有几种哀求方式,区别是什么

1. HTTP 哀求报文的组成

一个 HTTP 哀求报文通常包罗以下几个部门:
1.1 哀求行



[*]哀求方法:指明哀求的范例(如 GET、POST 等)。
[*]哀求 URI:指定哀求的资源地址。
[*]HTTP 版本:标明使用的 HTTP 协议版本(如 HTTP/1.1)。
GET /index.html HTTP/1.1
1.2 哀求头

哀求头包罗了多个字段,用于传递额外的信息。常见的哀求头包括:


[*]Host:哀求的主机名,必须包罗在 HTTP/1.1 哀求中。
[*]User-Agent:客户端的范例和版本信息。
[*]Accept:告诉服务器客户端可接受的内容范例(如 text/html、application/json)。
[*]Content-Type:表示发送内容的范例,通常在 POST 哀求中使用。
[*]Authorization:用于身份验证的凭证。
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
1.3 空行

哀求头与哀求体之间有一个空行,表示哀求头部门的结束。
1.4 哀求体

哀求体包罗了要发送给服务器的数据,通常在 POST、PUT 等方法中使用。GET 方法通常没有哀求体。
name=John&age=30
2. HTTP 相应报文的组成

一个 HTTP 相应报文同样包罗几个部门:
2.1 相应行



[*]HTTP 版本:使用的 HTTP 协议版本。
[*]状态码:表示哀求的处置惩罚结果(如 200、404 等)。
[*]状态消息:对状态码的简短描述。
HTTP/1.1 200 OK
2.2 相应头

相应头同样包罗多个字段,用于传递额外的信息。常见的相应头包括:


[*]Content-Type:返回内容的范例。
[*]Content-Length:返回内容的字节数。
[*]Set-Cookie:设置客户端的 cookie。
[*]Cache-Control:缓存控制指令。
Content-Type: text/html; charset=UTF-8
Content-Length: 1234
2.3 空行

相应头与相应体之间有一个空行,表示相应头部门的结束。
2.4 相应体

相应体包罗服务器返回的数据(如 HTML 页面、JSON 数据等)。
示例:
<html>...</html>
3. 常见哀求方式及其区别

HTTP 支持多种哀求方法,主要包括:
3.1 GET



[*]作用:从服务器获取资源。
[*]特点:

[*]哀求参数通常通过 URL 传递。
[*]无哀求体。
[*]数据在 URL 中可见,得当获取数据,且可被缓存。

3.2 POST



[*]作用:向服务器提交数据,通常用于创建新资源。
[*]特点:

[*]哀求参数通过哀求体传递。
[*]数据在哀求体中,不易被缓存。
[*]得当上传文件或提交表单。

3.3 PUT



[*]作用:向服务器上传数据,用于更新已存在的资源。
[*]特点:

[*]哀求参数通过哀求体传递。
[*]通常是幂等的(多次哀求结果雷同)。

3.4 DELETE



[*]作用:哀求服务器删除指定的资源。
[*]特点:

[*]通常是幂等的。

3.5 HEAD



[*]作用:类似于 GET,但只哀求相应头,不返回相应体。
[*]特点:

[*]用于获取资源的元数据。

3.6 OPTIONS



[*]作用:哀求服务器支持的 HTTP 方法。
[*]特点:

[*]常用于跨域哀求的预检。

2.HTTP所有状态码的具体寄义,看到非常状态码能快速定位问题

1. 1xx:信息性状态码

这些状态码表示哀求已被吸收并正在处置惩罚。


[*]100 Continue:初始哀求已接受,客户端可以继续发送哀求的别的部门。
[*]101 Switching Protocols:服务器已明白客户端的哀求,并将切换协议(如从 HTTP/1.1 切换到 WebSocket)。
2. 2xx:成功状态码

这些状态码表示哀求已成功处置惩罚。


[*]200 OK:哀求成功,服务器返回所哀求的资源。
[*]201 Created:哀求成功并创建了新资源,通常在 POST 哀求后返回。
[*]202 Accepted:哀求已接受,但尚未处置惩罚,通常用于异步处置惩罚。
[*]204 No Content:哀求成功,但没有返回内容,常用于 DELETE 哀求。
3. 3xx:重定向状态码

这些状态码表示哀求需要进一步操作才气完成。


[*]300 Multiple Choices:哀求的资源有多个选择,客户端可以选择其中之一。
[*]301 Moved Permanently:哀求的资源已永久移动到新 URI,客户端应使用新 URI 进行后续哀求。
[*]302 Found:哀求的资源暂时被移动到新 URI,客户端应继续使用原 URI。
[*]303 See Other:哀求应使用 GET 方法访问另一个 URI,以获取相应。
[*]304 Not Modified:资源未被修改,客户端可以使用缓存的版本。
[*]307 Temporary Redirect:哀求的资源暂时重定向到新 URI,客户端应使用原哀求方法进行哀求。
[*]308 Permanent Redirect:哀求的资源永久重定向到新 URI,客户端应使用原哀求方法进行哀求。
4. 4xx:客户端错误状态码

这些状态码表示哀求存在问题,导致服务器无法处置惩罚。


[*]400 Bad Request:哀求无效,服务器无法明白。
[*]401 Unauthorized:未提供身份验证凭证或凭证无效,访问被拒绝。
[*]403 Forbidden:服务器明白哀求,但拒绝执行,客户端没有访问权限。
[*]404 Not Found:哀求的资源不存在,URI 无效。
[*]405 Method Not Allowed:哀求方法不被允许,服务器不支持该方法。
[*]408 Request Timeout:哀求超时,服务器未能在等待哀求时收到有效数据。
[*]429 Too Many Requests:客户端在短时间内发送了过多哀求,服务器拒绝处置惩罚。
5. 5xx:服务器错误状态码

这些状态码表示服务器在处置惩罚哀求时发生了错误。


[*]500 Internal Server Error:服务器遇到意外情况,无法完成哀求。
[*]501 Not Implemented:服务器不支持哀求的功能,无法实现哀求。
[*]502 Bad Gateway:作为网关或代理的服务器从上游服务器收到无效相应。
[*]503 Service Unavailable:服务器当前无法处置惩罚哀求,通常因为过载或维护。
[*]504 Gateway Timeout:作为网关或代理的服务器未能在规定时间内从上游服务器得到相应。
6. 如何快速定位问题

当遇到非常状态码时,可以根据状态码的种别和具体寄义快速定位问题:


[*]1xx 状态码:通常不是错误,表明哀求正在处置惩罚。
[*]2xx 状态码:表示哀求成功,通常不需要关注。
[*]3xx 状态码:查抄 URI 是否正确,是否需要处置惩罚重定向。
[*]4xx 状态码:通常是客户端哀求的问题,查抄哀求参数、身份验证和资源路径。
[*]5xx 状态码:表示服务器问题,通常需要查抄服务器日志、设置或后端服务。
3.HTTP1.1、HTTP2.0带来的改变

1. HTTP/1.1 的特点



[*]文本协议:HTTP/1.1 是基于文本的协议,所有哀求和相应都是以文本情势传输。
[*]连接受理:每个哀求都需要创建一个独立的 TCP 连接,尽管支持持久连接(keep-alive),但仍然存在队头阻塞问题。
[*]哀求/相应模子:每个哀求都是独立的,客户端必须等待当前哀求完成才气发送下一个哀求。
[*]无压缩:HTTP/1.1 的头部信息未经过压缩,大概导致带宽浪费。
[*]支持缓存:通过缓存控制字段(如 Cache-Control)来管理缓存行为。
2. HTTP/2.0 的改变

2.1 二进制协议



[*]二进制分帧:HTTP/2.0 采用二进制格式,所有数据都被分帧传输。这使得协议更高效,易于解析。
[*]流和帧:HTTP/2.0 将数据分为差别的流(stream)和帧(frame),允许在同连续接上并行处置惩罚多个哀求和相应。
2.2 多路复用



[*]并行哀求:HTTP/2.0 允许在单一 TCP 连接上并行发送多个哀求和相应,消除了 HTTP/1.1 中的队头阻塞问题。
[*]流优先级:可以为差别的流设置优先级,优化资源的分配和加载顺序。
2.3 头部压缩



[*]HPACK 压缩:HTTP/2.0 使用 HPACK 算法对哀求和相应头进行压缩,减少了传输的字节数,提高了带宽利用率。
2.4 服务器推送



[*]服务器推送:服务器可以主动推送资源到客户端,而不必等待客户端哀求。这对于需要资源的页面非常有效,如 CSS 和 JavaScript 文件。
2.5 连接复用



[*]单连续接:HTTP/2.0 使用单连续接处置惩罚多个哀求和相应,减少了连接创建和关闭的开销。
2.6 更好的流量控制



[*]流量控制:HTTP/2.0 允许更复杂的流量控制机制,以管理数据流的传输和吸收,改善网络性能。
3. 总结

HTTP/2.0 在多个方面对 HTTP/1.1 进行了明显改进,主要包括:


[*]从文本协议变化为二进制协议,提高了解析效率。
[*]引入多路复用,允许并行哀求,消除了队头阻塞。
[*]采用头部压缩,减少了带宽斲丧。
[*]实现服务器推送,提高了资源加载效率。
[*]简化连接受理,使用单连续接处置惩罚多个哀求。
4.HTTPS的加密原理,如何开启HTTPS,如何劫持HTTPS哀求

1. HTTPS 的加密原理

1.1 SSL/TLS 协议

HTTPS 基于 SSL(安全套接层)或 TLS(传输层安全协议)协议工作。其主要功能包括:


[*]数据加密:使用对称加密和非对称加密技术加密数据,确保数据在传输过程中不被窃听。
[*]身份验证:通过数字证书验证服务器的身份,防止中间人攻击。
[*]数据完整性:使用消息摘要算法(如 SHA-256)确保数据在传输过程中未被窜改。
1.2 加密过程


[*] 创建连接:

[*]当客户端(如浏览器)向服务器发起 HTTPS 哀求时,首先会创建一个 SSL/TLS 连接。

[*] 握手过程:

[*]客户端向服务器发送支持的 SSL/TLS 版本和加密算法。
[*]服务器选择合适的版本和算法,并将其数字证书发送给客户端。
[*]客户端验证证书的有效性(如查抄证书链和颁发者)。
[*]客户端生成一个随机数(称为预主密钥),并用服务器的公钥加密后发送给服务器。
[*]服务器使用私钥解密预主密钥。

[*] 生成会话密钥:

[*]客户端和服务器使用预主密钥生成对称加密的会话密钥。

[*] 加密通信:

[*]使用会话密钥加密后续的数据传输。

2. 如何开启 HTTPS

要在网站上启用 HTTPS,需要遵循以下步骤:
2.1 获取 SSL/TLS 证书



[*]选择证书颁发机构(CA):选择一个可信的证书颁发机构(如 Let’s Encrypt、Comodo、DigiCert 等)。
[*]申请证书:根据 CA 的要求生成证书签名哀求(CSR),并提交申请。CA 会验证你的身份后颁发证书。
2.2 安装证书



[*]服务器设置:将得到的 SSL/TLS 证书安装到你的 Web 服务器上。差别的服务器(如 Apache、Nginx、IIS 等)有差别的安装步骤。
[*]设置 HTTPS:修改服务器设置文件,启用 HTTPS,指定证书和私钥的路径。
2.3 重定向 HTTP 到 HTTPS



[*]设置重定向:在服务器上设置 HTTP 哀求主动重定向到 HTTPS,以确保所有流量都是安全的。
2.4 测试 HTTPS



[*]查抄设置:使用在线工具(如 SSL Labs)查抄 SSL/TLS 设置的安全性,确保没有漏洞。
3. 如何劫持 HTTPS 哀求

尽管 HTTPS 提供了安全保障,但在一些情况下,攻击者仍大概实验劫持 HTTPS 哀求。以下是一些常见的攻击方法:
3.1 中间人攻击(MITM)



[*]伪造证书:攻击者大概会伪造证书,冒充正当服务器。用户访问时大概无法察觉,尤其是假如用户未仔细查抄证书的有效性。
[*]DNS 劫持:攻击者大概通过 DNS 劫持将用户哀求重定向到恶意服务器,尽管连接是 HTTPS,但仍然大概受到攻击。
3.2 SSL 剥离攻击



[*]SSL 剥离:攻击者使客户端与服务器之间的 HTTPS 连接被降级为 HTTP 连接,用户在不知情的情况下输入敏感信息。此攻击通常利用用户访问不安全的 HTTP 页面后,再转向安全的 HTTPS 页面。
3.3 恶意软件



[*]安装恶意软件:通过恶意软件,攻击者可以获取用户的 HTTPS 流量,尽管数据在传输过程中是加密的,但假如恶意软件在设备上运行,用户的信息仍然会被窃取。
5.明白WebSocket协议的底层原理、与HTTP的区别

1. WebSocket 协议的底层原理

1.1 创建连接



[*] 握手过程:

[*]WebSocket 连接始于一个 HTTP 哀求,以升级哀求(Upgrade Request)的情势发送。
[*]客户端向服务器发送一个 HTTP 哀求,要求将连接升级到 WebSocket 协议。哀求中包罗特定的头部字段,如 Upgrade: websocket 和 Connection: Upgrade。
示例哀求:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

[*] 服务器相应:

[*]假如服务器支持 WebSocket,它会回复一个 101 Switching Protocols 相应,表示连接已成功升级。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: [生成的接收密钥]

1.2 数据帧结构



[*] 数据帧:WebSocket 数据通过“帧”进行传输。每个帧包罗一个标头和有效载荷,标头包括信息如数据范例(文本、二进制)、帧长度等。
[*] 帧范例:

[*]文本帧:用于传输文本数据(UTF-8 编码)。
[*]二进制帧:用于传输二进制数据。
[*]控制帧:用于管理连接(如关闭连接、Ping/Pong 心跳机制)。

1.3 持久连接



[*] 全双工通信:一旦 WebSocket 连接创建,客户端和服务器可以随时发送消息,而无需重新创建连接。这种持久连接极大地减少了耽误,提高了实时性。
[*] 心跳机制:为了保持连接的活泼性,WebSocket 通常使用 Ping/Pong 帧,客户端和服务器会定期发送 Ping 帧以确认连接仍然有效。
2. WebSocket 与 HTTP 的区别

特性HTTPWebSocket连接范例无状态的哀求-相应模子持久的全双工连接创建连接每个哀求都需要创建和关闭连接仅在初始握手时创建一次连接数据传输单向哀求-相应双向实时传输数据格式文本格式二进制和文本格式性能每次哀求都需重新传输 HTTP 头部传输数据时只需较小的帧头适用场景得当哀求-相应的应用得当实时应用(如聊天、游戏) 6.多种方式实现数组去重、扁平化、对比优缺点

1. 数组去重

1.1 使用 Set

const uniqueArray = [...new Set(array)];
优点



[*]简洁明了。
[*]性能较好,时间复杂度为 O(n)。
缺点



[*]不支持对象的深度去重(只对根本数据范例有效)。
1.2 使用 filter 和 indexOf

const uniqueArray = array.filter((item, index) => array.indexOf(item) === index);
优点



[*]语法简朴,易读。
缺点



[*]性能较差,时间复杂度为 O(n^2),对于大数组效率低下。
1.3 使用 reduce

const uniqueArray = array.reduce((acc, item) => {
if (!acc.includes(item)) {
    acc.push(item);
}
return acc;
}, []);
优点



[*]灵活,得当复杂的去重逻辑。
缺点



[*]性能较差,时间复杂度为 O(n^2)。
1.4 使用对象或 Map

const uniqueArray = Object.keys(array.reduce((acc, item) => {
acc = true;
return acc;
}, {}));
优点



[*]性能较好,时间复杂度为 O(n)。
缺点



[*]只对根本数据范例有效,不支持对象深度去重。
2. 数组扁平化

2.1 使用 flat 方法(ES2019)

const flatArray = array.flat();
优点



[*]语法简朴,直接。
[*]支持指定层数的扁平化。
缺点



[*]仅在 ES2019 及以上版本可用,兼容性问题。
2.2 使用 reduce 递归

const flatten = (arr) => arr.reduce((acc, item) =>
Array.isArray(item) ? acc.concat(flatten(item)) : acc.concat(item), []);
优点



[*]可以实现深度扁平化。
缺点



[*]代码较长,大概影响可读性。
[*]在深层嵌套数组时大概导致栈溢出。
2.3 使用 forEach 递归

const flatten = (arr) => {
const result = [];
arr.forEach(item => {
    if (Array.isArray(item)) {
      result.push(...flatten(item));
    } else {
      result.push(item);
    }
});
return result;
};
优点



[*]代码清晰,易于明白。
缺点



[*]在深层嵌套数组时大概导致栈溢出。
2.4 使用 concat 和 apply

const flatten = (arr) => [].concat.apply([], arr);
优点



[*]简洁,得当一层的扁平化。
缺点



[*]仅得当一层,无法实现深度扁平化。
[*]性能较差,尤其在大数组中。
3. 总结

数组去重



[*]Set 和 对象/Map 方法 性能较好,语法简洁。
[*]filter 和 reduce 方法代码简朴,但性能较差,得当小数组。
数组扁平化



[*]flat 方法 是最简朴的选择,但兼容性较差。
[*]递归方法(reduce 和 forEach)得当深度扁平化,但在深层嵌套时大概导致性能问题。
[*]concat 和 apply 方法得当一层扁平化,代码简洁但性能较差。
7.JSON.parse和JSON.stringify

JSON.parse 和 JSON.stringify 是 JavaScript 中用于处置惩罚 JSON 数据的两个内置函数。
JSON.parse

JSON.parse() 函数用于将 JSON 字符串转换为 JavaScript 对象。
语法:
JSON.parse(text[, reviver])

[*]text: 一个符合 JSON 格式的字符串。
[*]reviver(可选): 一个函数,它可以在 JSON 字符串被转换为 JavaScript 对象后,对结果进行后处置惩罚。这个函数接受两个参数:属性的键和值。
示例:
const jsonString = '{"name": "Kimi", "age": 30}';
const obj = JSON.parse(jsonString);
console.log(obj.name); // 输出: Kimi 假如提供了 reviver 函数,它会对解析后的对象中的每个键值对进行处置惩罚:
const jsonString = '{"name": "Kimi", "age": 30}';
const obj = JSON.parse(jsonString, (key, value) => {
if (key === "age") {
    return value + 10; // 修改年龄
}
return value;
});
console.log(obj.age); // 输出: 40 JSON.stringify

JSON.stringify() 函数用于将 JavaScript 对象转换为 JSON 字符串。
语法:
JSON.stringify(value[, replacer[, space]])

[*]value: 要转换为 JSON 字符串的 JavaScript 值。
[*]replacer(可选): 一个函数或数组,用于过滤 JSON 中的值。

[*]函数接受两个参数:属性的键和值,并返回一个值,该值被转换为 JSON 字符串。
[*]数组包罗要包罗在 JSON 字符串中的属性名。

[*]space(可选): 一个数字或字符串,用于美化输出,即缩进。
示例:
const obj = { name: "Kimi", age: 30 };
const jsonString = JSON.stringify(obj);
console.log(jsonString); // 输出: {"name":"Kimi","age":30} 假如提供了 replacer 函数,它会决定哪些属性/对象应该被包罗在 JSON 字符串中:
const obj = { name: "Kimi", age: 30, city: "Shanghai" };
const jsonString = JSON.stringify(obj, (key, value) => {
if (key === "city") {
    return undefined; // 从 JSON 字符串中排除 'city'
}
return value;
});
console.log(jsonString); // 输出: {"name":"Kimi","age":30} 假如提供了 space 参数,可以美化输出:
const jsonString = JSON.stringify(obj, null, 2);
console.log(jsonString);
/*
输出:
{
"name": "Kimi",
"age": 30
}
*/ 注意事项



[*]JSON.parse 可以处置惩罚有效的 JSON 字符串,假如字符串不是有效的 JSON 格式,它会抛出 SyntaxError。
[*]JSON.stringify 只能序列化 JavaScript 的原生数据范例(如对象、数组、字符串、数字、布尔值等),不能序列化函数、undefined、循环引用等。
[*]通过 replacer 函数,你可以控制序列化过程,决定哪些属性被包罗或清除,以及如何转换值。
[*]space 参数不仅可以用于美化输出,还可以用于提高可读性或防止某些字符冲突。
8.多种方式实现深拷贝、对比优缺点

1. 使用 JSON 方法

代码示例

const deepCopy = (obj) => JSON.parse(JSON.stringify(obj));
优点



[*]简朴易用:实现非常简朴,得当快速应用。
[*]兼容性好:在大多数浏览器中均可使用。
缺点



[*]无法处置惩罚函数:会丢失对象中的方法。
[*]无法处置惩罚特别对象:如 Date、RegExp、Set、Map 等,均会被转化为平凡对象。
[*]无法处置惩罚循环引用:会导致错误。
2. 使用递归

代码示例

const deepCopy = (obj) => {
if (obj === null || typeof obj !== 'object') {
    return obj; // 基本类型直接返回
}

if (Array.isArray(obj)) {
    return obj.map(item => deepCopy(item)); // 处理数组
}

const copy = {};
for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy = deepCopy(obj); // 递归拷贝
    }
}
return copy;
};
优点



[*]灵活性高:可以处置惩罚多种数据范例,包括嵌套对象和数组。
[*]可以处置惩罚自定义对象:可以处置惩罚任何范例的对象。
缺点



[*]性能问题:对于深层嵌套的对象,大概导致性能下降。
[*]无法处置惩罚特别对象:如 Date、RegExp、Set、Map 等。
3. 使用 Lodash 库的 cloneDeep

代码示例

// 需要安装 lodash
const _ = require('lodash');
const deepCopy = (obj) => _.cloneDeep(obj);
优点



[*]功能强大:能够处置惩罚多种数据范例,包括函数、Date、RegExp、Set、Map 等。
[*]经过优化:性能相对较好,处置惩罚复杂对象时稳固。
缺点



[*]依靠第三方库:需要引入 Lodash,增加了项目标体积。
[*]学习曲线:对于不认识 Lodash 的开发者,大概需要时间去学习。
4. 使用 Object.assign(得当浅拷贝)

代码示例

const shallowCopy = (obj) => Object.assign({}, obj);
优点



[*]简朴易用:语法简朴,得当快速实现浅拷贝。
缺点



[*]仅得当浅拷贝:只拷贝第一层属性,嵌套对象仍为引用。
[*]无法处置惩罚数组及特别对象:假如对象中有数组或特别对象,无法正确深拷贝。
5. 使用 structuredClone(现代浏览器)

代码示例

const deepCopy = (obj) => structuredClone(obj);
优点



[*]内置函数:不需要引入库,直接使用。
[*]支持多种数据范例:可以处置惩罚 Date、Map、Set、ArrayBuffer 等。
缺点



[*]浏览器支持:现在仅在部门现代浏览器(如 Chrome 17+、Firefox 63+)中支持,大概存在兼容性问题。
6. 总结

适用场景和总结



[*]JSON 方法:得当简朴、无函数和特别对象的场景,代码简洁。
[*]递归方法:灵活性高,得当需要处置惩罚复杂嵌套对象的情况。
[*]Lodash 的 cloneDeep:得当需要深拷贝各种复杂范例对象的场景,功能强大。
[*]structuredClone:得当现代浏览器中需要处置惩罚多种数据范例的场景。
[*]Object.assign:仅得当浅拷贝,不得当深拷贝的场景。
9.手写函数柯里化工具函数、并明白其应用场景和上风

1. 手写柯里化函数

代码示例

function curry(fn) {
// 获取函数的参数数量
const arity = fn.length;

// 内部递归函数实现柯里化
function curried(...args) {
    // 如果传入的参数数量满足要求,调用原函数
    if (args.length >= arity) {
      return fn(...args);
    }
    // 否则,返回一个新函数,继续接收参数
    return (...next) => curried(...args, ...next);
}

return curried;
}
使用示例

// 一个简单的加法函数
function add(x, y, z) {
return x + y + z;
}

// 使用柯里化工具函数
const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3));// 6
console.log(curriedAdd(1, 2, 3));// 6
2. 应用场景

2.1 参数复用

柯里化可以让你创建一个新的函数,固定某些参数。如允许以避免重复传递雷同的参数。
const multiply = (x, y) => x * y;
const curriedMultiply = curry(multiply);

const double = curriedMultiply(2); // 固定第一个参数为 2
console.log(double(5)); // 10
2.2 函数组合

柯里化使得函数组合变得更加简朴。可以将多个小函数组合成一个复杂的函数。
const add = (x, y) => x + y;
const multiply = (x, y) => x * y;

const curriedAdd = curry(add);
const curriedMultiply = curry(multiply);

const incrementAndDouble = (num) => curriedMultiply(2)(curriedAdd(1)(num));
console.log(incrementAndDouble(3)); // 8
2.3 耽误执行

柯里化可以用于实现耽误执行,直到满足特定条件时才调用函数。
const logMessage = (level) => (message) => {
console.log(`[${level}] ${message}`);
};

const infoLogger = logMessage('INFO');
infoLogger('This is an info message.'); // This is an info message.
3. 上风



[*]提高可读性:柯里化使得代码更加清晰易懂,尤其在函数参数较多的情况下。
[*]增强灵活性:通过固定部门参数,可以创建更灵活的函数。
[*]便于测试:柯里化后的函数可以更容易地进行单元测试,因为它们吸收的参数较少。
[*]函数复用:可以通过柯里化创建多个特定的函数,重复使用雷同的逻辑。
10.手写防抖和节省工具函数、并明白其内部原理和应用场景

1. 防抖(Debouncing)

1.1 原理

防抖的核心思想是:在变乱被触发后,设定一个耽误时间,假如在这个时间内再次触发变乱,则重新计时。只有在变乱触发结束后的耽误时间内没有再次触发,才会执行回调函数。
1.2 代码示例

function debounce(func, delay) {
let timer;

return function (...args) {
    const context = this;

    clearTimeout(timer); // 清除之前的定时器
    timer = setTimeout(() => {
      func.apply(context, args); // 在延迟时间后执行回调
    }, delay);
};
}
1.3 应用场景



[*]输入框实时搜索:在用户输入时,防止每输入一个字符就发送哀求,而是在用户停止输入一段时间后再发送哀求。
[*]窗口调解变乱:在窗口大小调解时,防止频繁触发变乱,只有在用户停止调解后再执行回调。
2. 节省(Throttling)

2.1 原理

节省的核心思想是:规定在一定时间内只允许函数执行一次,无论该变乱触发多少次,回调函数只会在规定的时间间隔内被调用一次。
2.2 代码示例

function throttle(func, limit) {
let lastFunc;
let lastRan;

return function (...args) {
    const context = this;

    if (!lastRan) {
      func.apply(context, args); // 立即执行
      lastRan = Date.now(); // 记录执行时间
    } else {
      clearTimeout(lastFunc); // 清除之前的定时器
      lastFunc = setTimeout(() => {
      if (Date.now() - lastRan >= limit) {
          func.apply(context, args); // 在限制时间后执行
          lastRan = Date.now(); // 更新上次执行时间
      }
      }, limit - (Date.now() - lastRan));
    }
};
}
2.3 应用场景



[*]滚动变乱:在用户滚动页面时,限制滚动变乱的处置惩罚频率,避免性能问题。
[*]窗口调解变乱:在窗口大小调解时,限制回调执行的频率,防止性能瓶颈。
3. 总结

3.1 防抖与节省的区别

特性防抖(Debounce)节省(Throttle)触发时机变乱触发后耽误执行在规定时间间隔内只执行一次应用场景得当处置惩罚用户输入、搜索等需要耽误的操作得当处置惩罚高频率的变乱,如滚动、窗口调解 3.2 选择使用



[*]防抖:适用于希望在特定操作完成后再执行某个操作的场景。
[*]节省:适用于需要限制操作频率的场景,尤其是在高频变乱处置惩罚时。
11.实现一个sleep函数

1. 使用 Promise 实现 sleep 函数

代码示例

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
使用示例

你可以通过 async/await 语法来使用这个 sleep 函数:
async function demo() {
console.log("Start sleeping...");
await sleep(2000); // 暂停 2 秒
console.log("Awake after 2 seconds!");
}

demo();
2. 表明

2.1 如何工作



[*]Promise:sleep 函数返回一个 Promise 对象,Promise 的 resolve 方法在 setTimeout 的回调中被调用。
[*]setTimeout:setTimeout 用于在指定的毫秒数后执行回调函数,这里用来控制暂停的时间。
[*]async/await:通过 await 关键字,可以暂停 async 函数的执行,直到 Promise 被办理。
2.2 上风



[*]非阻塞:由于使用了 Promise 和 async/await,这个 sleep 函数不会阻塞主线程,其他代码仍然可以执行。
[*]易于使用:可以方便地与其他异步操作团结使用。
12.手动实现call、apply、bind

实现 call 方法

Function.prototype.call = function(context, ...args) {
// 若没有传入上下文或者传入的上下文为null,则默认上下文为window
if (typeof context !== 'object' && typeof context !== 'function') {
    context = window;
}

// 给 context 添加一个属性,值为函数本身,然后执行函数
const fnSymbol = Symbol(); // 使用Symbol保证不会重复
context = this;

// 执行函数
const result = context(...args);

// 删除刚才添加的属性
delete context;

// 返回执行结果
return result;
}; 实现 apply 方法

Function.prototype.apply = function(context, argsArray) {
// 若没有传入上下文或者传入的上下文为null,则默认上下文为window
if (typeof context !== 'object' && typeof context !== 'function') {
    context = window;
}

// 给 context 添加一个属性,值为函数本身,然后执行函数
const fnSymbol = Symbol();
context = this;

// 执行函数
let result;
if (argsArray) {
    result = context(...argsArray);
} else {
    result = context();
}

// 删除刚才添加的属性
delete context;

// 返回执行结果
return result;
}; 实现 bind 方法

Function.prototype.bind = function(context, ...args) {
// 保存原函数
const fn = this;

// 绑定函数
return function(...newArgs) {
    // 若传入的上下文为null或undefined,则默认上下文为window
    if (typeof context !== 'object' && typeof context !== 'function') {
      context = window;
    }

    // 如果原函数是构造函数,则无法使用bind
    if (this instanceof fn.bind) {
      return new fn(...args, ...newArgs);
    }

    // 执行原函数
    return fn.apply(context, args.concat(newArgs));
};
}; 表明



[*] call:

[*]call 方法允许你调用一个具有给定 this 值和单独给出的参数的函数。
[*]它立即执行函数。

[*] apply:

[*]apply 方法与 call 类似,但允许你以数组的情势传递参数。
[*]它立即执行函数。

[*] bind:

[*]bind 方法创建了一个新的函数,当被调用时,它的 this 被设置为提供的值,预置参数为给定的参数。
[*]它返回一个新的函数,而不是立即执行。

注意事项



[*]在实现 call 和 apply 时,需要考虑 context 为 null 或 undefined 的情况。
[*]在实现 bind 时,需要考虑 new 操作符的情况,确保绑定函数可以正确地作为构造函数使用。
[*]这些实现简化了原始方法的一些复杂性,比方处置惩罚原生函数和内置函数的特别行为。
 13.手写一个EventEmitter实现变乱发布、订阅


1. EventEmitter 实现

代码示例

class EventEmitter {
constructor() {
    this.events = {}; // 存储事件及其对应的回调函数
}

// 订阅事件
on(event, listener) {
    if (!this.events) {
      this.events = []; // 创建事件数组
    }
    this.events.push(listener); // 添加回调函数
}

// 发布事件
emit(event, ...args) {
    if (this.events) {
      this.events.forEach(listener => {
      listener(...args); // 调用所有回调函数
      });
    }
}

// 取消订阅
off(event, listener) {
    if (!this.events) return;
    this.events = this.events.filter(l => l !== listener); // 移除指定的回调函数
}

// 只监听一次的事件
once(event, listener) {
    const onceListener = (...args) => {
      listener(...args); // 调用原回调函数
      this.off(event, onceListener); // 取消订阅
    };
    this.on(event, onceListener); // 注册一次性回调
}
}
2. 使用示例

const emitter = new EventEmitter();

// 订阅事件
const greet = (name) => {
console.log(`Hello, ${name}!`);
};

emitter.on('greet', greet);

// 发布事件
emitter.emit('greet', 'Alice'); // Hello, Alice!
emitter.emit('greet', 'Bob');   // Hello, Bob!

// 取消订阅
emitter.off('greet', greet);
emitter.emit('greet', 'Charlie'); // 没有输出,因为回调已被取消

// 只监听一次的事件
emitter.once('onceEvent', (msg) => {
console.log(`This will be logged only once: ${msg}`);
});

emitter.emit('onceEvent', 'First Call'); // This will be logged only once: First Call
emitter.emit('onceEvent', 'Second Call'); // 没有输出
3. 总结

3.1 功能概述



[*]on:用于订阅变乱,将回调函数存储在变乱列表中。
[*]emit:用于发布变乱,调用所有与该变乱关联的回调函数,并传递参数。
[*]off:用于取消订阅,移除指定变乱的回调函数。
[*]once:用于只监听一次的变乱,回调函数在调用后主动取消订阅。
3.2 扩展功能



[*]变乱参数:可以通过 emit 方法传递参数给回调函数。
[*]多次订阅:同一个变乱可以被多个差别的回调函数订阅。
14.说出两种实现双向绑定的方案

1. Object.defineProperty 实现双向绑定

原理

通过 Object.defineProperty 方法,可以劫持对象的属性访问和赋值,进而实现数据变革时主动更新视图。
实现步骤


[*]劫持数据属性:使用 Object.defineProperty 为对象的属性定义 getter 和 setter。
[*]视图更新:在 setter 中添加视图更新的逻辑,当数据变革时触发更新。
优点



[*]兼容性好,适用于大多数浏览器。
[*]实现相对简朴。
缺点



[*]只能劫持对象的已有属性,无法相应动态添加的属性。
[*]对数组的操作处置惩罚较为复杂。
2. Proxy 实现双向绑定

原理

使用 Proxy 对对象进行代理,可以拦截对对象的根本操作,如读取和写入,提供更灵活的方式来实现双向绑定。
实现步骤


[*]创建 Proxy 对象:使用 Proxy 定义一个 handler,拦截 get 和 set 操作。
[*]更新视图:在 set 拦截中添加视图更新的逻辑,当数据变革时触发更新。
优点



[*]更灵活,可以拦截多种操作,包括数组操作和动态属性。
[*]代码简洁,易于维护。
缺点



[*]在较老的浏览器中支持较差。
[*]大概会增加一些性能开销。
15.手动实现双向绑定

HTML 结构

<div>
<input type="text" id="inputElement" />
<p>Value: <span id="textElement"></span></p>
</div> JavaScript 实现

// 获取 DOM 元素
const inputElement = document.getElementById('inputElement');
const textElement = document.getElementById('textElement');

// 初始化数据
let data = '';

// 观察者对象
const observer = {
subscribe(callback) {
    this._callbacks = callback;
},
notify() {
    if (this._callbacks) {
      this._callbacks();
    }
}
};

// 将数据和视图绑定
function bindData(dataInitialValue) {
data = dataInitialValue;

// 当输入框的内容改变时,更新数据
inputElement.addEventListener('input', (event) => {
    data = event.target.value;
    observer.notify();
});

// 更新视图
function updateView() {
    textElement.textContent = data;
}

// 订阅并更新视图
observer.subscribe(updateView);

// 初始更新视图
updateView();
}

// 初始化绑定
bindData('Initial Value'); 表明


[*] HTML 结构:

[*]包罗一个输入框和一个用于显示数据的段落。

[*] JavaScript 实现:

[*]获取输入框和段落的 DOM 元素。
[*]定义一个观察者对象,包罗订阅和关照的方法。
[*]定义 bindData 函数,用于绑定数据和视图。

[*] 观察者对象:

[*]subscribe 方法用于订阅回调函数。
[*]notify 方法用于关照所有订阅者数据已更新。

[*] 绑定数据和视图:

[*]当输入框的内容改变时,更新数据。
[*]定义一个 updateView 函数,用于更新视图。
[*]通过 observer.subscribe 方法订阅 updateView 函数。
[*]初始时调用 updateView 函数更新视图。

工作原理


[*] 初始化:

[*]调用 bindData 函数,初始化数据和视图。

[*] 输入框变革:

[*]当用户在输入框中输入文本时,触发 input 变乱。
[*]变乱处置惩罚函数更新数据,并关照观察者。

[*] 更新视图:

[*]观察者收到关照后,调用订阅的 updateView 函数。
[*]updateView 函数更新段落中的文本内容。

扩展

为了使其更加通用,我们可以创建一个更加灵活的双向绑定函数:
function twoWayBind(inputElement, viewElement, initialValue) {
let data = initialValue;

const observer = {
    subscribe(callback) {
      this._callbacks = callback;
    },
    notify() {
      if (this._callbacks) {
      this._callbacks();
      }
    }
};

inputElement.value = data;
viewElement.textContent = data;

function updateData(event) {
    data = event.target.value;
    observer.notify();
}

function updateView() {
    viewElement.textContent = data;
}

inputElement.addEventListener('input', updateData);
observer.subscribe(updateView);

updateView(); // 初始更新视图
}

const inputElement = document.getElementById('inputElement');
const textElement = document.getElementById('textElement');
twoWayBind(inputElement, textElement, 'Initial Value'); 表明


[*]通用函数:

[*]twoWayBind 函数接受输入框元素、视图元素和初始值。
[*]设置输入框和视图的初始值。
[*]定义 updateData 函数,用于更新数据并关照观察者。
[*]定义 updateView 函数,用于更新视图。
[*]为输入框添加 input 变乱监听器。
[*]初始更新视图。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 前端杂学录(八)