今天我们把前两天复习的浏览器原理(跨域、安全、进程模型、渲染优化)和手写题结合起来,深入探讨 网络请求 这个核心话题,并手写一个基于 Promise 的 AJAX 封装,从简单到进阶,彻底搞懂前端如何与服务端通信。


Q4:什么是跨域?如何解决跨域问题?

我的回答:

  • 同源策略:协议、域名、端口完全一致才允许访问,否则跨域。
  • 解决方式:
  1. 后端设置 Access-Control-Allow-Origin,可以是 * 或指定域名。
  2. 前端设置代理,如请求 /api 由代理转发到目标接口。

补充与深化:

1. 同源策略的细节

  • 浏览器出于安全考虑,限制跨域请求,但某些标签不受限:<script src>、<img>、<link>、<iframe> 等可以加载跨域资源,但不能读取响应内容。
  • 跨域限制主要是阻止恶意网站读取另一个网站的敏感数据,但允许发送请求(如表单提交)只是无法读取响应。

2. CORS(跨域资源共享)

后端设置头部是 CORS 的核心,但还有一些细节:

  • 简单请求(无预检,直接发):满足特定条件(如 GET/POST、Content-Type 为 application/x-www-form-urlencoded 等),浏览器直接发送请求,通过 Origin 头告知来源,服务器返回 Access-Control-Allow-Origin。
  • 预检请求:对于复杂请求(如自定义头、PUT/DELETE),浏览器会先发 OPTIONS 请求,服务器返回允许的 Access-Control-Allow-Methods、Access-Control-Allow-Headers 等,确认后再发真实请求。
  • 还可以设置 Access-Control-Allow-Credentials 允许携带 Cookie,此时 Access-Control-Allow-Origin 不能为 *,必须指定具体域名。

3. 代理方案

“前端设置代理”是开发环境常用方案:

  • Webpack DevServer 配置 proxy,将 /api 请求转发到目标服务器,绕过浏览器的同源策略。
  • 生产环境常用 Nginx 反向代理,统一入口,避免跨域。
  • 本质是让浏览器请求同源地址,由服务器代理转发。

Q5:常见的 web 前端攻击有哪些?如何防范?

我的回答:

  • XSS:注入脚本,获取用户信息或监控操作,通过验证输入解决。
  • CSRF:注入请求引导用户点击,通过同源策略、加强验证、双 token 解决。

补充和优化:

1. XSS(跨站脚本攻击)

  • 原理:攻击者在目标网站注入恶意脚本,当用户访问时执行,窃取 Cookie、会话令牌或进行其他操作。
  • 防范:
  • 输入过滤:对用户输入进行严格过滤(如使用白名单)。
  • 输出转义:在 HTML、JS、CSS 上下文中对特殊字符转义(如 &lt;、&gt;)。
  • 使用 CSP(内容安全策略):限制页面可以加载的资源来源,即使注入脚本也无法执行。
  • Cookie 设置 HttpOnly:防止脚本读取 Cookie。

2. CSRF(跨站请求伪造)

  • 原理:攻击者诱导用户访问恶意网站,该网站利用用户的登录状态,向目标网站发起伪造请求(如修改密码、转账)。
  • 防范:
  • 同源检查:验证请求的 Referer 或 Origin 是否合法,但可能被伪造。
  • CSRF Token:服务器生成随机 token 存在 session,表单提交时携带,服务器验证。
  • SameSite Cookie:设置 Cookie 的 SameSite 属性为 Strict 或 Lax,限制跨站请求携带 Cookie。
  • 验证码:敏感操作强制要求用户输入验证码。
  • 二次验证:如支付密码。

Q6:浏览器的进程/线程模型是怎样的?

我的回答:

  • 主进程:解析 HTML、预解析 CSS/JS。
  • GPU 进程:操作光栅化、像素。

