http 和 https
HTTP(超文本传输协议)是一种用于从服务器传输超文本到本地浏览器的应用层协议。它是一种请求-响应协议,在客户端用户代理(通常是一个 Web 浏览器)和作为信息资源服务的服务器之间进行通信。它是万维网(WWW)数据通信的基础,主要用于网页的加载。HTTP 遵循无状态协议,但通常通过使用 cookies 等机制在会话中保持状态。
HTTPS(安全的超文本传输协议)是一个用于安全通信的网络协议。这种协议的设计目的在于确保在用户与网站服务器之间传输的数据不会被第三方窃取或篡改。换句话说,它在 HTTP 的基础上通过加密的方式提供了数据传输的安全性保护。
HTTP 的工作机制简单来说可以概括为以下几个步骤
- 客户端请求(Request): 用户在浏览器中输入一个 URL 或点击一个链接后,浏览器就会向服务器发送一个 HTTP 请求。一个 HTTP 请求通常包含以下几个部分:请求行(包含方法 GET、POST 等,资源的 URL 和 HTTP 版本),请求头(包含一些客户端环境信息,如接受的内容类型等),以及可选的请求体(通常用于 POST 请求,包含要发送给服务器的数据)。
- 服务器响应(Response): 服务器收到客户端的请求后,会处理该请求,并返回一个 HTTP 响应。响应通常包含:状态行(包括 HTTP 版本,状态码,和状态说明),响应头(包含服务器信息,内容类型等),和响应体(实际请求的内容,就是客户端请求的网页或其它数据)。
- 建立连接: 在发送请求之前,客户端需要与服务器建立一个连接。在 HTTP/1.1 和更早的版本中,每个请求/响应对通常都需要一个新的 TCP 连接,这会导致额外的延迟。为了改善性能,HTTP/1.1 引入了持久连接(“keep-alive”),允许在一次连接中发送、接收多个请求/响应,而 HTTP/2 进一步引入了多路复用功能。
- 渲染: 一旦浏览器收到服务器的响应,它将解析该响应,并根据接收到的 HTML、CSS 和 JavaScript 内容渲染页面。
HTTPS 和 HTTP 的工作方式对比
- HTTP(超文本传输协议)是用于从服务器传输超文本到本地浏览器的传输协议。HTTP 采用明文方式发送数据,这意味着请求和响应未加密,容易遭受中间人攻击或其他安全威胁。
- HTTPS 则在 HTTP 的基础上加入了 SSL/TLS 协议,即安全套接层/传输层安全协议,用于在客户端和服务器之间建立一个加密的通信通道。在该通道中,数据传输前先被加密,到达接收方后再解密,增加了数据的隐私性和安全性。
具体过程如下:当用户试图通过 HTTPS 访问一个网站时,用户的浏览器首先会与网站服务器建立一个“握手”过程,在这个过程中,服务器会提供其 SSL 证书,该证书包含了服务器的公钥。浏览器通过一系列校验确保证书有效并属于真正的网站后,会生成一个对称加密的密钥,并用服务器的公钥加密发送给服务器。服务器收到之后用自己的私钥解密,获取对称加密密钥。此后,双方通过这个对称密钥进行加密通讯。
HTTP 与 HTTPS 的区别主要有以下 6 个特性:
特性 | HTTP | HTTPS |
1.安全性 | 不安全,数据以明文形式传输,易被第三方截取和查看 | 安全,使用 TLS/SSL 协议加密数据包,防止拦截和篡改 |
2.加密方式 | 无 | 使用 TLS/SSL 协议加密数据包 |
3.端口号 | 80 | 443 |
4.URL 标识 | 以 "http://" 开头 | 以 "https://" 开头 |
5.证书 | 不需要 | 需要 SSL 证书,由可信任的证书颁发机构(CA)颁发,用于验证服务器身份 |
6.性能 | 通常较高 | 可能略低,由于加密和解密过程带来一些计算开销,但随着技术进步,差异逐渐减小 |
TCP
TCP(Transmission Control Protocol,传输控制协议)是互联网协议套件中的一种核心协议,主要负责在计算机网络中提供可靠的、按顺序的、无差错的数据传输。TCP 是一个面向连接的协议,确保数据在传输过程中不会丢失、重复或乱序。
TCP 连接的建立过程
TCP 连接的建立过程称为“三次握手”(Three-Way Handshake),用于在客户端和服务器之间建立可靠的连接。
三次握手过程:客户端发送一个带有SYN标志的数据包给到服务端,服务端收到后,会传一个带有SYN/ACK标志的数据包给客户端表示确认,最后客户端再传一个带ACK标志的数据包给服务端表示连接成功。
1. 第一次握手(SYN)
-
客户端: 客户端发送一个 SYN(同步)报文段到服务器。这个报文段的标志位中 SYN 被设置为 1,表示客户端希望建立连接,并且包含一个初始的序列号。
Client -> Server: SYN, Seq = x
2. 第二次握手(SYN-ACK)
-
服务器: 服务器接收到客户端的 SYN 报文段后,回复一个 SYN-ACK(同步-确认)报文段。该报文段包含服务器自己的 SYN(表示接受连接请求)以及对客户端 SYN 的 ACK(确认)响应。ACK 的确认号是客户端的初始序列号加 1。
Server -> Client: SYN-ACK, Seq = y, Ack = x + 1
3. 第三次握手(ACK)
-
客户端: 客户端接收到服务器的 SYN-ACK 报文段后,发送一个 ACK(确认)报文段。ACK 报文段确认了服务器的 SYN,ACK 的确认号是服务器的序列号加 1。
Client -> Server: ACK, Seq = x + 1, Ack = y + 1
一旦这三次握手完成,客户端和服务器之间的 TCP 连接就建立起来了,双方可以开始数据传输。
TCP 连接的关闭过程
TCP 连接的关闭过程称为“四次挥手”(Four-Way Handshake),用于安全地终止连接。
四次挥手的过程:当客户端与服务器之间没有任何请求和响应传递了之后,就会开始发送关闭请求了,就会有4次挥手的过程。
简单理解过程就是:
- 客户端:我这里没有东西给你了,这次通话就这样吧。
- 服务端:收到!我再检查一下还有没有东西给你。
- 客户端:好的。
- 服务端:我这也没有东西给你了,咱可以关闭连接了。
- 客户端:好咧!
1. 第一次挥手(FIN)
-
客户端: 客户端发送一个 FIN(结束)报文段到服务器,表示客户端没有数据要发送了,但仍然可以接收数据。
Client -> Server: FIN, Seq = u
2. 第二次挥手(ACK)
-
服务器: 服务器接收到 FIN 报文段后,回复一个 ACK(确认)报文段,确认客户端的 FIN 报文段。此时,服务器进入 CLOSE_WAIT 状态,表示服务器已经确认客户端关闭连接请求,但可能还有数据要发送。
Server -> Client: ACK, Seq = v, Ack = u + 1
3. 第三次挥手(FIN)
-
服务器: 服务器发送一个 FIN 报文段给客户端,表示服务器也没有数据要发送了,并请求关闭连接。
Server -> Client: FIN, Seq = w
4. 第四次挥手(ACK)
-
客户端: 客户端接收到服务器的 FIN 报文段后,回复一个 ACK 报文段,确认服务器的 FIN 报文段。
Client -> Server: ACK, Seq = x + 1, Ack = w + 1
一旦这四次挥手完成,TCP 连接就完全关闭,客户端和服务器都可以释放连接资源。
TCP 连接的特点
-
面向连接: TCP 在传输数据之前必须建立一个连接。连接建立后,数据可以在连接中双向传输。
-
可靠性: TCP 提供数据的可靠传输,确保数据包的顺序和完整性。它通过重传机制和确认机制来实现这一点。
-
流量控制: TCP 使用流量控制机制(如滑动窗口)来防止发送方过快地发送数据,从而避免接收方的缓冲区溢出。
-
拥塞控制: TCP 还具有拥塞控制机制,以防止网络拥塞并调节数据传输速度。
-
全双工通信: TCP 支持全双工通信,允许数据在两个方向上同时传输。
总结
TCP 连接的建立和关闭过程保证了数据在网络中的可靠传输。三次握手确保双方能够建立有效的连接,而四次挥手则确保连接能够正确地关闭。TCP 的这些特性使得它成为互联网和许多网络应用中的核心协议,确保数据传输的可靠性和完整性。
TCP/IP
TCP/IP(Transmission Control Protocol/Internet Protocol)是互联网的基础通信协议套件,它定义了数据在网络上的传输方式,并确保不同类型的设备和系统可以互相通信。TCP/IP 协议栈分为多个层次,每个层次都执行特定的功能。下面详细介绍 TCP/IP 的各个方面。
TCP/IP 的分层模型
TCP/IP 模型通常分为四个层次(有时也称为五个层次),从高到低依次是:应用层、传输层、网络层、和链路层(数据链路层)。
1. 应用层(Application Layer)
- 功能:应用层提供用户与网络之间的接口,是网络服务和应用程序通信的层。常见的协议包括 HTTP、HTTPS、FTP、SMTP、DNS 等。
- 协议示例:
- HTTP/HTTPS:用于网页浏览和数据传输。
- FTP:用于文件传输。
- SMTP:用于电子邮件发送。
2. 传输层(Transport Layer)
- 功能:传输层负责端到端的通信管理和数据传输的完整性和可靠性。该层的主要协议有 TCP 和 UDP。
- 协议示例:
- TCP(Transmission Control Protocol):面向连接的协议,提供可靠的数据传输,保证数据包按序到达,并对丢失的数据包进行重传。
- UDP(User Datagram Protocol):无连接的协议,不保证数据包的顺序和可靠性,但速度快,适用于实时应用如视频流、在线游戏等。
3. 网络层(Internet Layer)
- 功能:网络层负责数据包的路由选择和转发,确定数据从源到目标的路径。该层的主要协议是 IP。
- 协议示例:
- IP(Internet Protocol):负责将数据包从源地址发送到目标地址,并进行路由选择。
- ICMP(Internet Control Message Protocol):用于发送网络层错误消息和操作信息,例如 Ping。
- ARP(Address Resolution Protocol):用于将 IP 地址转换为 MAC 地址。
4. 链路层(Link Layer)
- 功能:链路层处理设备之间的直接通信,包括帧的创建和传输、物理地址寻址等。它可以进一步分为逻辑链路控制(LLC)和介质访问控制(MAC)子层。
- 协议示例:
- Ethernet(以太网):用于局域网中的数据传输。
- Wi-Fi:无线局域网协议。
- PPP(Point-to-Point Protocol):点对点通信协议。
TCP/IP 工作原理
-
数据封装:
- 当应用程序发送数据时,数据会从应用层逐层向下传递,每层都会在数据前面添加对应层的协议头部信息,最后在链路层生成数据帧。
-
路由与转发:
- 网络层根据 IP 地址和路由表决定数据包的路径,将数据包从源地址传递到目标地址,经过多个路由器的中转。
-
数据解封装:
- 当数据包到达目的设备时,链路层开始解析数据帧并去除头部信息,然后依次向上传递到上层,直到应用层获得实际数据。
TCP 与 UDP 的比较
-
TCP:
- 优点:可靠、面向连接、数据包按序到达、错误检查和重传机制。
- 缺点:开销较大,速度相对较慢。
-
UDP:
- 优点:无连接、速度快、开销小,适合实时通信。
- 缺点:不保证数据包的顺序、没有重传机制,可能会出现丢包。
TCP/IP 的应用
- Web浏览:使用 HTTP/HTTPS 协议通过 TCP/IP 传输网页数据。
- 文件传输:通过 FTP 协议进行文件上传和下载。
- 电子邮件:SMTP 协议使用 TCP/IP 发送和接收电子邮件。
- 流媒体:通过 UDP 或者 TCP 传输音频、视频数据。
- 在线游戏:使用 UDP 进行实时数据传输,减少延迟。
TCP/IP 的重要性
TCP/IP 是支持现代互联网的基石,它提供了设备间可靠的数据通信手段,并且具备良好的扩展性,能够适应各种规模的网络。通过理解 TCP/IP 协议栈和它的工作原理,能够更好地设计、管理和优化网络应用及基础设施。
从输入Url到页面渲染发生了什么
1. 用户输入和浏览器解析
-
用户输入 URL: 用户在浏览器地址栏中输入 URL 并按下 Enter。
-
浏览器解析 URL: 浏览器解析 URL,将其分解为协议(如
http
或https
)、主机名(如www.example.com
)、路径(如/page
)、查询参数等。
2. DNS 解析
- 查找 IP 地址: 浏览器使用 DNS(域名系统)将域名(如
www.example.com
)解析为 IP 地址。这个过程可能涉及 DNS 缓存、递归查询等。
3. 建立连接
-
TCP 连接: 浏览器与服务器建立 TCP 连接。对于 HTTPS,还会进行 TLS/SSL 握手以确保安全通信。
-
三次握手: 在建立 TCP 连接之前,浏览器和服务器通过三次握手过程来确认连接的可靠性。
4. 发送 HTTP 请求
-
构建请求: 浏览器创建 HTTP 请求消息,包括请求方法(如 GET、POST)、请求头(如
User-Agent
、Accept
)和可能的请求体。 -
发送请求: 请求通过已建立的 TCP 连接发送到服务器。
5. 服务器处理请求
-
服务器接收请求: 服务器接收并解析请求,处理请求逻辑(如访问数据库、读取文件)。
-
生成响应: 服务器生成 HTTP 响应,包括状态码(如 200 OK、404 Not Found)、响应头和响应体(如 HTML 内容、JSON 数据)。
-
发送响应: 服务器将响应发送回浏览器。
6. 浏览器接收和解析响应
-
接收响应: 浏览器接收服务器的 HTTP 响应,解析响应头和响应体。
-
内容处理: 如果响应类型是 HTML,浏览器开始解析 HTML 文档,构建 DOM 树。
7. 构建 DOM 和 CSSOM
-
解析 HTML: 浏览器将 HTML 解析为 DOM(文档对象模型)树,表示页面结构和内容。
-
解析 CSS: 浏览器解析 CSS,构建 CSSOM(CSS 对象模型),表示样式规则。
8. 渲染页面
-
构建渲染树: 将 DOM 树和 CSSOM 结合起来,生成渲染树。这棵树包含了页面的可见部分及其样式。
-
布局计算: 计算每个元素的确切位置和大小,这个过程称为布局或回流(reflow)。
-
绘制: 将渲染树中的元素绘制到屏幕上,包括文本、图像和背景等。这个过程称为绘制(paint)。
-
合成: 将绘制后的页面合成到屏幕上,进行图层合成和优化,以提高渲染性能。
9. 执行 JavaScript
-
解析和执行: 如果页面包含 JavaScript,浏览器会解析并执行这些脚本。JavaScript 代码可能会修改 DOM 或 CSSOM,从而影响页面的布局和渲染。
-
事件处理: 处理用户交互事件(如点击、输入)并更新页面。
10. 浏览器优化和缓存
-
缓存: 浏览器可能会缓存 HTTP 响应和静态资源(如图片、CSS 文件、JavaScript 文件),以加快后续访问速度。
-
异步加载: 页面可能会异步加载更多资源(如 AJAX 请求),以提高用户体验。
总结
从用户输入 URL 到页面渲染的过程涵盖了多个阶段,包括 DNS 解析、TCP 连接、HTTP 请求和响应、浏览器解析和渲染等。每个阶段都涉及复杂的操作和优化,确保页面能够快速且正确地展示给用户。了解这些过程有助于前端开发人员优化性能和调试问题。
前端性能优化
- 防抖和节流
- 懒加载
- http缓存
- 减少对dom的操作
- 减少dom的重绘与回流
- React.memo: 用于优化函数组件的渲染,避免不必要的重新渲染
- useCallback 和 useMemo: 缓存函数和计算结果,避免每次渲染时都重新创建。
- 代码分割: 配置 Webpack 进行代码分割,按需加载模块和组件。
- 设置缓存头: 使用适当的缓存头(如
Cache-Control
、ETag
)来管理静态资源的缓存策略。 - 利用 Service Workers: 使用 Service Workers 进行离线缓存和后台同步,提高页面加载速度和离线体验。
- 异步加载 JavaScript: 使用
async
或defer
属性异步加载 JavaScript 文件,避免阻塞页面渲染。 - 避免不必要的计算: 将计算移出渲染过程,使用
useMemo
和useCallback
缓存计算结果和函数引用。 - 使用 CSS Sprites: 将多个小图标合并成一张图片,减少请求次数。
- 使用 SVG: 对于图标和简单图形,使用 SVG 替代位图,因为 SVG 通常更小。
什么是Event Loop(事件循环机制)
Event Loop(事件循环机制)是 JavaScript 中处理异步操作的核心机制,它使得 JavaScript 能够非阻塞地执行任务和响应事件。下面详细解释 Event Loop 的工作原理和相关概念:
1. JavaScript 执行模型
JavaScript 是单线程的,这意味着它只能同时执行一个任务。为了处理异步操作(例如 I/O 操作、定时器等),JavaScript 使用 Event Loop 机制来管理任务队列和执行上下文。
2. 执行栈(Call Stack)
执行栈是 JavaScript 用于管理函数调用的栈数据结构。它遵循“后进先出”(LIFO)原则:
- 当函数被调用时,它被压入执行栈。
- 当函数执行完成时,它从执行栈中弹出。
3. Web APIs
Web APIs 是浏览器提供的接口,例如 setTimeout
、XMLHttpRequest
、DOM 操作等。它们允许 JavaScript 执行一些异步操作。
4. 任务队列(Task Queue)
任务队列,也称为事件队列或消息队列,用于存储异步操作的回调函数。这些回调函数在 Web APIs 完成异步操作后被放入任务队列中,等待执行。
5. 事件循环(Event Loop)
事件循环是一个不断检查执行栈是否为空的机制。如果执行栈为空,事件循环会从任务队列中取出一个回调函数并将其推入执行栈,然后执行它。事件循环的主要作用是确保异步回调能够在主线程空闲时得到执行。
Event Loop 的工作流程
- 执行栈为空: 当执行栈为空时,事件循环会检查任务队列。
- 任务队列: 事件循环会从任务队列中取出第一个任务,并将其推入执行栈。
- 执行任务: 任务被执行后,事件循环会再次检查任务队列,直到队列为空。
- 继续循环: 事件循环持续运行,直到所有任务都被处理完毕。
示例
console.log('Start');
setTimeout(() => { console.log('Timeout'); }, 0);
console.log('End');
输出:
Start
End
Timeout
解释:
'Start'
和'End'
被立即打印,因为它们是同步操作,直接被压入执行栈并执行。setTimeout
的回调被传递给 Web APIs 进行处理,并且在指定时间后(在这个例子中是 0 毫秒)被放入任务队列。- 当执行栈为空时,事件循环会从任务队列中取出
setTimeout
的回调,并将其推入执行栈,最终打印'Timeout'
。
宏任务和微任务
宏任务包括:
setTimeout
setInterval
setImmediate
(在 Node.js 中)
微任务包括:
Promise.then
或Promise.catch
MutationObserver
queueMicrotask
(在一些现代浏览器中)
微任务的优先级高于宏任务,事件循环会在处理完当前的宏任务后,处理所有待处理的微任务。
示例
console.log('Start');
setTimeout(() => { console.log('Timeout'); }, 0); Promise.resolve().then(() => { console.log('Promise'); }); console.log('End');
输出:
Start
End
Promise
Timeout
解释:
'Start'
和'End'
立即被打印。Promise.resolve().then
的回调会被放入微任务队列,并且在当前宏任务完成后立即执行,打印'Promise'
。setTimeout
的回调被放入宏任务队列,最后被执行,打印'Timeout'
。
通过这种机制,JavaScript 能够有效地管理异步任务和事件,保持高效的单线程运行。
本地存储有哪些
在 Web 开发中,本地存储主要有以下几种方式:cookies
、localStorage
和 sessionStorage
。它们各自有不同的特点、优缺点以及适用场景。以下是它们的详细比较:
1. Cookies
特点:
- 存储大小: 每个 cookie 的最大尺寸通常为 4KB。
- 有效期: 可以设置到期时间,过期后自动删除。如果不设置,到会话结束时(即浏览器关闭)会被删除。
- 数据传输: 在每次 HTTP 请求时,都会将 cookie 发送到服务器,包括所有请求(如图像、CSS 文件等),可能会增加网络流量。
优点:
- 服务器访问: 适合存储需要服务器访问的数据(如会话 ID、身份验证信息)。
- 支持设置过期时间: 可以设置特定的过期时间或持续时间。
缺点:
- 大小限制: 存储空间有限(约 4KB)。
- 性能开销: 每次请求都要发送 cookie 数据,可能会影响性能。
- 安全性: 如果没有适当设置
Secure
和HttpOnly
属性,容易受到 XSS 攻击。
适用场景:
- 服务器需要访问的数据,如用户身份验证、会话 ID。
2. localStorage
特点:
- 存储大小: 通常为 5-10MB,具体取决于浏览器。
- 有效期: 数据永久保存,除非显式删除或清空。关闭浏览器或重新启动计算机不会影响存储的数据。
- 数据传输: 不会随每个 HTTP 请求发送,只在客户端存储。
优点:
- 存储容量大: 相比于 cookies,
localStorage
提供了更大的存储空间。 - 持久性: 数据在浏览器关闭后依然保留。
- 简单的 API: 操作简单,直接使用
localStorage.setItem
和localStorage.getItem
进行存取。
缺点:
- 跨域访问: 只能在同一域名下访问。
- 安全性: 数据可以通过 JavaScript 访问,易受 XSS 攻击。
适用场景:
- 长期存储数据,如用户设置、长期保存的数据等。
3. sessionStorage
特点:
- 存储大小: 通常为 5-10MB,具体取决于浏览器。
- 有效期: 数据在当前会话期间有效。会话结束(如关闭标签页或浏览器窗口)后,数据会被删除。
- 数据传输: 不会随每个 HTTP 请求发送,只在客户端存储。
优点:
- 存储容量大: 提供比 cookies 更大的存储空间。
- 会话级别的持久性: 数据仅在当前会话(即窗口或标签页)中有效,适用于需要临时存储的数据。
- 简单的 API: 与
localStorage
类似,使用sessionStorage.setItem
和sessionStorage.getItem
进行操作。
缺点:
- 会话失效: 关闭浏览器或标签页后,数据会丢失。
- 安全性: 数据可以通过 JavaScript 访问,易受 XSS 攻击。
适用场景:
- 临时存储数据,如表单数据、多步骤表单的中间状态等。
总结
存储方式 | 存储容量 | 有效期 | 数据传输 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|---|---|
Cookies | ~4KB | 可设置过期时间 | 每个 HTTP 请求 | 服务器访问、支持过期时间 | 存储空间有限、性能开销、安全性较低 | 服务器需要访问的数据、身份验证信息 |
localStorage | 5-10MB | 永久保存 | 不发送 | 大存储容量、持久性、简单 API | 数据可通过 JavaScript 访问、易受 XSS 攻击 | 长期保存的数据、用户设置 |
sessionStorage | 5-10MB | 当前会话 | 不发送 | 大存储容量、会话级别持久性、简单 API | 会话结束后数据丢失、易受 XSS 攻击 | 临时数据、表单中间状态 |
根据具体需求选择合适的存储方式,可以提高 Web 应用的性能和用户体验。
什么是BFC?
BFC(Block Formatting Context,块级格式化上下文) 是 CSS 规范中的一个重要概念,它定义了一个区域内的元素如何进行布局。BFC 是浏览器处理块级盒子的一种方式,用于管理元素之间的布局、定位和浮动等方面。
触发 BFC 的条件: 以下情况会创建一个新的 BFC(即触发 BFC):
float
属性的值不为none
(如float: left
或float: right
)。position
属性的值为absolute
或fixed
。display
属性的值为flex、inline-block
、block
、list-item
、table-cell、table等
overflow
属性的值不为visible
(如overflow: hidden
、overflow: auto
)。display: flow-root
(在 CSS3 中引入,用于强制生成 BFC)。
BFC 的特性
-
块级盒子的垂直 margin 合并: 在 BFC 内部,相邻的块级元素的垂直外边距(margin)会合并,而在 BFC 外部则不会。
-
浮动元素: BFC 会包含所有浮动的元素,不会影响外部元素的布局。BFC 内的元素可以清除其内部浮动元素的影响。
-
元素的布局不会影响外部: 在 BFC 内部的元素的定位、大小和布局不会影响到外部的元素,这样可以避免一些布局问题(如浮动元素的覆盖问题)。
-
清除浮动: 使用 BFC 可以清除浮动元素的影响,以避免浮动元素超出容器边界或容器高度被挤压成 0 的问题。
BFC 的应用实例
-
清除浮动: 在浮动元素之后创建一个新的 BFC 可以防止浮动元素影响到后续的布局。
<style> .container { overflow: hidden; /* 创建一个新的 BFC */ } .float-element { float: left; width: 100px; height: 100px; background-color: red; } </style> <div class="container"> <div class="float-element"></div> <p>浮动元素后的内容</p> </div>
-
解决垂直 margin 合并问题: 通过设置 BFC,可以避免垂直 margin 的合并。
<style> .box { overflow: hidden; /* 创建 BFC */ } .box div { height: 100px; background-color: blue; } .box p { height: 50px; background-color: green; } </style> <div class="box"> <div></div> <p></p> </div>
总结
BFC 是一个重要的 CSS 机制,用于管理和控制元素的布局行为。理解和合理使用 BFC 可以帮助解决复杂的布局问题,并使 Web 页面布局更加稳定和可控。通过触发 BFC,可以清除浮动影响、避免 margin 合并、以及更好地控制布局容器的行为。
Get请求 和 Post请求的区别
下面的表格比较了两种 HTTP 方法:GET 和 POST。
GET | POST | |
---|---|---|
后退按钮/刷新 | 无害 | 数据会被重新提交(浏览器应该告知用户数据会被重新提交)。 |
书签 | 可收藏为书签 | 不可收藏为书签 |
缓存 | 能被缓存 | 不能缓存 |
编码类型 | application/x-www-form-urlencoded | application/x-www-form-urlencoded or multipart/form-data。为二进制数据使用多重编码。 |
历史 | 参数保留在浏览器历史中。 | 参数不会保存在浏览器历史中。 |
对数据长度的限制 | 有限制。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。 | 无限制。 |
对数据类型的限制 | 只允许 ASCII 字符。 | 没有限制。也允许二进制数据。 |
安全性 | 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。 在发送密码或其他敏感信息时绝不要使用 GET ! | POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。 |
可见性 | 数据在 URL 中对所有人都是可见的。 | 数据不会显示在 URL 中。 |
JS数据类型
-
8种数据类型 Number、String、Boolean、Null、Undefined、Object、Symbol、BigInt
- ES5中确定的是前6种类型
- 在ES6中加入了一种新的数据类型: Symbol
- 在ES11中加入了一种新的数据类型: BigInt
-
基本数据类型:Number、String、Boolean、Null、Undefined、Symbol、BigInt
- BigInt和Number的区别
-
在JS中Number是双精度浮点数,精确表示的最大整数是 2^53, 这也就导致了运算时的一些精度问题
-
BigInt 可以表示任意大的整数
BigInt(value)
也可以是用 在一个整数后面加n的方式定义一个BigInt, 比如: 10n -
BigInt 和 Number 不是严格相等的,但是宽松相等的。
10n === 10 // false 10n == 10 // true
-
引用数据类型:Object
- 包含Function、Array、Date
-
如何判断数据类型
- typeof
typeof 1 // "number" typeof "1" // "string" typeof [] // "object" typeof function() {} // "function" typeof {} // "object" typeof Symbol("") // "symbol" typeof BigInt(1) // "bigint" typeof null // "object" typeof undefined // "undefined" typeof NaN // "number"
- Object.prototype.toString.call
const isType = value => { return Object.prototype.toString.call(value).slice(8, -1) } isType(1) // "Number" isType("1") // "String" isType([]) // "Array" isType(function() {}) // "Function" isType({}) // "Object" isType(Symbol("")) // "Symbol" isType(BigInt(1)) // "Bigint" isType(null) // "Null" isType(undefined) // "Undefined" isType(NaN) // "Number"
- instanceof 并不能准确判断 如:
[] instanceof Array // true
,[] instanceof Object // true
- Array.isArray 可以判断数组
this指向问题
- 在函数的简单中,严格模式下(use strict),函数内的this会被指向undefined,在非严格模式下,this会指向window/global
- 使用new方法调用的构造函数,构造函数内的this一般指向到创建的对象上
- 函数通过call/apply/bind方法调用后,函数内的this会指向到传入的指定参数的对象上
- 在上下文对象调用函数时,函数内的this一般都指向该对象
- 箭头函数中的this,一般都是由外层作用域继承而来(函数体,对象,或者全局window/global)
原型链
当访问一个对象的属性时,先在对象的本身找,找不到就去对象的原型上找,如果还是找不到,就去对象的原型(原型也是对象,也有它自己的原型)的原型上找,如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回undefined。 这条由对象及其原型组成的链就叫做原型链。
继承
1. 原型链继承
实现方法
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
};
function Dog(name) {
Animal.call(this, name); // 调用父类构造函数
}
Dog.prototype = Object.create(Animal.prototype); // 继承父类原型
Dog.prototype.constructor = Dog; // 修正构造函数指向
Dog.prototype.speak = function() {
console.log(`${this.name} barks.`);
};
const dog = new Dog('Rex');
dog.speak(); // 输出: Rex barks.
function Parent(){
this.color = 'red'
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(){}
// 将父类的实例赋给子类的原型
Child.prototype = new Parent();
new Child();
优点
- 简单直接:容易理解和实现,特别是在没有复杂需求时。
- 原型链的自然继承:通过原型链的机制,子类可以直接访问父类的属性和方法。
缺点
- 构造函数无法继承:父类的构造函数不会被调用(除非手动调用),可能导致初始化问题。
- 父类属性共享:所有实例共享父类的原型属性和方法,可能导致数据污染。
2. 构造函数继承
实现方法
function Animal(name) {
this.name = name;
}
function Dog(name) {
Animal.call(this, name); // 通过调用父类构造函数实现继承
}
Dog.prototype.speak = function() {
console.log(`${this.name} barks.`);
};
const dog = new Dog('Rex');
dog.speak(); // 输出: Rex barks.
function Parent(name){
this.name = name;
this.colors = ['red','green','pink'];
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(value){
//使用call()方法继承父类构造函数中的属性
Parent.call(this, value);
this.age = '18';
}
new Child();
优点
- 私有属性继承:子类可以继承父类的实例属性。
- 构造函数的调用:父类的构造函数会被正确调用,实例化时属性被正确设置。
缺点
- 方法无法继承:父类的方法不会被继承到子类的原型链上,需要在子类中重新定义。
- 无法实现多继承:构造函数继承不支持多继承。
3. 组合继承
实现方法
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
};
function Dog(name) {
Animal.call(this, name); // 调用父类构造函数
}
Dog.prototype = Object.create(Animal.prototype); // 继承父类原型
Dog.prototype.constructor = Dog; // 修正构造函数指向
Dog.prototype.speak = function() {
console.log(`${this.name} barks.`);
};
const dog = new Dog('Rex');
dog.speak(); // 输出: Rex barks.
function Parent(){
this.color = 'red'
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(name){
Parent.call(this, name);
}
Child.prototype = new Parent();
new Child();
优点
- 综合了原型链继承和构造函数继承:子类可以继承父类的属性和方法。
- 解决了构造函数和原型链的问题:通过组合方式解决了构造函数无法继承的问题。
缺点
- 多次调用父类构造函数:在创建实例时,父类构造函数会被调用两次(一次在子类构造函数中,一次在
Object.create
中),可能影响性能。
4. 寄生组合继承
实现方法
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
};
function Dog(name) {
Animal.call(this, name); // 调用父类构造函数
}
// 创建一个临时构造函数
function TempConstructor() {}
TempConstructor.prototype = Animal.prototype;
// 设置子类的原型对象为临时构造函数的一个实例
Dog.prototype = new TempConstructor();
Dog.prototype.constructor = Dog; // 修正构造函数指向
// 在子类原型上定义方法
Dog.prototype.speak = function() {
console.log(`${this.name} barks.`);
};
const dog = new Dog('Rex');
dog.speak(); // 输出: Rex barks.
function Parent(){
this.color = 'red'
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(name){
Parent.call(this, name);
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
emumerable: false,
writable: true,
configurable: true
}
});
new Child();
优点
- 解决了构造函数调用多次的问题:寄生组合继承只调用一次父类构造函数,避免了组合继承中存在的性能开销。
- 原型链清晰:通过使用临时构造函数来设置子类原型,保证了子类和父类原型之间的正确关系。
缺点
- 代码复杂性:实现相较于简单的原型链继承和构造函数继承稍显复杂。
- 兼容性:虽然在现代 JavaScript 环境中,寄生组合继承已被广泛使用,但它仍然需要手动处理原型链的细节。
5. ES6 Class 继承
实现方法
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
const dog = new Dog('Rex');
dog.speak(); // 输出: Rex barks.
class Parent {
constructor(name) {
this.name = name;
}
getName() {
console.log(this.name)
}
}
class Child extends Parent {
constuctor(name) {
// super 相当于是 Parent.call(this, name)
super(name)
}
}
new Child()
优点
- 语法简洁:ES6 提供的
class
语法使得继承的实现更加直观和简洁。 - 支持 super:
super
关键字用于调用父类的方法和构造函数,便于管理继承层次。
缺点
- 语法糖:虽然语法上更简洁,但实际上它仍然是基于原型链的继承机制。
- 较新的标准:在旧版本的 JavaScript 环境中可能不被支持(但现代浏览器和 Node.js 都已支持)。
每种方法有其适用的场景和优缺点,选择合适的继承方式取决于项目的需求和代码的复杂性。
6.JS中创建对象的多种方式及优缺点
在 JavaScript 中,有多种方式可以创建对象,每种方式都有其优缺点。以下是一些常见的方法:
1. 原型模式
代码示例
const person = {
name: 'John',
greet() {
console.log(`Hello, my name is ${this.name}`);
}
};
const alice = Object.create(person);
alice.name = 'Alice';
alice.greet(); // 输出: Hello, my name is Alice
优点
- 简单易用:通过原型链直接继承,创建新对象时不需要重复定义方法。
- 共享方法:多个对象可以共享原型上的方法,节省内存。
缺点
- 原型链复杂:复杂的原型链可能会导致调试困难。
- 不能添加私有属性:所有的属性都是公共的,难以管理对象的私有数据。
2. 构造函数模式
代码示例
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
const john = new Person('John');
john.greet(); // 输出: Hello, my name is John
优点
- 清晰的构造函数:通过构造函数创建对象,适用于需要构造函数的场景。
- 支持私有属性:可以在构造函数中定义私有属性。
缺点
- 重复方法定义:如果在构造函数内部定义方法,每个实例都会创建一个方法副本,浪费内存。
this
指向问题:this
的使用需要谨慎,以避免上下文丢失。
3. 组合模式(组合构造函数和原型模式)
代码示例
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
function Employee(name, job) {
Person.call(this, name); // 继承 Person 构造函数
this.job = job;
}
Employee.prototype = Object.create(Person.prototype); // 继承 Person 原型
Employee.prototype.constructor = Employee; // 修正构造函数
Employee.prototype.describeJob = function() {
console.log(`${this.name} works as a ${this.job}`);
};
const jane = new Employee('Jane', 'Developer');
jane.greet(); // 输出: Hello, my name is Jane
jane.describeJob(); // 输出: Jane works as a Developer
优点
- 综合优势:结合了构造函数的私有属性和原型模式的共享方法。
- 代码复用:可以继承和重用父类的属性和方法,同时在子类中添加新的功能。
缺点
- 复杂性:实现较复杂,需要理解构造函数和原型链的关系。
- 性能问题:创建新对象时可能会有性能开销,特别是在多个层次的继承中。
4. ES6 类(Class)
代码示例
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
class Employee extends Person {
constructor(name, job) {
super(name); // 调用父类构造函数
this.job = job;
}
describeJob() {
console.log(`${this.name} works as a ${this.job}`);
}
}
const jane = new Employee('Jane', 'Developer');
jane.greet(); // 输出: Hello, my name is Jane
jane.describeJob(); // 输出: Jane works as a Developer
优点
- 简洁语法:ES6 的
class
语法提供了更加简洁和易读的方式来创建对象和继承。 - 支持 super:可以使用
super
关键字调用父类的方法和构造函数。
缺点
- 语法糖:虽然语法上更简洁,但底层仍然是基于原型链的继承机制。
- 兼容性:在旧版 JavaScript 环境中可能需要使用 Babel 等工具进行转译。
5. 工厂函数
实现方法
function createPerson(name, age) { return { name, age, greet() { console.log(`Hello, my name is ${this.name}.`); } }; } const person = createPerson('Alice', 30);
优点
- 无需
new
关键字:避免了new
操作符的复杂性。 - 灵活性高:可以根据需要返回不同结构的对象。
缺点
- 每次创建对象都会重复定义方法:每次调用工厂函数都会创建一个新的对象副本,可能导致内存使用问题。
6. ES6 Object.assign()
实现方法
const personPrototype = { greet() { console.log(`Hello, my name is ${this.name}.`); } }; const person = Object.assign(Object.create(personPrototype), { name: 'Alice', age: 30 });
优点
- 组合多个对象:可以将多个对象的属性合并到一个对象中,方便创建对象。
- 支持原型链继承:结合
Object.create()
使用时,支持创建有原型链的对象。
缺点
- 合并属性可能导致冲突:如果多个对象有相同的属性,后面的对象属性会覆盖前面的对象属性。
- 难以管理复杂对象:在复杂场景下,可能需要额外的管理。
7. 对象字面量
实现方法
const person = {
name: 'Alice',
age: 30,
greet() {
console.log(`Hello, my name is ${this.name}.`);
}
};
优点
- 简洁易读:语法简洁,直观易懂。
- 快速定义:适用于创建简单的对象实例。
- 支持方法:可以直接在对象字面量中定义方法。
缺点
- 不适用于复杂继承:对象字面量无法处理复杂的继承或构造函数需求。
- 每次创建都需重复定义:如果需要创建多个类似的对象,可能会需要重复定义相同的结构。
总结
- 原型模式:适合简单的对象创建和继承,优点是内存节省,但缺乏私有属性和复杂的原型链问题。
- 构造函数模式:适合需要使用构造函数创建对象的场景,支持私有属性,但需要管理内存和
this
问题。 - 组合模式:适合需要组合私有属性和共享方法的场景,提供了灵活的继承机制,但实现复杂。
- ES6 类:提供了现代、简洁的语法来创建对象和继承,适用于最新的 JavaScript 环境和代码风格。
- 简单对象:使用对象字面量。
- 需要多个实例:使用构造函数或
class
语法。 - 灵活的原型链继承:使用
Object.create()
。 - 避免
new
关键字的复杂性:使用工厂函数。 - 合并多个对象的属性:使用
Object.assign()
。
7.基本类型和引用类型
基本类型的值是不可变的,存储在栈内存中。基本类型的数据在进行赋值时会创建一个新的副本,修改副本不会影响原始值。JavaScript 的基本类型包括:
- Number:表示数字,包括整数和浮点数。例如:
42
,3.14
。 - String:表示文本字符串。例如:
'Hello'
,"World"
。 - Boolean:表示布尔值,即
true
或false
。 - Undefined:表示变量未定义的状态。一个声明了但未赋值的变量的默认值。
- Null:表示一个“空”或“无”值,通常用于表示意图为空的变量。
- Symbol:表示唯一且不可变的值,主要用于对象属性的标识符。
- BigInt:表示大于
Number
类型范围的整数值,用于处理超大整数。
引用类型(Reference Types)
引用类型的值是可变的,在栈中存放指针,在堆中存放内容。引用类型的数据在进行赋值时并不会创建新的副本,而是将引用的内存地址传递给新的变量。因此,修改一个引用类型的值会影响所有引用到该值的变量。JavaScript 的引用类型包括:
- Object:通用的数据结构,用于存储键值对。例如:
{ name: 'John', age: 30 }
。 - Array:表示有序集合的数据结构。例如:
[1, 2, 3]
。 - Function:表示函数对象,是对象的一种特殊类型。
- Date:表示日期和时间。
- RegExp:表示正则表达式。
- Map、Set:ES6 引入的新数据结构,用于存储键值对和唯一值的集合。
基本类型和引用类型的区别
-
存储方式:
- 基本类型:存储在栈内存中,值是不可变的。
- 引用类型:存储在堆内存中,变量存储的是对堆内存中对象的引用。
-
赋值行为:
- 基本类型:赋值操作会创建一个新的副本,修改副本不会影响原值。
- 引用类型:赋值操作仅复制引用,修改对象会影响所有引用该对象的变量。
-
比较方式:
- 基本类型:比较值是否相等。
- 引用类型:比较引用的内存地址是否相等(即是否指向同一个对象)。
-
可变性:
- 基本类型:值不可变。
- 引用类型:值可变(对象的属性和内容可以改变)。
总结
- 基本类型:简单且不可变的数据类型,赋值时会复制值本身。
- 引用类型:复杂且可变的数据类型,赋值时会复制引用,修改会影响所有引用的变量。
浅拷贝方式
- Object.assign
Object.assign
只是拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝 - ES6中的展开运算符
...
深拷贝方式
- 使用
JSON.parse(JSON.stringfy(Object))
这种方式简单粗暴,但是对象内存在函数、undefined
、symbol
时不能使用 - 自己实现深拷贝函数方法 - 递归实现
loadsh._cloneDeep(Object)
方法
栈内存与堆内存的对比
特性 | 栈内存 | 堆内存 |
---|---|---|
存储方式 | 先进后出(LIFO) | 动态分配 |
管理方式 | 自动分配和释放 | 程序员管理或垃圾回收机制管理 |
生命周期 | 函数调用期间有效 | 直到显式释放或垃圾回收 |
访问速度 | 较快 | 较慢 |
数据类型 | 基本类型,局部变量 | 引用类型(对象、数组等) |
内存大小 | 通常较小 | 通常较大 |