补充与深化:

  • 主进程(Browser Process):负责浏览器界面(地址栏、书签)、网络请求、文件访问、跨进程通信等,不负责页面渲染。
  • 渲染进程(Renderer Process):每个标签页一个独立进程(部分站点可能共享),负责页面渲染、JS 执行、事件处理等。内部包含多个线程:
  1. GUI 渲染线程:解析 HTML/CSS、构建 DOM/CSSOM、布局、绘制。
  2. JS 引擎线程:执行 JS 代码,与 GUI 线程互斥(JS 执行时会阻塞渲染)。
  3. 事件触发线程:管理事件队列,当事件触发时回调 JS 引擎。

  4. 定时器线程:处理 setTimeout、setInterval,计时结束后将回调放入事件队列。

  5. 异步 HTTP 请求线程:处理 AJAX 请求,请求完成后将回调放入事件队列。

  • GPU 进程:处理 GPU 任务,如 3D 绘制、光栅化、合成图层,提升渲染性能。
  • 网络进程:负责网络资源加载。
  • 插件进程:每个插件一个进程,保证安全。

为什么是多进程

  • 安全隔离:每个标签页独立进程,一个崩溃不影响其他。
  • 性能利用:多进程可充分利用多核 CPU。
  • 保护隐私:不同站点隔离。

Q7:你对浏览器渲染层优化的理解?

我的回答:

  • 通过 will-change 进行分层,以及 transform 代替 margin 等。

补充与深化:

1. 为什么分层?

  • 页面某些元素变化时,如果未分层,可能触发整个页面重绘。分层后,只重绘变化层,然后合成,提升性能。
  • 浏览器会自动将某些元素提升为合成层(如 video、canvas、transform 动画等),也可以通过 will-change 提示浏览器提前优化。

2. 如何创建合成层?

  • transform: translateZ(0) 或 transform: translate3d(0,0,0) 强制使用硬件加速。
  • opacity 动画也会产生合成层。
  • will-change: transform, opacity 等属性提前告知浏览器可能变化,但不要滥用。

3. 优化原则

  • 减少重排和重绘:用 transform 替代 top/left(因为 transform 在合成层处理,不触发布局和绘制)。
  • 避免强制同步布局:如先读 offsetHeight 再写样式,会导致浏览器立即计算布局,应分离读写。
  • 使用 requestAnimationFrame 做动画:保证在每一帧开始前更新,避免丢帧。
  • 减少图层数量:过多的图层会增加内存和合成开销。

手写题:用 Promise 封装 AJAX

今天的手写题是将理论付诸实践——封装一个基于 Promise 的 AJAX 函数,既能复习网络请求,又能巩固 Promise 用法。

我的代码(原生 XHR 版)

// 原生ajax版本
function ajax({ url, method = "GET", data }) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();

    xhr.open(method, url, true);

    xhr.send(data);

    // 监听
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4 && xhr.status === 200) {
        try {
          const result = JSON.parse(xhr.responseText);
          resolve(result);
        } catch (e) {
          resolve(xhr.responseText);
        }
      } else if (xhr.readyState === 4 && xhr.status !== 200) {
        reject("error");
      }
    };
  });
}

结合 jQuery 的封装(简化版)

// 结合 jQuery 的封装(简化版)
function ajax({ url, type = "get", data }) {
  return new Promise((resolve, reject) => {
    $.ajax({
      url: url,
      type: type,
      data: data,
      success(res) {
        resolve(res);
      },

      error(err) {
        reject(err);
      },
    });
  });
}

1. 为什么需要封装ajax?

    在传统的JavaScript中,发送网络请求主要依靠 XMLHttpRequest 或 fetch。但XMLHttpRequest 基于回调,容易形成“回调地狱”,代码难以维护。Promise 的出现让异步操作变得更加优雅,通过 then 和 catch 可以实现链式调用,避免层层嵌套。

2. 进阶扩展

a. 超时控制
function myAjaxWithTimeout(url, timeout = 5000) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.timeout = timeout;
    xhr.ontimeout = () => reject(new Error('Request timeout'));
    // ... 其余代码
  });
}
b. 取消请求
const controller = new AbortController();
const signal = controller.signal;

function myAjax(url, { signal }) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    signal.addEventListener('abort', () => {
      xhr.abort();
      reject(new Error('Request aborted'));
    });
    // ... 其余代码
  });
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