普通视图

发现新文章,点击刷新页面。
昨天 — 2025年9月7日首页

前端面试第 78 期 - 2025.09.07 更新 Nginx 专题面试总结(12 道题)

作者 晴小篆
2025年9月7日 14:24

2025.08.31 - 2025.09.07 更新前端面试问题总结(12 道题)
获取更多面试相关问题可以访问
github 地址: github.com/pro-collect…
gitee 地址: gitee.com/yanleweb/in…

目录

中级开发者相关问题【共计 2 道题】

  1. SPA 的 history 路由模式在 Nginx 部署时刷新 404,如何配置解决【热度: 488】【web 应用场景】
  2. 如何通过 Nginx 配置前端静态资源的 “hash 资源永久缓存 + 非 hash 资源协商缓存”?【热度: 75】【web 应用场景】

高级开发者相关问题【共计 10 道题】

  1. Nginx 配置跨域(CORS)需设置哪些关键响应头?复杂跨域(带 cookie、自定义头)有何注意点【热度: 124】【web 应用场景】
  2. Nginx 中 proxy_pass 路径结尾加 / 与不加 /,对前端接口代理路径有何差异?举例说明。【热度: 106】【web 应用场景】
  3. Nginx 如何通过 include 或 vhost 实现前端多环境隔离?同域名不同路径映射需解决哪些重写问题?【热度: 112】【web 应用场景】
  4. Nginx 如何配置支持前端大资源的 Range 分片请求?核心参数是什么?【热度: 146】【web 应用场景】
  5. Nginx 如何按资源类型(如.js/.png)分发到不同服务器?配置策略是什么?【热度: 159】【web 应用场景】【出题公司: 阿里巴巴】
  6. Nginx 配置 gzip/brotli 压缩时需考虑哪些因素?为何不建议对所有前端资源开启压缩?【热度: 106】【web 应用场景】【出题公司: 阿里巴巴】
  7. 微前端通过 Nginx 实现主 / 子应用路由分发,如何配置 location 和 try_files?需处理哪些资源路径问题?【热度: 120】【web 应用场景】
  8. Nginx 配置 HTTPS 需哪些核心项(协议、加密套件)?如何强制 HTTP 跳转 HTTPS?【热度: 182】【web 应用场景】
  9. 前端静态资源加载超时,Nginx 可通过哪些配置优化?【热度: 190】【web 应用场景】【出题公司: 阿里巴巴】
  10. Nginx 如何为不同前端资源配置缓存策略?如何强制刷新特定资源?【热度: 110】【web 应用场景】

中级开发者相关问题【共计 2 道题】

1137. SPA 的 history 路由模式在 Nginx 部署时刷新 404,如何配置解决【热度: 488】【web 应用场景】

关键词:nginx 刷新 404

要解决 SPA(单页应用)History 路由模式在 Nginx 部署时刷新 404 的问题,核心是理解 History 路由的原理缺陷Nginx 的请求匹配逻辑,再通过针对性配置让所有路由请求都指向 SPA 的入口文件(通常是 index.html)。以下是完整解决方案:

一、问题根源:为什么会出现 404?

首先要明确 SPA 两种路由模式的本质差异,这是理解问题的关键:

路由模式 原理 部署后刷新行为
Hash 模式(# 路由信息包含在 URL 的 # 后(如 https://xxx.com/#/about),# 后的内容不会发送给服务器,所有请求本质都是访问根路径(/ 刷新时服务器只接收 / 请求,返回 index.html,SPA 再解析 # 后的路由,不会 404
History 模式(无 # 路由信息是真实 URL 路径(如 https://xxx.com/about),刷新时浏览器会将完整路径(/about)发送给服务器 Nginx 会查找 /about 对应的物理文件/目录,而 SPA 只有 index.html 一个入口文件,找不到就返回 404

二、解决方案:Nginx 核心配置

核心思路:让 Nginx 接收到所有 SPA 路由相关的请求时,都返回入口文件 index.html,由 SPA 框架(Vue/React/Angular 等)再解析具体路由。

1. 基础配置(通用版)

在 Nginx 的 server 块中,通过 try_files 指令实现“优先匹配物理文件,匹配不到则返回 index.html”:

server {
    listen 80;                  # 监听端口(根据实际情况调整,如 443 用于 HTTPS)
    server_name your-domain.com; # 你的域名(如 localhost 用于本地测试)
    root /path/to/your/spa;     # SPA 打包后文件的根目录(绝对路径,如 /usr/local/nginx/html/spa)
    index index.html;           # 默认入口文件

    # 关键配置:解决 History 路由刷新 404
    location / {
        # try_files 逻辑:先尝试访问 $uri(当前请求路径对应的物理文件)
        # 再尝试访问 $uri/(当前请求路径对应的目录)
        # 最后都找不到时,重定向到 /index.html(SPA 入口)
        try_files $uri $uri/ /index.html;
    }
}
2. 进阶配置(处理子路径部署)

如果 SPA 不是部署在域名根路径(如 https://xxx.com/admin,而非 https://xxx.com),需调整 location 匹配规则和 try_files 目标路径,避免路由错乱:

server {
    listen 80;
    server_name your-domain.com;
    root /path/to/your/project; # 注意:这里是父目录(包含 admin 子目录)
    index index.html;

    # 匹配所有以 /admin 开头的请求(SPA 部署在 /admin 子路径)
    location /admin {
        # 1. 先尝试访问子路径下的物理文件(如 /admin/static/css/main.css)
        # 2. 再尝试访问子路径下的目录
        # 3. 最后重定向到 /admin/index.html(子路径下的入口文件,而非根目录)
        try_files $uri $uri/ /admin/index.html;

        # 可选:如果 SPA 框架需要 base 路径,需在框架配置中同步设置
        # 例:Vue 需配置 publicPath: '/admin/',React 需配置 homepage: '/admin/'
    }
}

三、注意事项(避坑点)

  1. 路径正确性

    • root 指令必须指向 SPA 打包后文件的 实际绝对路径(如 Linux 下的 /var/www/spa,Windows 下的 D:/nginx/html/spa),错误路径会导致 Nginx 找不到 index.html
    • 子路径部署时,try_files 最后一个参数必须是 完整的子路径入口(如 /admin/index.html),不能写 /index.html(会指向根目录,导致 404)。
  2. HTTPS 场景适配: 如果网站使用 HTTPS(listen 443 ssl),配置逻辑完全一致,只需在 server 块中补充 SSL 证书相关配置,不影响路由处理:

    server {
        listen 443 ssl;
        server_name your-domain.com;
        ssl_certificate /path/to/cert.pem;   # SSL 证书路径
        ssl_certificate_key /path/to/key.pem; # 证书私钥路径
    
        root /path/to/your/spa;
        index index.html;
    
        location / {
            try_files $uri $uri/ /index.html;
        }
    }
    
  3. 配置生效方式: 修改 Nginx 配置后,需执行以下命令让配置生效(避免重启服务导致短暂 downtime):

    # 1. 测试配置是否有语法错误(必须先执行,避免配置错误导致 Nginx 启动失败)
    nginx -t
    
    # 2. 重新加载配置(平滑生效,不中断现有连接)
    nginx -s reload
    
  4. 与后端接口的冲突处理: 如果 SPA 同时有后端接口请求(如 /api 开头的接口),需在 Nginx 中优先匹配接口路径,避免接口请求被转发到 index.html。配置示例:

    server {
        # ... 其他基础配置 ...
    
        # 第一步:优先匹配后端接口(/api 开头的请求),转发到后端服务
        location /api {
            proxy_pass http://your-backend-server:port; # 后端服务地址(如 http://127.0.0.1:3000)
            proxy_set_header Host $host;               # 传递 Host 头信息
            proxy_set_header X-Real-IP $remote_addr;   # 传递真实客户端 IP
        }
    
        # 第二步:剩余请求(SPA 路由)转发到 index.html
        location / {
            try_files $uri $uri/ /index.html;
        }
    }
    

四、原理总结

通过 try_files $uri $uri/ /index.html 这行核心配置,Nginx 实现了:

  1. 优先处理 静态资源请求(如 cssjsimg):如果请求路径对应物理文件(如 /static/css/main.css),则直接返回该文件。
  2. 兜底处理 SPA 路由请求:如果请求路径不对应任何物理文件(如 /about/user/123),则返回 index.html,由 SPA 框架根据 URL 解析并渲染对应的页面,从而解决刷新 404 问题。

1144. 如何通过 Nginx 配置前端静态资源的 “hash 资源永久缓存 + 非 hash 资源协商缓存”?【热度: 75】【web 应用场景】

关键词:nginx 资源缓存

要实现前端前端静态资源的“hash 资源永久缓存 + 非 hash 资源协商缓存”,需结合 Nginx 的缓存头配置,针对不同类型资源设计差异化策略。核心思路是:对带 hash 的指纹文件(如app.8f3b.js)设置长期强缓存,对无 hash 的文件(如index.html)使用协商缓存,既以下是具体实现方案:

一、两种缓存策略的适用场景

资源类型 特征 缓存策略 目的
带 hash 的资源 文件名含唯一 hash(如style.1a2b.css),内容变化则 hash 变化 永久强缓存 一次缓存后不再请求,减少重复下载
非 hash 的资源 文件名固定(如index.htmlfavicon.ico),内容可能动态更新 协商缓存 每次请求验证是否更新,确保获取最新内容

二、核心配置方案

通过location匹配不同资源类型,分别设置缓存头:

server {
    listen 80;
    server_name example.com;
    root /path/to/frontend/dist;  # 前端打包目录
    index index.html;

    # 1. 处理带hash的静态资源(JS/CSS/图片等):永久强缓存
    # 假设hash格式为 8-16位字母数字(如 app.8f3b1e7d.js)
    location ~* \.(js|css|png|jpg|jpeg|gif|webp|svg|ico|woff2?)(\?.*)?$ {
        # 匹配带hash的文件名(如 .1a2b3c. 或 .v2.3.4. 等格式)
        # 正则说明:\.\w{8,16}\. 匹配 .hash. 结构(8-16位hash值)
        if ($request_filename ~* .*\.\w{8,16}\.(js|css|png|jpg|jpeg|gif|webp|svg|ico|woff2?)$) {
            # 永久缓存(1年)
            expires 365d;
            # 强缓存标识:告知浏览器直接使用缓存,不发请求
            add_header Cache-Control "public, max-age=31536000, immutable";
        }
    }

    # 2. 处理非hash资源(如 index.html):协商缓存
    location / {
        # 禁用强缓存
        expires -1;
        # 协商缓存:基于文件修改时间(Last-Modified)验证
        add_header Cache-Control "no-cache, must-revalidate";

        # 支持 History 路由(SPA必备)
        try_files $uri $uri/ /index.html;
    }

    # 3. 特殊资源补充:favicon.ico(通常无hash)
    location = /favicon.ico {
        expires 7d;  # 短期强缓存(7天)+ 协商缓存兜底
        add_header Cache-Control "public, max-age=604800, must-revalidate";
    }
}

三、配置详解与核心参数

1. 带 hash 资源的永久强缓存
  • 匹配规则
    通过正则.*\.\w{8,16}\.(js|css...)精准匹配带 hash 的文件(如app.8f3b1e7d.jslogo.a1b2c3.png),确保只有内容不变的文件被长期缓存。

  • 核心缓存头

    • expires 365d:设置浏览器缓存过期时间(1 年)。
    • Cache-Control: public, max-age=31536000, immutable
      • public:允许中间代理(如 CDN)缓存。
      • max-age=31536000:1 年内直接使用缓存(单位:秒)。
      • immutable:告知浏览器资源不会变化,无需发送验证请求(H5 新特性,增强缓存效果)。
  • 关键逻辑
    当资源内容更新时,打包工具(Webpack/Vite 等)会生成新的 hash 文件名(如app.9c4d2f8e.js),浏览器会将其视为新资源重新请求,完美解决“缓存更新”问题。

2. 非 hash 资源的协商缓存
  • 适用场景
    index.html(SPA 入口文件)、robots.txt等文件名固定的资源,需确保用户能获取最新版本。

  • 核心缓存头

    • expires -1:禁用强缓存(立即过期)。
    • Cache-Control: no-cache, must-revalidate
      • no-cache:浏览器必须发送请求到服务器验证资源是否更新。
      • must-revalidate:若资源过期,必须向服务器验证。
  • 协商验证机制
    Nginx 默认会返回Last-Modified头(文件最后修改时间),浏览器下次请求时会携带If-Modified-Since头:

    • 若文件未修改,服务器返回304 Not Modified(无响应体),浏览器使用缓存。
    • 若文件已修改,服务器返回200 OK和新内容。
3. 特殊资源处理(如 favicon.ico)
  • 对于不常变化但无 hash 的资源(如网站图标),可采用“短期强缓存 + 协商缓存兜底”:
    • expires 7d:7 天内直接使用缓存。
    • must-revalidate:过期后必须向服务器验证是否更新。

四、与前端打包的配合要点

  1. 确保 hash 生成规则可靠
    前端打包时,需保证“内容不变则 hash 不变,内容变化则 hash 必变”。例如:

    • Webpack:contenthash(基于文件内容生成 hash)。
    • Vite:默认对静态资源生成 contenthash。
  2. 避免 hash 资源依赖非 hash 资源
    确保带 hash 的 JS/CSS 不引用无 hash 的资源(如background: url(/img/bg.png)),否则 bg.png 更新后,引用它的 CSS 因 hash 未变而无法更新。
    解决方案:让被引用资源也带上 hash(如bg.a1b2c3.png)。

  3. index.html 必须无 hash
    作为入口文件,index.html需通过协商缓存确保用户每次获取最新版本,从而加载新的 hash 资源。

五、验证缓存是否生效

  1. 强缓存验证
    访问带 hash 的资源(如app.8f3b.js),在浏览器 Network 面板中查看:

    • 状态码为200 OK (from disk cache)200 OK (from memory cache)
    • Response Headers 包含Cache-Control: public, max-age=31536000, immutable
  2. 协商缓存验证
    访问index.html,刷新页面:

    • 第一次请求:状态码200 OK,Response Headers 有Last-Modified
    • 第二次请求:Request Headers 有If-Modified-Since,若未修改,状态码304 Not Modified

总结

通过 Nginx 实现“hash 资源永久缓存 + 非 hash 资源协商缓存”的核心是:

  1. 利用 hash 文件名的唯一性,对静态资源设置长期强缓存,最大化减少重复请求。
  2. 对入口文件等无 hash 资源启用协商缓存,确保内容更新时能被及时获取。
  3. 前端打包与 Nginx 配置协同,保证 hash 机制可靠,避免缓存不一致问题。

这种策略既能大幅提升页面加载速度,又能确保资源更新的即时性,是前端性能优化的关键实践。

高级开发者相关问题【共计 10 道题】

1138. Nginx 配置跨域(CORS)需设置哪些关键响应头?复杂跨域(带 cookie、自定义头)有何注意点【热度: 124】【web 应用场景】

关键词:nginx 配置跨域

Nginx 配置跨域(CORS)的核心是通过设置特定响应头,允许不同域的前端资源请求当前服务器资源。以下是关键配置和复杂跨域场景的注意事项:

一、基础跨域配置:关键响应头

基础跨域(无 cookie、无自定义头)需配置以下核心响应头,允许指定域的请求访问资源:

location / {
    # 1. 允许的源域名(必填)
    # 注意:生产环境建议明确指定域名(如 https://example.com),而非 *
    add_header Access-Control-Allow-Origin *;

    # 2. 允许的请求方法(必填)
    add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';

    # 3. 允许的请求头(可选,根据实际需求添加)
    add_header Access-Control-Allow-Headers 'Content-Type, Authorization';

    # 4. 预检请求(OPTIONS)的缓存时间(可选,减少预检请求次数)
    add_header Access-Control-Max-Age 3600;

    # 处理预检请求(OPTIONS):直接返回 204 成功状态
    if ($request_method = 'OPTIONS') {
        return 204;
    }
}

各头字段作用

  • Access-Control-Allow-Origin:指定允许跨域请求的源(* 表示允许所有源,不推荐生产环境使用)。
  • Access-Control-Allow-Methods:允许的 HTTP 方法(需包含实际使用的方法,如 OPTIONS 是预检请求必须的)。
  • Access-Control-Allow-Headers:允许请求中携带的自定义头(如 AuthorizationX-Custom-Header)。
  • Access-Control-Max-Age:预检请求(OPTIONS)的结果缓存时间(秒),避免频繁预检。

二、复杂跨域场景:带 cookie、自定义头的注意点

当跨域请求需要 携带 cookie自定义请求头 时,配置需更严格,且前后端需协同配合:

1. 带 cookie 的跨域(withCredentials: true
  • Nginx 必须明确指定允许的源(不能用 *),否则浏览器会拒绝响应:

    # 错误:带 cookie 时不能用 *
    # add_header Access-Control-Allow-Origin *;
    
    # 正确:明确指定允许的源(如 https://frontend.com)
    add_header Access-Control-Allow-Origin https://frontend.com;
    
    # 必须添加:允许携带 cookie
    add_header Access-Control-Allow-Credentials true;
    
  • 前端需配合设置:请求时需显式开启 withCredentials(以 Fetch 为例):

    fetch("https://backend.com/api/data", {
      credentials: "include", // 等价于 XMLHttpRequest 的 withCredentials: true
    });
    
2. 带自定义请求头(如 X-Token
  • 需在 Access-Control-Allow-Headers 中显式包含自定义头,否则预检请求会失败:

    # 例如允许 X-Token、X-User-Id 等自定义头
    add_header Access-Control-Allow-Headers 'Content-Type, X-Token, X-User-Id';
    
  • 浏览器会先发送 OPTIONS 预检请求,需确保 Nginx 正确处理(返回 204 或 200):

    if ($request_method = 'OPTIONS') {
        return 204;  # 预检请求成功,无需返回 body
    }
    
3. 其他注意事项
  • add_header 指令的作用域:如果 Nginx 配置中存在多个 location 块,跨域头需配置在对应请求的 location 中(如接口请求通常在 /api 路径)。

  • 避免重复设置头:如果后端服务(如 Node.js、Java)已设置 CORS 头,Nginx 无需重复添加,否则可能导致浏览器解析冲突。

  • 生产环境安全性

    • 禁止使用 Access-Control-Allow-Origin: *(尤其是带 cookie 的场景)。
    • 限制 Access-Control-Allow-Methods 为必要的方法(如仅允许 GET, POST)。
    • 避免 Access-Control-Allow-Headers 包含通配符(如 *),仅添加实际需要的头。

三、完整复杂跨域配置示例(带 cookie + 自定义头)

server {
    listen 80;
    server_name backend.com;

    # 接口路径的跨域配置(假设接口都在 /api 下)
    location /api {
        # 明确允许的前端域名(不能用 *)
        add_header Access-Control-Allow-Origin https://frontend.com;

        # 允许携带 cookie
        add_header Access-Control-Allow-Credentials true;

        # 允许的方法(包含预检请求 OPTIONS)
        add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';

        # 允许的头(包含自定义头 X-Token)
        add_header Access-Control-Allow-Headers 'Content-Type, Authorization, X-Token';

        # 预检请求结果缓存 1 小时
        add_header Access-Control-Max-Age 3600;

        # 处理预检请求
        if ($request_method = 'OPTIONS') {
            return 204;
        }

        # 转发请求到后端服务(如 Node.js、Java 服务)
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
    }
}

总结

  • 基础跨域:核心配置 Access-Control-Allow-Origin-Methods-Headers
  • 带 cookie 跨域:必须指定具体 Origin,并添加 Access-Control-Allow-Credentials: true
  • 自定义头跨域:需在 Allow-Headers 中显式声明,并正确处理 OPTIONS 预检请求。
  • 生产环境需严格限制允许的源、方法和头,避免安全风险。

1139. Nginx 中 proxy_pass 路径结尾加 / 与不加 /,对前端接口代理路径有何差异?举例说明。【热度: 106】【web 应用场景】

关键词:nginx proxy_pass

Nginx 中 proxy_pass 路径结尾是否加 / 会直接影响代理后的 URL 拼接规则,对前端接口请求路径的映射结果有显著差异。理解这一差异是配置接口代理的关键。

核心差异:路径拼接规则

假设:

  • Nginx 配置的 location 匹配规则为 /api
  • 后端服务地址为 http://backend:3000

两种配置的区别如下:

proxy_pass 配置 拼接规则 最终代理地址
不加 /proxy_pass http://backend:3000 location 匹配的路径(/api完整拼接到后端地址后 http://backend:3000 + /api + 剩余路径
/proxy_pass http://backend:3000/ location 匹配的路径(/api替换为 /,仅拼接剩余路径 http://backend:3000 + / + 剩余路径

举例说明(前端请求路径对比)

假设前端发送请求:http://nginx-host/api/user/list

1. proxy_pass 不加 / 的情况
location /api {
    # 后端地址末尾无 /
    proxy_pass http://backend:3000;
}
  • 匹配逻辑:location /api 匹配到请求中的 /api 部分
  • 代理后地址:http://backend:3000 + /api + /user/listhttp://backend:3000/api/user/list
2. proxy_pass/ 的情况
location /api {
    # 后端地址末尾有 /
    proxy_pass http://backend:3000/;
}
  • 匹配逻辑:location /api 匹配到的 /api 被替换为 /
  • 代理后地址:http://backend:3000/ + /user/listhttp://backend:3000/user/list

扩展场景:location 带多级路径时

location 规则为 /api/v1,请求路径为 http://nginx-host/api/v1/user/list

1. 不加 /
location /api/v1 {
    proxy_pass http://backend:3000;
}
# 代理结果:http://backend:3000/api/v1/user/list
2. 加 /
location /api/v1 {
    proxy_pass http://backend:3000/;
}
# 代理结果:http://backend:3000/user/list

特殊场景:proxy_pass 包含子路径

proxy_pass 不仅是域名/IP,还包含子路径(如 http://backend:3000/service),加不加 / 的规则依然适用:

1. 后端路径不加 /
location /api {
    proxy_pass http://backend:3000/service;
}
# 请求 /api/user → 代理到 http://backend:3000/serviceapi/user
# (注意:/api 被完整拼接,导致 service 和 api 连在一起,通常不符合预期)
2. 后端路径加 /
location /api {
    proxy_pass http://backend:3000/service/;
}
# 请求 /api/user → 代理到 http://backend:3000/service/user
# (正确拼接:/api 被替换为 /,剩余路径 /user 拼接到 service/ 后)

总结

  • 不加 /location 匹配的路径会被完整拼接到 proxy_pass 地址后(适合后端接口路径与前端请求路径完全一致的场景)。
  • /location 匹配的路径会被替换为 /,仅将剩余路径拼接到 proxy_pass 地址后(适合需要简化后端接口路径的场景)。

实际配置时,建议通过 curl 测试或查看后端日志确认代理结果,避免因路径拼接错误导致接口 404。

1140. Nginx 如何通过 include 或 vhost 实现前端多环境隔离?同域名不同路径映射需解决哪些重写问题?【热度: 112】【web 应用场景】

关键词:nginx 多环境隔离

Nginx 可以通过 include 指令或虚拟主机(vhost)实现前端多环境(如开发、测试、生产)的隔离部署,同时针对同域名不同路径的映射场景,需要处理路径重写和资源引用问题。以下是具体实现方案和注意事项:

一、通过 include 或 vhost 实现多环境隔离

多环境隔离的核心是为不同环境(如 devtestprod)配置独立的 Nginx 规则,避免互相干扰。

1. 基于 include 指令的多环境配置(推荐)

适合单服务器部署多个环境,通过拆分配置文件实现隔离,便于维护。

目录结构

nginx/
├── conf.d/
│   ├── common.conf          # 公共配置(如日志、超时时间)
│   ├── frontend-dev.conf    # 开发环境配置
│   ├── frontend-test.conf   # 测试环境配置
│   └── frontend-prod.conf   # 生产环境配置
└── nginx.conf               # 主配置文件(通过 include 引入子配置)

主配置(nginx.conf)

http {
    # 引入公共配置
    include conf.d/common.conf;

    # 引入各环境配置(按需启用,生产环境可注释 dev/test)
    include conf.d/frontend-dev.conf;
    include conf.d/frontend-test.conf;
    include conf.d/frontend-prod.conf;
}

环境配置示例(frontend-dev.conf)

# 开发环境:监听 8080 端口
server {
    listen 8080;
    server_name localhost;

    # 开发环境前端文件目录
    root /path/to/frontend/dev;
    index index.html;

    # 开发环境特有的路由配置(如 History 模式支持)
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 开发环境接口代理(指向开发后端)
    location /api {
        proxy_pass http://dev-backend:3000;
    }
}

优势

  • 配置模块化,各环境规则独立,修改单个环境不影响其他环境。
  • 可通过注释 include 语句快速切换生效的环境。
2. 基于虚拟主机(vhost)的多环境配置

适合通过不同域名/端口区分环境(如 dev.example.comtest.example.com)。

配置示例

http {
    # 开发环境(域名区分)
    server {
        listen 80;
        server_name dev.example.com;  # 开发环境域名
        root /path/to/frontend/dev;
        # ... 其他配置(路由、代理等)
    }

    # 测试环境(端口区分)
    server {
        listen 8081;  # 测试环境端口
        server_name localhost;
        root /path/to/frontend/test;
        # ... 其他配置
    }

    # 生产环境(HTTPS)
    server {
        listen 443 ssl;
        server_name example.com;  # 生产环境域名
        root /path/to/frontend/prod;
        # ... SSL 配置和其他生产环境特有的规则
    }
}

优势

  • 环境边界清晰,通过域名/端口直接访问对应环境,适合团队协作。
  • 可针对生产环境单独配置 HTTPS、缓存等高级特性。

二、同域名不同路径映射的重写问题及解决方案

当多个前端应用部署在同一域名的不同路径下(如 example.com/app1example.com/app2),需要解决路径映射和资源引用的问题。

场景示例
  • 应用 A 部署在 /app1 路径,文件目录为 /var/www/app1
  • 应用 B 部署在 /app2 路径,文件目录为 /var/www/app2
1. 基础路径映射配置
server {
    listen 80;
    server_name example.com;
    root /var/www;  # 父目录

    # 应用 A:匹配 /app1 路径
    location /app1 {
        # 实际文件目录为 /var/www/app1
        alias /var/www/app1;  # 注意:这里用 alias 而非 root(关键区别)
        index index.html;

        # 解决 History 路由刷新 404
        try_files $uri $uri/ /app1/index.html;
    }

    # 应用 B:匹配 /app2 路径
    location /app2 {
        alias /var/www/app2;
        index index.html;
        try_files $uri $uri/ /app2/index.html;
    }
}

关键区别alias vs root

  • root /var/www:请求 /app1/static/css.css 会映射到 /var/www/app1/static/css.css(拼接完整路径)。
  • alias /var/www/app1:请求 /app1/static/css.css 会直接映射到 /var/www/app1/static/css.css(替换 /app1 为实际目录),更适合子路径部署。
2. 需要解决的重写问题及方案
(1)前端资源引用路径错误

问题:应用内的静态资源(如 jscss、图片)若使用绝对路径(如 /static/js/main.js),会被解析为 example.com/static/js/main.js,而非 example.com/app1/static/js/main.js,导致 404。

解决方案

  • 前端打包时配置 公共路径(publicPath)
    • Vue 项目:在 vue.config.js 中设置 publicPath: '/app1/'
    • React 项目:在 package.json 中设置 homepage: '/app1'
  • 资源引用使用相对路径(如 ./static/js/main.js),避免绝对路径。
(2)接口请求路径前缀问题

问题:应用内的接口请求(如 /api/user)会被发送到 example.com/api/user,若需要根据子路径区分接口(如 example.com/app1/api/user),需调整代理规则。

解决方案

  • 前端统一为接口添加路径前缀(如 axios.defaults.baseURL = '/app1/api')。
  • Nginx 配置对应代理规则:
    location /app1/api {
        # 移除 /app1 前缀后转发到后端
        proxy_pass http://backend:3000/api;  # 或 http://backend:3000/(根据后端路径调整)
    }
    
(3)路由跳转路径错误

问题:SPA 路由跳转(如从 /app1/home 跳转到 /app1/about)若使用绝对路径,可能因框架路由配置未添加基础路径导致跳转错误。

解决方案

  • 前端路由配置基础路径:
    • Vue Router:base: '/app1/'
    • React Router:basename="/app1"

三、总结

  1. 多环境隔离

    • 推荐使用 include 指令拆分配置文件,实现模块化管理。
    • 或通过虚拟主机(不同域名/端口)实现环境隔离,适合团队协作。
  2. 同域名不同路径映射

    • 使用 alias 指令正确映射子路径到实际文件目录。
    • 解决资源引用问题:前端配置 publicPath,使用相对路径。
    • 解决接口和路由问题:统一添加路径前缀,配置对应 Nginx 代理和前端路由基础路径。

通过以上配置,可以实现前端多环境的清晰隔离和同域名下多应用的无冲突部署。

1141. Nginx 如何配置支持前端大资源的 Range 分片请求?核心参数是什么?【热度: 146】【web 应用场景】

关键词:nginx 配置分片请求

Nginx 支持前端大资源(如视频、大型压缩包等)的 Range 分片请求,主要通过配置实现对 HTTP 范围请求的支持,允许客户端分段块下载资源,提升大文件传输效率。以下是具体配置方法和核心参数解析:

一、Range 分片请求的原理

HTTP 协议的 Range 请求头允许客户端只请求资源的一部分(如 Range: bytes=0-1023 表示请求前 1024 字节),服务器通过 Accept-RangesContent-Range 头响应支持状态和分片数据。

Nginx 默认已支持 Range 请求,但需确保配置正确以避免功能被禁用,尤其针对大文件场景需优化相关参数。

二、核心配置(支持 Range 请求)

1. 基础配置(启用 Range 支持)
server {
    listen 80;
    server_name example.com;
    root /path/to/large-files;  # 存放大资源的目录

    # 关键:确保未禁用 Range 请求(默认启用,无需额外配置,但需避免以下错误)
    # 错误示例:禁用 Range 的配置(生产环境需删除)
    # proxy_set_header Range "";  # 禁止传递 Range 头
    # add_header Accept-Ranges none;  # 告知客户端不支持 Range

    # 大文件传输优化(可选但推荐)
    location / {
        # 支持断点续传和分片请求(默认开启,显式声明更清晰)
        add_header Accept-Ranges bytes;

        # 读取文件的缓冲区大小(根据服务器内存调整)
        client_body_buffer_size 10M;

        # 发送文件的缓冲区大小(优化大文件传输效率)
        sendfile on;               # 启用零拷贝发送文件
        tcp_nopush on;             # 配合 sendfile 提高网络效率
        tcp_nodelay off;           # 减少小包发送,适合大文件

        # 超时设置(避免大文件传输中断)
        client_header_timeout 60s;
        client_body_timeout 60s;
        send_timeout 300s;         # 发送超时延长至 5 分钟
    }
}
2. 核心参数解析
  • Accept-Ranges: bytes
    响应头,明确告知客户端服务器支持字节范围的分片请求(这是支持 Range 的核心标志)。Nginx 默认会自动添加该头,无需显式配置,但显式声明可增强配置可读性。

  • sendfile on
    启用零拷贝(zero-copy)机制,让 Nginx 直接从磁盘读取文件并发送到网络,跳过用户态到内核态的数据拷贝,大幅提升大文件传输效率(对 Range 分片请求尤其重要)。

  • tcp_nopush on
    sendfile 配合使用,在发送文件时先积累一定数据量再一次性发送,减少网络包数量,适合大文件的连续分片传输。

  • proxy_set_header Range $http_range(反向代理场景)
    若大资源存储在后端服务(而非 Nginx 本地),需通过此配置将客户端的 Range 请求头传递给后端,确保后端能正确处理分片请求:

    location /large-files {
        proxy_pass http://backend-server;
        proxy_set_header Range $http_range;          # 传递 Range 头
        proxy_set_header If-Range $http_if_range;    # 传递 If-Range 头(验证资源是否修改)
        proxy_pass_request_headers on;               # 确保所有请求头被传递
    }
    

三、验证 Range 请求是否生效

可通过 curl 命令测试服务器是否支持分片请求:

# 测试请求前 1024 字节
curl -v -H "Range: bytes=0-1023" http://example.com/large-file.mp4

若响应中包含以下头信息,则表示配置生效:

HTTP/1.1 206 Partial Content  # 206 状态码表示部分内容响应
Accept-Ranges: bytes
Content-Range: bytes 0-1023/10485760  # 表示返回 0-1023 字节,总大小 10485760 字节

四、注意事项

  1. 避免禁用 Range 的配置
    确保配置中没有 add_header Accept-Ranges noneproxy_set_header Range "" 等禁用 Range 的指令,这些会导致客户端分片请求失败。

  2. 后端服务配合
    若资源通过反向代理从后端服务获取,需确保后端服务本身支持 Range 请求(如 Node.js、Java 服务需实现对 Range 头的处理),否则 Nginx 无法单独完成分片响应。

  3. 大文件存储优化
    对于超大型文件(如 GB 级视频),建议结合 open_file_cache 配置缓存文件描述符,减少频繁打开文件的开销:

    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
    

总结

Nginx 支持 Range 分片请求的核心是:

  1. 确保默认的 Accept-Ranges: bytes 响应头有效(不被禁用)。
  2. 启用 sendfile 等传输优化参数提升大文件处理效率。
  3. 反向代理场景下需传递 Range 相关请求头给后端服务。

通过以上配置,前端可以实现大资源的断点续传、分片下载,显著提升用户体验。

1142. Nginx 如何按资源类型(如.js/.png)分发到不同服务器?配置策略是什么?【热度: 159】【web 应用场景】【出题公司: 阿里巴巴】

关键词:nginx 转发

Nginx 可以通过 location 指令匹配不同资源类型(如 .js.png),并将请求分发到不同服务器,实现资源的分类部署和负载均衡。这种配置策略适合将静态资源(JS、图片)与动态资源(API)分离部署,提升整体服务性能。

一、核心配置策略:按文件后缀匹配并转发

通过 location 块的正则表达式匹配符(区分大小写)或 ~* 匹配符(不区分大小写),根据文件后缀名匹配不同资源类型,再通过 proxy_pass 转发到对应服务器。

1. 基础配置示例(分离 JS/CSS 与图片资源)
http {
    # 定义后端服务器组(可配置负载均衡)
    # JS/CSS 资源服务器组
    upstream js_css_servers {
        server 192.168.1.101:8080;  # JS/CSS 服务器1
        server 192.168.1.102:8080;  # JS/CSS 服务器2(负载均衡)
    }

    # 图片资源服务器组
    upstream image_servers {
        server 192.168.1.201:8080;  # 图片服务器1
        server 192.168.1.202:8080;  # 图片服务器2(负载均衡)
    }

    # 其他资源(如HTML、API)服务器
    upstream default_server {
        server 192.168.1.301:8080;
    }

    server {
        listen 80;
        server_name example.com;

        # 1. 匹配 .js 和 .css 文件,转发到 JS/CSS 服务器组
        location ~* \.(js|css)$ {
            proxy_pass http://js_css_servers;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            # 静态资源缓存优化(可选)
            expires 1d;  # 缓存 1 天
            add_header Cache-Control "public, max-age=86400";
        }

        # 2. 匹配图片文件(.png/.jpg/.jpeg/.gif/.webp),转发到图片服务器组
        location ~* \.(png|jpg|jpeg|gif|webp)$ {
            proxy_pass http://image_servers;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            # 图片缓存时间更长(可选)
            expires 7d;  # 缓存 7 天
            add_header Cache-Control "public, max-age=604800";
        }

        # 3. 其他所有请求(如 HTML、API)转发到默认服务器
        location / {
            proxy_pass http://default_server;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}

二、配置策略解析

1. 匹配规则说明
  • ~* \.(js|css)$

    • ~* 表示不区分大小写匹配(如 .JS.Css 也会被匹配)。
    • \.(js|css)$ 是正则表达式,匹配以 .js.css 结尾的请求。
  • 优先级注意
    Nginx 的 location 匹配有优先级,精确匹配(=)> 前缀匹配(不含正则)> 正则匹配(~/~*
    因此,按资源类型的正则匹配会优先于普通前缀匹配(如 /static),需确保规则无冲突。

2. 服务器组(upstream)配置
  • 通过 upstream 定义同类资源的服务器集群,支持负载均衡策略(默认轮询):
    • 可添加 weight=2 调整权重(如 server 192.168.1.101:8080 weight=2;)。
    • 可添加 backup 配置备用服务器(如 server 192.168.1.103:8080 backup;)。
3. 资源优化补充配置
  • 缓存策略:静态资源(JS、图片)通常不频繁变动,通过 expiresCache-Control 头设置浏览器缓存,减少重复请求。
  • 防盗链:图片等资源可添加防盗链配置,防止被其他网站盗用:
    location ~* \.(png|jpg|jpeg|gif|webp)$ {
        # 仅允许 example.com 域名引用图片
        valid_referers none blocked example.com *.example.com;
        if ($invalid_referer) {
            return 403;  # 非法引用返回 403
        }
        # ... 其他配置
    }
    

三、扩展场景:按目录 + 资源类型组合匹配

若资源按目录分类(如 /static/js/static/img),可结合目录和后缀匹配,进一步细化转发规则:

# 仅匹配 /static/js 目录下的 .js 文件
location ~* /static/js/.*\.js$ {
    proxy_pass http://js_servers;
}

# 仅匹配 /static/img 目录下的图片文件
location ~* /static/img/.*\.(png|jpg)$ {
    proxy_pass http://image_servers;
}

四、注意事项

  1. 正则表达式效率
    过多复杂的正则匹配会影响 Nginx 性能,建议资源类型规则尽量简洁(如合并同类后缀)。

  2. 后端资源路径一致性
    确保转发目标服务器的资源路径与请求路径一致。例如,请求 example.com/static/a.js 被转发到 js_css_servers 后,服务器需能在 /static/a.js 路径找到资源。

  3. HTTPS 场景适配
    若使用 HTTPS,配置逻辑不变,只需在 server 块中添加 SSL 证书配置,转发目标可保持 HTTP(内部通信)或 HTTPS(跨公网)。

总结

按资源类型分发的核心策略是:

  1. location ~* \.(后缀1|后缀2)$ 匹配不同资源类型。
  2. 通过 upstream 定义对应资源的服务器集群,支持负载均衡。
  3. 结合缓存、防盗链等配置优化静态资源访问。

这种方案能实现资源的分类部署,减轻单服务器压力,同时针对不同资源类型(如图片、JS)进行专项优化,提升整体服务性能。

1143. Nginx 配置 gzip/brotli 压缩时需考虑哪些因素?为何不建议对所有前端资源开启压缩?【热度: 106】【web 应用场景】【出题公司: 阿里巴巴】

关键词:nginx 配置资源压缩

在 Nginx 中配置 gzip 或 brotli 压缩时,需综合考虑压缩效率、服务器性能开销、客户端兼容性等核心因素;而不建议对所有前端资源开启压缩,本质是避免“无效压缩”(压缩后体积无明显减小)和“反向损耗”(压缩耗时 > 传输耗时)。以下是具体分析:

一、配置 gzip/brotli 需考虑的核心因素

无论是 gzip 还是 brotli(压缩率通常优于 gzip,但需额外模块支持),配置时需围绕“收益最大化、损耗最小化”展开,核心考虑因素如下:

1. 资源类型适配:选择“高压缩收益”的资源

不同资源的压缩潜力差异极大,需优先对文本类资源开启压缩(压缩率高、收益显著),对二进制资源谨慎处理(压缩率低、甚至体积变大)。

资源类型 压缩收益 建议配置 原因
HTML/CSS/JS 极高 强制开启 文本内容重复度高,压缩率可达 60%-80%,传输体积大幅减小。
JSON/XML 极高 强制开启 结构化文本,压缩率与 JS 接近,尤其适合 API 响应数据。
图片(PNG/JPG) 极低 禁止开启 本身已是压缩格式(PNG 无损压缩、JPG 有损压缩),再压缩体积基本不变,反而增加耗时。
视频(MP4/WEBM) 极低 禁止开启 视频编码已做深度压缩,gzip/brotli 无法进一步减小体积,纯浪费资源。
字体(WOFF2) 可选开启 WOFF2 本身已内置压缩(基于 brotli),再压缩收益有限;若使用旧字体格式(WOFF/TTF),可开启。
压缩包(ZIP/RAR) 极低 禁止开启 压缩包本身是压缩格式,二次压缩可能导致体积轻微增大。
2. 压缩级别:平衡“压缩率”与“服务器耗时”

gzip 和 brotli 均支持多级别压缩(级别越高,压缩率越高,但消耗 CPU 资源越多、压缩耗时越长),需根据服务器性能和业务需求选择:

  • gzip 压缩级别gzip_comp_level 1-9):

    • 级别 1-3:轻量压缩,CPU 消耗低,耗时短,适合高并发场景(如秒杀、峰值流量),压缩率约 40%-50%;
    • 级别 4-6:平衡压缩率与性能,默认推荐级别(Nginx 默认是 1,需手动调至 4-6),压缩率约 50%-70%;
    • 级别 7-9:高强度压缩,CPU 消耗高,耗时久,仅适合低并发、对带宽敏感的场景(如静态资源 CDN 后台)。
  • brotli 压缩级别brotli_comp_level 1-11):
    比 gzip 多 2 个级别,压缩率更高(同级别下比 gzip 高 10%-20%),但 CPU 消耗也更高。推荐级别 4-8,避免使用 9-11(耗时显著增加,收益边际递减)。

3. 客户端兼容性:避免“压缩后客户端无法解压”

压缩生效的前提是客户端支持对应压缩算法(通过 HTTP 请求头 Accept-Encoding: gzip, br 告知服务器),需避免对不支持的客户端发送压缩数据:

  • gzip 兼容性:几乎所有现代浏览器(IE6+)、客户端均支持,兼容性无压力。
  • brotli 兼容性:支持 95% 以上现代浏览器(Chrome 49+、Firefox 44+、Edge 15+),但需注意:
    • 仅支持 HTTPS 环境(部分浏览器限制 HTTP 下不使用 brotli);
    • 需 Nginx 额外安装 ngx_brotli 模块(默认不内置,需编译时添加或通过动态模块加载)。

配置时需通过 gzip_disable/brotli_disable 排除不支持的客户端,例如:

# gzip:排除 IE6 及以下不支持的客户端
gzip_disable "MSIE [1-6]\.";

# brotli:仅对支持的客户端生效(依赖 Accept-Encoding 头)
brotli on;
brotli_types text/html text/css application/javascript application/json;
4. 压缩阈值:避免“小文件压缩反而耗时”

极小文件(如 < 1KB 的 CSS/JS 片段)开启压缩,可能出现“压缩耗时 > 传输耗时”的反向损耗——因为压缩需要 CPU 计算,而小文件即使不压缩,传输耗时也极短。
需通过 gzip_min_length/brotli_min_length 设置“压缩阈值”,仅对超过阈值的文件开启压缩(Nginx 默认 gzip_min_length 20,即 20 字节,建议调整为 1KB 以上):

# 仅对 > 1KB 的文件开启压缩(单位:字节)
gzip_min_length 1024;
brotli_min_length 1024;
5. 缓存与预压缩:减少“重复压缩”损耗

Nginx 默认“实时压缩”(每次请求都重新压缩资源),若资源长期不变(如静态 JS/CSS),会导致重复的 CPU 消耗。需通过以下方式优化:

  • 开启压缩缓存:通过 gzip_buffers 配置内存缓存,减少重复压缩(Nginx 默认开启,建议调整缓存块大小适配资源):
    # gzip 缓存:4 个 16KB 块(总 64KB),适配中小型文本资源
    gzip_buffers 4 16k;
    
  • 预压缩静态资源:提前通过工具(如 gzip 命令、brotli 命令)生成压缩后的资源文件(如 app.js.gzapp.js.br),Nginx 直接返回预压缩文件,避免实时压缩:
    # 优先返回预压缩的 .gz 文件(若存在)
    gzip_static on;
    # 优先返回预压缩的 .br 文件(若存在,需 brotli 模块支持)
    brotli_static on;
    
6. 服务器性能:避免“压缩耗尽 CPU 资源”

压缩(尤其是高级别压缩)会消耗 CPU 资源,若服务器 CPU 核心数少(如 1-2 核)或并发量极高(如每秒万级请求),过度压缩可能导致 CPU 使用率飙升,影响其他服务(如动态请求处理)。
需结合服务器配置调整:

  • 低配置服务器(1-2 核):使用 gzip 级别 1-3,关闭 brotli;
  • 中高配置服务器(4 核以上):使用 gzip 级别 4-6 或 brotli 级别 4-8;
  • 可通过 gzip_threads(仅部分 Nginx 版本支持)开启多线程压缩,分摊 CPU 压力:
    # 开启 2 个线程处理 gzip 压缩
    gzip_threads 2;
    

二、为何不建议对所有前端资源开启压缩?

核心原因是“部分资源压缩无收益,反而增加损耗”,具体可归纳为 3 类:

1. 压缩收益为负:体积不变或增大
  • 已压缩的二进制资源(如 PNG/JPG/MP4/ZIP):本身已通过专业算法压缩(如 JPG 的 DCT 变换、MP4 的 H.264 编码),gzip/brotli 无法进一步减小体积,甚至因“压缩头额外开销”导致体积轻微增大(如 10MB 的 MP4 压缩后可能变成 10.01MB)。
2. 性能损耗 > 传输收益
  • 极小文件(如 < 1KB 的 CSS 片段、小图标 base64 字符串):压缩耗时(即使 1ms)可能超过“压缩后减少的传输时间”(假设带宽 100Mbps,1KB 传输时间仅 0.08ms),反而拖慢整体响应速度。
3. 客户端兼容性风险
  • 若对不支持 brotli 的旧客户端(如 IE11)发送 brotli 压缩数据,客户端无法解压,会直接返回“空白页面”或“乱码”;
  • 虽可通过 Accept-Encoding 头判断,但配置不当(如遗漏 brotli_disable)仍可能出现兼容性问题,而“不压缩所有资源”是更稳妥的规避方式。

三、推荐的 gzip + brotli 配置示例

结合上述因素,以下是兼顾“性能、兼容性、收益”的配置(需确保 Nginx 已安装 brotli 模块):

http {
    # -------------------------- gzip 配置 --------------------------
    gzip on;                          # 开启 gzip
    gzip_comp_level 5;                # 平衡级别(压缩率 ~60%,CPU 消耗适中)
    gzip_min_length 1024;             # 仅压缩 >1KB 的文件
    gzip_buffers 4 16k;               # 内存缓存块
    gzip_types
        text/html text/css application/javascript
        application/json application/xml
        text/plain text/javascript;   # 仅对文本类资源压缩
    gzip_disable "MSIE [1-6]\.";      # 排除 IE6 及以下
    gzip_static on;                   # 优先使用预压缩的 .gz 文件
    gzip_vary on;                     # 向客户端返回 Vary: Accept-Encoding 头(利于 CDN 缓存)

    # -------------------------- brotli 配置 --------------------------
    brotli on;                        # 开启 brotli
    brotli_comp_level 6;              # 平衡级别(压缩率 ~70%,比 gzip 高 10%)
    brotli_min_length 1024;           # 同 gzip 阈值
    brotli_types
        text/html text/css application/javascript
        application/json application/xml
        text/plain text/javascript;   # 仅对文本类资源压缩
    brotli_disable "MSIE [1-6]\.|Firefox/[1-43]\.";  # 排除不支持的旧浏览器
    brotli_static on;                 # 优先使用预压缩的 .br 文件
    brotli_vary on;                   # 同 gzip_vary
}

总结

配置 Nginx 压缩的核心逻辑是“针对性优化”:

  1. 只对“文本类资源”(HTML/CSS/JS/JSON)开启压缩,对“二进制资源”(图片/视频/压缩包)坚决关闭;
  2. 选择“平衡的压缩级别”,避免过度消耗 CPU;
  3. 通过“预压缩”和“缓存”减少实时压缩损耗;
  4. 兼容旧客户端,避免因压缩导致的访问异常。

这种策略既能最大化压缩带来的“带宽节省、加载加速”收益,又能最小化服务器性能损耗和兼容性风险。

1145. 微前端通过 Nginx 实现主 / 子应用路由分发,如何配置 location 和 try_files?需处理哪些资源路径问题?【热度: 120】【web 应用场景】

微前端通过 Nginx 实现主/子应用路由分发时,核心是通过 location 匹配不同应用的路由路径,并结合 try_files 处理 SPA 路由刷新 404 问题。同时需解决子应用资源路径、主/子应用路由冲突等关键问题。以下是具体实现方案:

一、基础场景:主应用与子应用通过路径前缀区分

假设:

  • 主应用路由:https://example.com/(根路径)
  • 子应用 A 路由:https://example.com/app1/(前缀 /app1
  • 子应用 B 路由:https://example.com/app2/(前缀 /app2
1. 目录结构(前端资源存放)
/var/www/
├── main-app/          # 主应用打包文件
│   ├── index.html
│   ├── static/
│   └── ...
├── app1/              # 子应用 A 打包文件
│   ├── index.html
│   ├── static/
│   └── ...
└── app2/              # 子应用 B 打包文件
    ├── index.html
    └── ...
2. Nginx 核心配置(location + try_files)
server {
    listen 80;
    server_name example.com;
    root /var/www;  # 父目录(包含所有应用)

    # 1. 主应用路由(根路径 /)
    location / {
        # 主应用实际目录为 /var/www/main-app
        alias /var/www/main-app/;
        index index.html;

        # 解决主应用 History 路由刷新 404
        # 逻辑:优先匹配物理文件,匹配不到则返回主应用 index.html
        try_files $uri $uri/ /main-app/index.html;
    }

    # 2. 子应用 A 路由(/app1 前缀)
    location /app1 {
        # 子应用 A 实际目录为 /var/www/app1
        alias /var/www/app1/;
        index index.html;

        # 解决子应用 A History 路由刷新 404
        # 注意:try_files 最后需指向子应用自己的 index.html
        try_files $uri $uri/ /app1/index.html;
    }

    # 3. 子应用 B 路由(/app2 前缀)
    location /app2 {
        alias /var/www/app2/;
        index index.html;
        try_files $uri $uri/ /app2/index.html;
    }
}

二、关键配置解析

1. aliasroot 的选择
  • 必须使用 alias:子应用路径(如 /app1)与实际目录(/var/www/app1)是“映射关系”,alias 会将 /app1 直接替换为实际目录(如请求 /app1/static.js 映射到 /var/www/app1/static.js)。
  • 若误用 rootroot /var/www 会在请求路径后拼接目录(/app1/static.js 会映射到 /var/www/app1/static.js,看似可行,但子应用内路由跳转可能出现异常)。
2. try_files 的路径规则
  • 主应用:try_files $uri $uri/ /main-app/index.html
    最后一个参数必须是主应用 index.html绝对路径(相对于 Nginx 根目录),确保主应用路由(如 /home)刷新时返回主应用入口。
  • 子应用:try_files $uri $uri/ /app1/index.html
    最后一个参数必须是子应用自己的 index.html(如 /app1/index.html),否则子应用路由(如 /app1/detail)刷新会返回主应用入口,导致路由错乱。

三、需处理的资源路径问题

微前端路由分发的核心坑点是资源路径引用错误,需从 Nginx 配置和前端打包两方面协同解决:

1. 子应用静态资源路径错误(404)

问题:子应用打包时若使用绝对路径(如 src="/static/js/app1.js"),会被解析为 https://example.com/static/js/app1.js,但实际路径应为 https://example.com/app1/static/js/app1.js,导致 404。

解决方案

  • 前端打包配置:子应用需设置 publicPath 为自身路径前缀(如 /app1/):
    • Vue 项目:vue.config.jspublicPath: '/app1/'
    • React 项目:package.jsonhomepage: '/app1'webpack.config.jsoutput.publicPath: '/app1/'
  • 效果:资源引用会自动添加 /app1 前缀(如 src="/app1/static/js/app1.js"),匹配 Nginx 配置的 alias 路径。
2. 主/子应用路由冲突

问题:若主应用存在 /app1 路由,会与子应用的 /app1 路径冲突,导致主应用路由被 Nginx 拦截并转发到子应用。

解决方案

  • 路由命名规范:子应用路径前缀需全局唯一(如 /micro-app1/micro-app2),避免与主应用路由重名。

  • Nginx 优先级控制:若必须使用相同前缀,可通过 location 精确匹配优先处理主应用路由:

    # 主应用的 /app1 路由(精确匹配,优先级高于子应用的 /app1 前缀匹配)
    location = /app1 {
        alias /var/www/main-app/;
        try_files $uri $uri/ /main-app/index.html;
    }
    
    # 子应用 /app1 前缀路由(优先级低)
    location /app1/ {
        alias /var/www/app1/;
        try_files $uri $uri/ /app1/index.html;
    }
    
3. 子应用接口请求路径错误

问题:子应用接口请求(如 /api/data)会被发送到 https://example.com/api/data,若需区分子应用接口(如 https://example.com/app1/api/data),需调整代理规则。

解决方案

  • 前端统一前缀:子应用接口请求添加自身路径前缀(如 axios.defaults.baseURL = '/app1/api')。
  • Nginx 代理转发
    # 子应用 A 的接口代理
    location /app1/api {
        # 移除 /app1 前缀后转发到后端(如后端接口实际路径为 /api)
        proxy_pass http://backend-server/api;
        proxy_set_header Host $host;
    }
    
4. 子应用懒加载路由资源 404

问题:子应用使用路由懒加载时(如 Vue/React 的 import('./page.vue')),打包后的 chunk 文件路径可能未包含子应用前缀,导致加载失败。

解决方案

  • 确保懒加载的 chunk 路径也使用 publicPath 配置的前缀,现代打包工具(Webpack 5+、Vite)会自动处理,只需正确设置 publicPath 即可。

四、复杂场景:子应用嵌套或动态路径

若子应用包含动态路由(如 /app1/user/:id)或嵌套路由(如 /app1/dashboard/settings),Nginx 配置无需额外调整,因为 location /app1 会匹配所有以 /app1 开头的路径,try_files 会统一指向子应用 index.html,路由解析由子应用前端框架完成。

五、总结

微前端 Nginx 路由分发的核心配置要点:

  1. location /app1 匹配子应用路径,alias 指向实际目录。
  2. try_files $uri $uri/ /app1/index.html 解决子应用 History 路由刷新 404。
  3. 必须处理的资源问题:
    • 子应用打包设置 publicPath 为路径前缀(如 /app1/)。
    • 避免主/子应用路由重名,接口请求添加子应用前缀。

通过以上配置,可实现主/子应用路由的无冲突分发,确保静态资源和路由正常访问。

1146. Nginx 配置 HTTPS 需哪些核心项(协议、加密套件)?如何强制 HTTP 跳转 HTTPS?【热度: 182】【web 应用场景】

关键词:nginx 配置 https

Nginx 配置 HTTPS 需重点关注协议版本、加密套件、证书配置等核心项,同时需正确设置 HTTP 到 HTTPS 的强制跳转。以下是详细配置方案:

一、HTTPS 核心配置项(协议、加密套件等)

HTTPS 配置的核心目标是确保安全性(禁用不安全协议和加密套件)和兼容性(支持主流浏览器),关键配置项缺一不可:

1. 证书与私钥配置(必备)

需指定 SSL 证书(公钥)和私钥文件路径,证书需由可信 CA 机构签发(如 Let's Encrypt、阿里云等):

server {
    listen 443 ssl;  # 监听 443 端口并启用 SSL
    server_name example.com;  # 证书绑定的域名

    # 证书文件路径(PEM 格式)
    ssl_certificate /path/to/fullchain.pem;  # 包含服务器证书和中间证书
    ssl_certificate_key /path/to/privkey.pem;  # 服务器私钥
}
2. 协议版本(禁用不安全协议)

需明确启用现代安全协议,禁用已被破解或不安全的旧协议(如 SSLv2、SSLv3、TLSv1.0、TLSv1.1):

# 仅启用 TLSv1.2 和 TLSv1.3(目前最安全的协议版本)
ssl_protocols TLSv1.2 TLSv1.3;
  • 为何禁用旧协议
    TLSv1.0/1.1 存在安全漏洞(如 BEAST 攻击),且不支持现代加密套件;SSL 协议已完全过时,必须禁用。
3. 加密套件(优先选择强加密算法)

加密套件决定数据传输的加密方式,需优先选择支持前向 secrecy(完美前向保密)AES-GCM 等强加密算法的套件:

# 现代浏览器兼容的强加密套件(TLSv1.2+)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

# 优先使用服务器端的加密套件选择(增强安全性)
ssl_prefer_server_ciphers on;
  • 核心原则
    避免使用 RSA 密钥交换(无 Forward Secrecy)和 CBC 模式加密(存在漏洞),优先 ECDHE 密钥交换 + GCM 模式。
4. 性能与安全性优化项
# SSL 会话缓存(减少握手耗时,提升性能)
ssl_session_cache shared:SSL:10m;  # 共享缓存,容量 10MB(约 40000 个会话)
ssl_session_timeout 1d;  # 会话超时时间(1天)

# 启用 HSTS(强制客户端后续使用 HTTPS 访问,防降级攻击)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# 禁用 SSL 压缩(防止 CRIME 攻击)
ssl_compression off;

# 启用 OCSP Stapling(减少证书验证步骤,提升加载速度)
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/fullchain.pem;  # 与 ssl_certificate 一致即可
resolver 8.8.8.8 114.114.114.114 valid=300s;  # DNS 解析器(用于验证 OCSP 响应)

二、强制 HTTP 跳转 HTTPS 的配置方法

需将所有 HTTP(80 端口)请求强制重定向到 HTTPS(443 端口),确保用户始终使用加密连接。推荐两种可靠方案:

1. 方案一:通过 301 永久重定向(推荐)

在 80 端口的 server 块中直接返回 301 重定向,适用于大多数场景:

# HTTP 服务器(80端口):仅用于跳转 HTTPS
server {
    listen 80;
    server_name example.com;  # 需与 HTTPS 服务器的域名一致

    # 永久重定向到 HTTPS
    return 301 https://$host$request_uri;
}
  • 优势:简单高效,搜索引擎会记住重定向,将权重转移到 HTTPS 域名。
2. 方案二:通过 rewrite 指令(灵活适配复杂场景)

若需对特定路径做特殊处理(如临时不跳转某些路径),可使用 rewrite

server {
    listen 80;
    server_name example.com;

    # 对 /api/temp 路径临时不跳转(示例)
    location /api/temp {
        # 保持 HTTP 访问(仅临时使用,不推荐长期保留)
        proxy_pass http://backend;
    }

    # 其他所有路径跳转 HTTPS
    location / {
        rewrite ^(.*)$ https://$host$1 permanent;  # permanent 等价于 301
    }
}

三、完整 HTTPS 配置示例

# HTTP 服务器:强制跳转 HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

# HTTPS 服务器:核心配置
server {
    listen 443 ssl;
    server_name example.com;

    # 证书配置
    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    # 协议与加密套件
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers on;

    # 性能与安全优化
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_compression off;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /path/to/fullchain.pem;
    resolver 8.8.8.8 114.114.114.114 valid=300s;

    # 前端资源配置(如 SPA 路由、缓存等)
    root /path/to/frontend;
    index index.html;
    location / {
        try_files $uri $uri/ /index.html;
    }
}

四、关键注意事项

  1. 证书路径正确性
    确保 ssl_certificatessl_certificate_key 指向的文件存在且权限正确(Nginx 进程需可读,建议权限 600)。

  2. HSTS 配置风险
    Strict-Transport-Security 头一旦设置,客户端会严格遵守(即使后续关闭 HTTPS 也会强制使用),需确保 HTTPS 服务长期稳定后再添加 preload 选项。

  3. 兼容性平衡
    若需支持非常旧的浏览器(如 IE 10),可临时启用 TLSv1.1,但需知晓安全风险;现代网站建议仅保留 TLSv1.2+。

  4. 配置验证
    修改配置后需执行 nginx -t 检查语法,通过 nginx -s reload 生效;可使用 SSL Labs 工具 检测配置安全性(目标评分 A+)。

总结

HTTPS 核心配置包括:

  • 证书与私钥(基础)、TLSv1.2+ 协议(安全)、强加密套件(防破解);
  • 强制跳转通过 301 重定向实现,确保所有 HTTP 请求转向 HTTPS。

合理配置可在安全性、兼容性和性能之间取得平衡,是现代网站的必备实践。

1147. 前端静态资源加载超时,Nginx 可通过哪些配置优化?【热度: 190】【web 应用场景】【出题公司: 阿里巴巴】

关键词:nginx 加载超时优化

前端静态资源(如 JS、CSS、图片、视频等)加载超时,通常与网络传输效率服务器响应速度资源处理策略相关。Nginx 可通过针对性配置优化传输效率、延长超时阈值、减少阻塞风险,从而解决超时问题。以下是具体优化方案:

一、延长关键超时时间(避免传输中断)

针对大资源(如视频、大型 JS 包)或弱网络环境,默认超时时间可能不足,需调整以下参数:

server {
    # 1. 客户端与服务器建立连接的超时(握手阶段)
    client_header_timeout 120s;  # 等待客户端发送请求头的超时(默认 60s,延长至 2 分钟)
    client_body_timeout 120s;    # 等待客户端发送请求体的超时(默认 60s)

    # 2. 服务器向客户端发送响应的超时(传输阶段,核心!)
    send_timeout 300s;  # 大文件传输时,服务器发送数据的超时(默认 60s,延长至 5 分钟)

    # 3. 长连接保持时间(复用连接,减少重复握手开销)
    keepalive_timeout 120s;  # 连接空闲后保持的时间(默认 75s,延长至 2 分钟)
    keepalive_requests 200;  # 单个长连接可处理的请求数(默认 100,提高至 200)
}

关键逻辑send_timeout 是防止大资源传输中断的核心参数(如 100MB 的视频文件,弱网环境可能需要几分钟传输),需根据资源最大体积和目标用户网络环境调整。

二、优化资源传输效率(减少传输耗时)

通过零拷贝数据合并压缩等技术,减少资源在服务器与客户端之间的传输时间:

1. 启用零拷贝与 TCP 优化
location ~* \.(js|css|png|jpg|jpeg|webp|mp4)$ {
    # 零拷贝:直接从磁盘读取文件发送到网络,跳过用户态-内核态数据拷贝(核心优化!)
    sendfile on;

    # 配合 sendfile 使用,积累数据后一次性发送,减少网络包数量(提升大文件传输效率)
    tcp_nopush on;

    # 禁用 Nagle 算法(减少小数据包延迟,适合动态内容,但大文件建议关闭)
    tcp_nodelay off;
}
2. 启用压缩(减小传输体积)

对文本类资源(JS/CSS/HTML)启用 gzip 或 brotli 压缩,对图片等二进制资源确保已预压缩(如 WebP 格式):

# 全局压缩配置
gzip on;
gzip_comp_level 5;  # 压缩级别 1-9(5 为平衡值)
gzip_min_length 1024;  # 仅压缩 >1KB 的文件(小文件压缩收益低)
gzip_types
    text/html text/css application/javascript
    application/json image/svg+xml;  # 仅压缩文本类资源

# 若 Nginx 安装了 brotli 模块(压缩率高于 gzip)
brotli on;
brotli_comp_level 6;
brotli_types text/css application/javascript;
3. 预压缩静态资源(避免实时压缩耗时)

提前对静态资源进行压缩(如 app.jsapp.js.gz),Nginx 直接返回预压缩文件,减少实时压缩的 CPU 消耗和延迟:

location ~* \.(js|css)$ {
    gzip_static on;  # 优先返回 .gz 预压缩文件(需手动生成或通过打包工具生成)
    brotli_static on;  # 优先返回 .br 预压缩文件
}

三、优化文件读取效率(减少服务器内部延迟)

静态资源加载超时可能是服务器磁盘 I/O 慢文件打开频繁导致,可通过缓存文件描述符优化:

# 缓存打开的文件描述符(减少重复打开文件的磁盘 I/O 耗时)
open_file_cache max=10000 inactive=30s;  # 最多缓存 10000 个文件,30s 未访问则移除
open_file_cache_valid 60s;  # 每 60s 验证一次缓存有效性
open_file_cache_min_uses 2;  # 文件被访问至少 2 次才加入缓存
open_file_cache_errors on;  # 缓存"文件不存在"的错误(避免重复检查)

效果:频繁访问的静态资源(如首页 JS/CSS)会被缓存描述符,后续请求无需再次读取磁盘,响应速度提升 50%+。

四、限制并发与请求大小(避免服务器过载)

服务器资源耗尽(CPU/内存/磁盘 I/O 满)会导致响应延迟,需通过配置限制并发压力:

1. 限制单个请求体大小

防止超大文件请求阻塞服务器(如恶意上传 1GB 无效文件):

# 全局限制:单个请求体最大 100MB(根据业务调整,如图片站可设 50MB)
client_max_body_size 100m;

# 针对视频等超大资源单独限制
location /videos {
    client_max_body_size 500m;  # 视频文件最大 500MB
}
2. 调整 worker 进程与连接数

充分利用服务器 CPU 资源,提升并发处理能力:

# 在 nginx.conf 全局配置中
worker_processes auto;  # 自动设置为 CPU 核心数(如 4 核服务器则启动 4 个进程)
worker_connections 10240;  # 每个 worker 最大连接数(默认 1024,提高至 10240)
multi_accept on;  # 允许每个 worker 同时接受多个新连接

五、CDN 与资源分片配合(彻底解决跨地域超时)

若用户分布在不同地域,仅靠源站优化效果有限,需结合:

  1. 静态资源托管到 CDN
    将 JS/CSS/图片等静态资源上传至 CDN(如 Cloudflare、阿里云 CDN),CDN 节点就近分发,减少跨地域传输延迟。
    Nginx 需配置允许 CDN 缓存:

    location ~* \.(js|css|png)$ {
        add_header Cache-Control "public, max-age=31536000";  # 允许 CDN 长期缓存
        add_header Access-Control-Allow-Origin *;  # 解决 CDN 跨域问题
    }
    
  2. 大文件分片传输
    对视频等超大型文件(>100MB),前端通过 Range 请求分片下载(如每次请求 10MB),Nginx 需支持 Range 分片(默认支持,无需额外配置):

    location /videos {
        add_header Accept-Ranges bytes;  # 显式声明支持分片(默认已开启)
    }
    

六、完整优化配置示例

# nginx.conf 全局配置
worker_processes auto;
worker_connections 10240;
multi_accept on;

http {
    # 压缩配置
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 1024;
    gzip_types text/html text/css application/javascript;
    gzip_static on;

    # 文件描述符缓存
    open_file_cache max=10000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;

    server {
        listen 80;
        server_name example.com;

        # 超时配置
        client_header_timeout 120s;
        client_body_timeout 120s;
        send_timeout 300s;
        keepalive_timeout 120s;
        keepalive_requests 200;

        # 请求体大小限制
        client_max_body_size 100m;

        # 静态资源优化
        location ~* \.(js|css|png|jpg|jpeg|webp|mp4)$ {
            root /path/to/frontend;
            sendfile on;
            tcp_nopush on;
            tcp_nodelay off;
            expires 30d;  # 浏览器缓存,减少重复请求
        }

        # 视频等大资源单独配置
        location /videos {
            client_max_body_size 500m;
            add_header Accept-Ranges bytes;
        }
    }
}

总结

Nginx 优化静态资源加载超时的核心思路是:

  1. 延长传输超时send_timeout),适应大资源和弱网络;
  2. 提升传输效率(零拷贝、压缩、预压缩),减少传输时间;
  3. 优化服务器性能(文件缓存、并发调整),减少内部延迟;
  4. 结合 CDN 与分片,解决跨地域传输问题。

通过多层优化,可显著降低静态资源加载超时概率,提升前端页面加载体验。

1148. Nginx 如何为不同前端资源配置缓存策略?如何强制刷新特定资源?【热度: 110】【web 应用场景】

关键词:nginx 加载特定资源

Nginx 为不同前端资源配置缓存策略的核心是根据资源特性(是否常变、是否带版本标识)差异化设置缓存规则,同时通过特定机制实现特定资源的强制刷新。以下是详细方案:

一、按资源类型配置差异化缓存策略

前端资源可分为静态资源(JS、CSS、图片等)和入口文件(如 index.html),需根据其更新频率和版本管理方式设置不同缓存策略:

1. 带哈希/版本号的静态资源(永久强缓存)

特征:文件名含唯一哈希(如 app.8f3b.js)或版本号(如 v2/style.css),内容变化时文件名必变。
策略:设置长期强缓存,减少重复请求。

# 匹配带哈希的 JS/CSS/图片(假设哈希为 8-16 位字符)
location ~* \.\w{8,16}\.(js|css|png|jpg|jpeg|webp|svg)$ {
    # 缓存 1 年(31536000 秒)
    expires 365d;
    # 强缓存标识:浏览器直接使用本地缓存,不发送请求
    add_header Cache-Control "public, max-age=31536000, immutable";
}
  • 关键参数immutable(H5 新特性)告知浏览器资源不会变化,避免发送无效的条件请求(如 If-Modified-Since)。
2. 无哈希的静态资源(短期强缓存 + 协商缓存)

特征:文件名固定(如 favicon.icocommon.js),可能不定期更新但无版本标识。
策略:短期强缓存减少请求,过期后通过协商缓存验证是否更新。

# 匹配无哈希的图片、字体等
location ~* \.(png|jpg|jpeg|ico|woff2?)$ {
    # 短期强缓存 7 天
    expires 7d;
    # 过期后必须验证是否更新
    add_header Cache-Control "public, max-age=604800, must-revalidate";
}
3. 入口文件与动态页面(协商缓存)

特征:如 index.htmlpage.html,作为路由入口或动态内容载体,需确保用户获取最新版本。
策略:禁用强缓存,每次请求通过协商缓存验证。

# 入口文件(如 index.html)
location = /index.html {
    # 禁用强缓存(立即过期)
    expires -1;
    # 协商缓存:必须向服务器验证
    add_header Cache-Control "no-cache, must-revalidate";
}

# 其他 HTML 页面
location ~* \.html$ {
    expires -1;
    add_header Cache-Control "no-cache, must-revalidate";
}
  • 协商缓存原理:Nginx 自动返回 Last-Modified(文件修改时间),浏览器下次请求携带 If-Modified-Since,服务器比对后返回 304(未修改)或 200(新内容)。
4. API 接口与动态数据(无缓存或短时缓存)

特征:如 /api/user,返回动态数据,需实时性。
策略:禁用缓存或设置极短缓存时间。

# API 接口
location /api {
    # 完全禁用缓存
    add_header Cache-Control "no-store, no-cache, must-revalidate";
    expires -1;
    # 转发到后端服务
    proxy_pass http://backend;
}

二、强制刷新特定资源的方法

当资源更新但因缓存未生效时,需强制用户获取最新版本,核心思路是破坏缓存标识主动清理缓存

1. 前端主动更新资源标识(推荐)

利用“哈希/版本号与内容绑定”的特性,资源更新时修改文件名,浏览器会视为新资源自动请求:

  • 例:app.8f3b.js → 更新后变为 app.9c4d.js,无需 Nginx 配置,彻底避免缓存问题。
2. 通过 URL 参数强制刷新(临时方案)

对无哈希的资源,可在请求 URL 后添加随机参数(如 ?v=2),使浏览器认为是新资源:

  • 例:common.jscommon.js?v=2
  • Nginx 无需额外配置,但需前端手动更新参数,适合临时紧急更新。
3. 清理 CDN 缓存(若使用 CDN)

若资源通过 CDN 分发,需在 CDN 控制台手动清理特定资源缓存:

  • 例:阿里云 CDN 支持按路径(如 /*/*.js)或具体 URL 清理缓存,生效后用户请求会回源获取最新资源。
4. 动态修改资源的 Last-Modified(不推荐)

通过 Nginx 指令强制修改资源的 Last-Modified 头,触发协商缓存更新:

# 强制刷新某个资源(如 common.js)
location = /static/js/common.js {
    # 手动设置一个较新的修改时间(比实际文件新)
    add_header Last-Modified "Wed, 20 Sep 2025 08:00:00 GMT";
    # 协商缓存配置
    expires -1;
    add_header Cache-Control "no-cache, must-revalidate";
}
  • 缺点:需手动修改 Nginx 配置并 reload,仅适合紧急情况,不建议长期使用。

三、完整缓存配置示例

server {
    listen 80;
    server_name example.com;
    root /path/to/frontend;

    # 1. 带哈希的静态资源(永久缓存)
    location ~* \.\w{8,16}\.(js|css|png|jpg|jpeg|webp|svg)$ {
        expires 365d;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }

    # 2. 无哈希的静态资源(短期+协商)
    location ~* \.(png|jpg|jpeg|ico|woff2?)$ {
        expires 7d;
        add_header Cache-Control "public, max-age=604800, must-revalidate";
    }

    # 3. 入口文件与 HTML(协商缓存)
    location = /index.html {
        expires -1;
        add_header Cache-Control "no-cache, must-revalidate";
    }

    # 4. API 接口(无缓存)
    location /api {
        add_header Cache-Control "no-store, no-cache";
        expires -1;
        proxy_pass http://backend;
    }

    # SPA 路由支持(配合 History 模式)
    location / {
        try_files $uri $uri/ /index.html;
    }
}

四、关键注意事项

  1. 缓存与版本管理协同:前端打包工具(Webpack/Vite)需确保“内容变则哈希变”,与 Nginx 强缓存配合,这是最可靠的刷新方式。
  2. 避免缓存 index.html:入口文件必须用协商缓存,否则用户可能无法获取新的哈希资源列表。
  3. HTTPS 环境下的缓存:若启用 HTTPS,需确保 Cache-Control 头正确传递(Nginx 默认不拦截),避免 CDN 或代理服务器篡改缓存策略。

总结

  • 差异化缓存:带哈希资源用永久强缓存,无哈希资源用短期+协商缓存,入口文件和 API 禁用强缓存。
  • 强制刷新:优先通过修改资源哈希/版本号实现,临时场景可用 URL 参数,CDN 资源需手动清理 CDN 缓存。

这种策略既能最大化利用缓存提升性能,又能确保资源更新及时生效。

昨天以前首页

前端面试第 77 期 - 2025.09.02 更新前端面试问题总结(15 道题)

作者 晴小篆
2025年9月2日 21:58

2025.07.06 - 2025.08.31 更新前端面试问题总结(15 道题)
获取更多面试相关问题可以访问
github 地址: github.com/pro-collect…
gitee 地址: gitee.com/yanleweb/in…

目录

中级开发者相关问题【共计 9 道题】

  1. 深层清理对象中的空值属性【热度: 234】【代码实现/算法】
  2. 介绍一下 git stash【热度: 386】【web 应用场景】
  3. JS 里面, 对于对象的读写, 是使用 object 好,还是 Map,性能差异如何?【热度: 610】【JavaScript】【出题公司: 阿里巴巴】
  4. less 与 scss 有何区别【热度: 61】【web 应用场景】【出题公司: 腾讯】
  5. less 与 css 有何区别【热度: 214】【web 应用场景】【出题公司: 腾讯】
  6. 用 css 实现一个 loading 动画, 该如何做(转圈)【热度: 180】【CSS】
  7. ts 有哪些常用的关键词【热度: 178】【TypeScript】【出题公司: 美团】
  8. 对比一下 ts 和 jsdoc【热度: 126】【TypeScript】
  9. react 开发的应用里面, 如何给系统设置一个全局的崩溃的提示页面【热度: 725】【web 框架】【出题公司: 小米】

高级开发者相关问题【共计 6 道题】

  1. 将网页 dom 元素转为图片, 有哪些办法【热度: 41】【web 应用场景】
  2. 介绍一下 git diff【热度: 396】【web 应用场景】
  3. less 是否支持条件判定【热度: 112】【web 应用场景】【出题公司: 腾讯】
  4. less 有那些高级特性, 驱使你们项目工程去使用它【热度: 336】【web 应用场景】【出题公司: 腾讯】
  5. ts 里面 infer 是什么关键词, 怎么用【热度: 975】【TypeScript】【出题公司: 美团】
  6. TypeScript 中,ReturnType 的作用和用法【TypeScript】

中级开发者相关问题【共计 9 道题】

1123. 深层清理对象中的空值属性【热度: 234】【代码实现/算法】

请实现一个函数 deepOmitNil,要求如下:

  1. 功能:递归处理一个可能包含嵌套结构的对象(或数组),移除所有层级中值为 nullundefined 的属性

  2. 要求:

    • 支持对象和数组的嵌套结构
    • 对于对象:移除值为 null/undefined 的属性,保留其他属性并继续递归处理属性值
    • 对于数组:递归处理每个元素,同时过滤掉值为 null/undefined 的元素
    • 不改变原始数据结构,返回处理后的新数据
  3. 示例:

    // 输入
    const data = {
      name: 'test',
      age: null,
      info: {
        address: undefined,
        contact: {
          phone: null,
          email: 'test@example.com'
        }
      },
      list: [1, null, { id: undefined, value: 2 }, undefined, 3]
    };
    
    // 输出
    {
      name: 'test',
      info: {
        contact: {
          email: 'test@example.com'
        }
      },
      list: [1, { value: 2 }, 3]
    }
    

请用 JavaScript 实现该函数,可使用 Lodash 工具库辅助开发。

解法

function deepOmitNil(value) {
  // 处理null和undefined的情况
  if (value === null || value === undefined) {
    return null;
  }

  // 处理数组:递归处理每个元素,并过滤掉null和undefined
  if (Array.isArray(value)) {
    return value.map((item) => deepOmitNil(item)).filter((item) => item !== null && item !== undefined);
  }

  // 处理普通对象:检查是否为纯粹的对象(排除数组、null等)
  if (Object.prototype.toString.call(value) === "[object Object]") {
    const result = {};
    // 遍历对象自身属性
    for (const key in value) {
      if (value.hasOwnProperty(key)) {
        const processedValue = deepOmitNil(value[key]);
        if (processedValue !== null && processedValue !== undefined) {
          result[key] = processedValue;
        }
      }
    }
    return result;
  }

  // 其他类型直接返回(如字符串、数字、布尔值等)
  return value;
}

// 示例用法
const data = {
  name: "test",
  age: null,
  info: {
    address: undefined,
    contact: {
      phone: null,
      email: "test@example.com",
    },
  },
  list: [1, null, { id: undefined, value: 2 }, undefined, 3],
};

console.log(deepOmitNil(data));

1124. 介绍一下 git stash【热度: 386】【web 应用场景】

关键词:git stash

git stash 是 Git 中一个非常实用的命令,用于临时保存工作区和暂存区的修改,让你可以在不提交当前变更的情况下,切换到其他分支或进行其他操作,之后还能恢复这些临时保存的变更。

核心作用

当你正在一个分支上开发,突然需要切换到其他分支(比如修复紧急 Bug),但当前工作还没完成不想提交时,git stash 可以:

  • 把工作区和暂存区的所有修改(包括新增、修改、删除的文件)暂存到一个“栈”中
  • 让工作区回到最近一次提交的干净状态(与 git reset --hard HEAD 效果类似,但变更被临时保存了)
  • 之后可以随时从“栈”中恢复这些变更,继续之前的工作

常用命令

  1. 暂存当前变更

    git stash
    # 或添加描述(推荐,方便区分多个stash)
    git stash save "描述信息:例如「首页导航栏修改」"
    

    执行后,工作区会恢复到干净状态,变更被存入 stash 栈。

  2. 查看所有暂存的变更

    git stash list
    

    输出类似:

    stash@{0}: On feature/login: 修复登录按钮样式
    stash@{1}: On develop: 临时添加调试日志
    

    stash@{n} 是每个暂存的唯一标识,n 越小表示越新。

  3. 恢复暂存的变更

    • 恢复最新的 stash(stash@{0}),且保留 stash 记录:
      git stash apply
      
    • 恢复指定的 stash(例如 stash@{1}):
      git stash apply stash@{1}
      
    • 恢复最新的 stash 并删除该 stash 记录(推荐用完即删的场景):
      git stash pop
      
  4. 删除暂存的变更

    • 删除最新的 stash:
      git stash drop
      
    • 删除指定的 stash:
      git stash drop stash@{1}
      
    • 删除所有 stash:
      git stash clear
      
  5. 查看 stash 中的具体修改
    查看最新 stash 与当前工作区的差异:

    git stash show
    

    查看详细差异(显示具体修改的内容):

    git stash show -p
    

注意事项

  • git stash 只会暂存已跟踪文件(即已被 Git 管理的文件)的修改,以及已添加到暂存区的新增文件。未跟踪的全新文件(未执行过 git add)不会被暂存,需要先执行 git add 或使用 git stash -u-u 表示包括未跟踪文件)。
  • 恢复 stash 时,如果当前工作区有修改,可能会出现冲突,需要手动解决。
  • stash 存储在本地仓库,不会被推送到远程,切换电脑后无法获取。

简单来说,git stash 就像一个“剪贴板”,让你可以临时“剪切”当前工作状态,稍后再“粘贴”回来,非常适合处理多任务切换的场景。

1126. JS 里面, 对于对象的读写, 是使用 object 好,还是 Map,性能差异如何?【热度: 610】【JavaScript】【出题公司: 阿里巴巴】

关键词:Object 与 Map

在 JavaScript 中,ObjectMap 都可以用于存储键值对,但它们的设计目标和性能特性存在差异,选择哪一种取决于具体使用场景。

核心差异与性能对比

1. 内存占用
  • Object
    本质是原型链继承的对象,会默认一些额外属性(如 __proto__constructor),且键只能是字符串或 Symbol。
    对于少量键值对,内存开销较小,但键名会被强制转换为字符串(如数字 1 会转为 "1")。

  • Map
    专为键值对存储设计,无原型链开销,键可以是任意类型(包括对象、函数等)。
    但内部实现会维护哈希表结构,存储相同数量的键值对时,内存占用通常比 Object 略高(尤其键值对较少时)。

2. 读写性能
  • Object

    • 读取/写入速度:对于静态键(提前确定的键名),访问速度极快,因为 JavaScript 引擎会对对象属性进行优化(如静态属性的偏移量缓存)。
    • 动态键场景:如果键名是动态生成的(如通过变量拼接),性能会略有下降(需哈希计算),但仍优于 Map 对非字符串键的处理。
  • Map

    • 读取/写入速度:对于频繁的增删改查(尤其是动态键或非字符串键),性能更稳定。
    • 优势体现在:键可以是任意类型(无需转换)、内部哈希表优化更适合高频动态操作。
    • 劣势:对于静态字符串键,访问速度通常比 Object 慢 10%-30%(不同引擎优化不同)。
3. 遍历性能
  • Object
    遍历需要先获取键名(Object.keys() 等),再迭代访问,步骤较多。
    且会遍历自身可枚举属性(需注意原型链污染问题),额外消耗性能。

  • Map
    原生支持迭代器(for...of 直接遍历),遍历速度通常比 Object 快,尤其是键值对数量较多时。
    Mapsize 属性可直接获取长度(Object 需要 keys().length 计算),更高效。

4. 极端场景测试
  • 小规模数据(<100 键值对)
    Object 性能略优,内存占用更低,适合简单配置、数据存储。

  • 大规模数据(>1000 键值对)
    Map 在频繁增删、动态键、遍历场景下性能更稳定,Object 可能因哈希冲突导致性能波动。

  • 非字符串键
    Map 优势明显(无需转换键类型),Object 需要手动处理键名转换(如将对象转为字符串标识),既麻烦又影响性能。

使用建议

场景 推荐选择 理由
静态键名(如 { name: 'a' } Object 访问速度快,语法简洁,适合数据结构固定的场景(如配置、DTO)。
动态键名(如变量作为键) Map 无需处理键名转换,增删改查性能更稳定。
非字符串键(对象、函数等) Map 原生支持任意类型键,Object 会强制转换键为字符串,可能导致冲突。
频繁增删或遍历 Map 迭代器优化更好,size 属性获取高效,适合缓存、集合类场景。
序列化需求(JSON.stringify Object Map 无法直接序列化,需手动转换为对象,Object 原生支持。

总结

  • Object 适合静态、简单的键值对存储,语法简洁,内存占用低,静态访问速度快。
  • Map 适合动态、复杂的键值对场景(尤其是非字符串键、高频增删遍历),性能更稳定。

性能差异在大多数业务场景中不明显,优先根据代码可读性和功能需求选择,极端性能敏感场景(如大数据处理)再针对性优化。

1129. less 与 scss 有何区别【热度: 61】【web 应用场景】【出题公司: 腾讯】

关键词:less 与 scss

Less 和 Sass(通常以 SCSS 语法使用)是最流行的两款 CSS 预处理器,都旨在解决原生 CSS 的局限性(如缺乏变量、嵌套、复用机制等),但在语法细节、功能设计和生态上存在诸多差异。以下从核心区别、使用场景等方面详细对比:

一、语法差异

这是最直观的区别,直接影响开发体验。

特性 Less SCSS(Sass 的新语法)
语法风格 更接近原生 CSS,无需强制使用分号和大括号 完全兼容 CSS 语法,必须使用分号和大括号
变量声明 @variable: value; $variable: value;
嵌套规则 支持,与 SCSS 类似 支持,与 Less 类似
注释 单行 //(编译后移除)和多行 /* */(保留) 同 Less
示例代码 less<br>.container {<br> color: @text-color;<br> .box { padding: 10px }<br>}<br> scss<br>.container {<br> color: $text-color;<br> .box { padding: 10px; }<br>}<br>

关键区别

  • Less 语法更灵活,允许省略分号和大括号(类似 Stylus),但通常推荐保留以保持一致性;
  • SCSS 严格要求分号和大括号,完全兼容 CSS,因此从 CSS 迁移到 SCSS 几乎零成本。

二、变量与作用域

两者都支持变量,但作用域规则和特性有差异。

  1. 变量符号

    • Less 用 @(如 @color: red;);
    • SCSS 用 $(如 $color: red;),避免与 CSS 原生 @ 规则(如 @media)冲突。
  2. 作用域行为

    • Less:变量遵循「延迟加载」(Lazy Loading),即变量在使用前无需声明,作用域内后定义的变量会覆盖先定义的。
      .box {
        color: @color; // 允许使用后定义的变量
        @color: red;
      }
      
    • SCSS:变量必须先声明后使用,作用域更严格(类似 JavaScript)。
      .box {
        color: $color; // 报错:$color 未定义
        $color: red;
      }
      
  3. 全局变量

    • SCSS 需用 !global 关键字显式声明全局变量(局部作用域中):
      .box {
        $color: red !global; // 声明为全局变量
      }
      .text {
        color: $color;
      } // 可访问
      
    • Less 中变量默认全局有效(局部变量会覆盖全局,但不会污染全局)。

三、混合(Mixins)与函数

两者都支持代码复用,但实现方式和功能有差异。

1. 混合(Mixins)

用于复用样式片段。

  • Less
    混合无需特殊关键字,直接定义类或 id 选择器,使用时加括号(可选):

    // 定义混合
    .border-radius(@radius: 4px) {
      border-radius: @radius;
    }
    // 使用混合(可省略括号)
    .btn {
      .border-radius; // 或 .border-radius(8px)
    }
    
  • SCSS
    混合必须用 @mixin 定义,用 @include 调用,语法更明确:

    // 定义混合
    @mixin border-radius($radius: 4px) {
      border-radius: $radius;
    }
    // 使用混合
    .btn {
      @include border-radius(8px);
    }
    
2. 函数(Functions)

用于计算值并返回结果(不直接生成样式)。

  • Less:函数功能较弱,主要依赖内置函数(如 darken()lighten()),自定义函数需通过混合模拟(不支持返回值)。
  • SCSS:支持用 @function 自定义函数,可返回值,功能更强大:
    // 自定义函数:计算百分比宽度
    @function col-width($n) {
      @return ($n / 12) * 100%;
    }
    .col-6 {
      width: col-width(6); // 50%
    }
    

四、条件与循环

处理动态逻辑的能力不同。

1. 条件判断
  • Less:通过 when 关键字实现条件(Guards),语法较特殊:

    .theme(@type) when (@type = "dark") {
      background: #333;
    }
    .box {
      .theme("dark");
    }
    
  • SCSS:支持 @if/@else 语句,更接近传统编程语言:

    @mixin theme($type) {
      @if $type == "dark" {
        background: #333;
      } @else {
        background: #fff;
      }
    }
    .box {
      @include theme("dark");
    }
    
2. 循环
  • Less:通过混合自调用实现循环,语法较繁琐:

    .loop(@n) when (@n > 0) {
      .item-@{n} {
        width: @n * 10px;
      }
      .loop(@n - 1);
    }
    .loop(3); // 生成 .item-3、.item-2、.item-1
    
  • SCSS:提供 @for/@each/@while 多种循环语法,更直观:

    // @for 循环
    @for $i from 1 through 3 {
      .item-#{$i} {
        width: $i * 10px;
      }
    }
    

五、模块化与导入

处理样式文件拆分的方式。

  • Less

    • @import "file.less"; 导入文件,支持条件导入(结合 when):
      @import "theme.less" when (@theme = "dark");
      
    • 无内置模块化机制,需通过工具(如 Webpack)实现按需加载。
  • SCSS

    • @import "file.scss"; 导入文件,支持嵌套导入(在选择器内导入,作用域受限):
      .box {
        @import "partial.scss"; // 仅在 .box 内生效
      }
      
    • 支持 @use@forward(Sass 3.8+),实现更严格的模块化(类似 ES6 模块),避免变量冲突:
      // 导入并命名空间
      @use "variables" as vars;
      .box {
        color: vars.$text-color;
      }
      

六、生态与工具链

  • SCSS

    • 由 Ruby 开发(后部分用 C 重写),但现在主流通过 dart-sass 编译(性能更好);
    • 生态更成熟,广泛用于 React、Vue 等框架的组件库(如 Ant Design、Bootstrap 4+);
    • 工具支持完善(如 VS Code 的 Sass 插件、Webpack 的 sass-loader)。
  • Less

    • 基于 JavaScript 开发,编译速度快(尤其在 Node.js 环境);
    • 生态相对较小,但在早期前端框架(如 Bootstrap 3)中广泛使用;
    • 工具支持同样完善(如 less-loader)。

七、选择建议

场景/需求 推荐选择 理由
从 CSS 平滑迁移 SCSS 语法完全兼容 CSS,学习成本低
需要强大的函数和逻辑能力 SCSS 支持 @function@if/@for 等,适合复杂计算和动态样式
追求简洁语法和快速上手 Less 语法更灵活,变量和混合使用更简单
大型项目/组件库开发 SCSS 模块化机制(@use/@forward)更规范,避免变量冲突
依赖 JavaScript 生态(如 Node.js) Less 基于 JS 开发,与 Node 工具链集成更自然
团队已有 CSS 代码库 SCSS 无需修改原有 CSS 语法即可直接使用

总结

  • Less 更轻量、语法更灵活,适合中小型项目或追求简单上手的场景;
  • SCSS 功能更强大、语法更规范,适合大型项目或需要复杂逻辑的场景,且生态更成熟。

两者核心功能(变量、嵌套、混合)重叠度高,选择时可根据团队熟悉度、项目规模和工具链兼容性决定。

1130. less 与 css 有何区别【热度: 214】【web 应用场景】【出题公司: 腾讯】

关键词:less 与 css

Less 是 CSS 的预处理器,它在原生 CSS 的基础上扩展了诸多功能,解决了原生 CSS 开发中的痛点(如缺乏变量、复用机制、逻辑处理等)。以下是 Less 与 CSS 的核心区别:

1. 语法与功能

特性 CSS(原生) Less
变量支持 无(CSS 变量 --var 是较新特性,兼容性和功能有限) 支持 @variable: value 定义变量,可全局复用、参与运算,优先级清晰
嵌套规则 需重复书写父选择器,代码冗余(如 .parent .child { ... } 支持选择器嵌套(类似 HTML 结构),减少重复代码,层级更清晰
代码复用 无原生复用机制,需复制粘贴或依赖 @import 导入整个文件 支持「混合(Mixin)」复用样式片段,可带参数实现个性化复用
逻辑处理 无(仅能通过 @media 实现简单条件,无循环、判断) 支持条件判断(when)、循环(混合自调用)、运算(加减乘除、颜色计算)
模块化 @import 导入整个 CSS 文件,增加请求数,无作用域隔离 支持导入 Less 文件,可结合 (reference) 仅引用变量/混合,避免冗余代码
内置工具 提供颜色处理(darken()lighten())、数值计算(percentage())等函数

2. 开发效率

  • CSS
    编写重复代码多(如相同颜色、尺寸需多次书写),修改时需全局搜索替换,维护成本高。
    示例(重复代码问题):

    .btn {
      background: #1890ff;
    }
    .card {
      border-color: #1890ff;
    }
    .title {
      color: #1890ff;
    }
    /* 若要修改主题色,需逐个修改 */
    
  • Less
    通过变量、混合等特性减少重复代码,修改时只需调整一处,开发和维护效率大幅提升。
    示例(解决重复问题):

    @primary: #1890ff; /* 变量定义 */
    .btn {
      background: @primary;
    }
    .card {
      border-color: @primary;
    }
    .title {
      color: @primary;
    }
    /* 修改主题色只需改 @primary 即可 */
    

3. 编译方式

  • CSS
    是浏览器可直接解析的样式语言,无需编译,写完即可运行。

  • Less
    是「预编译语言」,浏览器无法直接识别,必须通过 Less 编译器(如 lessc、Webpack 的 less-loader)转换为 CSS 后才能被浏览器解析。
    流程:Less 代码 → 编译 → CSS 代码 → 浏览器执行

4. 代码结构

  • CSS
    层级嵌套需通过后代选择器实现,代码冗长且层级不直观。
    示例:

    .header {
      padding: 20px;
    }
    .header .logo {
      width: 100px;
    }
    .header .nav {
      margin-left: 20px;
    }
    .header .nav li {
      display: inline-block;
    }
    
  • Less
    支持嵌套语法,结构与 HTML 一致,层级清晰,减少选择器冗余。
    示例:

    .header {
      padding: 20px;
      .logo {
        width: 100px;
      }
      .nav {
        margin-left: 20px;
        li {
          display: inline-block;
        }
      }
    }
    

5. 适用场景

  • CSS
    适合简单页面(如静态页面、小网站),或需要快速编写、无需复杂逻辑的场景。

  • Less
    适合中大型项目、组件库开发或需要频繁复用样式、动态调整主题的场景(如通过变量切换主题色、响应式适配)。

总结

Less 不是替代 CSS,而是对 CSS 的增强:

  • CSS 是基础:浏览器最终执行的是 CSS,它是样式的“目标语言”。
  • Less 是工具:通过提供变量、嵌套、复用等功能,让开发者更高效地编写 CSS,最终仍需编译为 CSS 才能运行。

简单说,Less 解决了原生 CSS“写起来麻烦、改起来痛苦”的问题,是现代前端开发中提升样式开发效率的主流选择。

1131. 用 css 实现一个 loading 动画, 该如何做(转圈)【热度: 180】【CSS】

关键词:css 动画

作者备注

这个问题主要是对 css 动画的考察, 比直接问 animation 和 transform 属性有意义。

可以利用 CSS 的 animation 和 transform 属性,通过旋转一个带有渐变边框的元素来实现。

这个转圈 loading 动画的核心实现思路如下:

  1. 创建圆形元素

    • 使用 widthheight 设置相同的尺寸
    • 通过 border-radius: 50% 将方形元素变成圆形
  2. 设计边框样式

    • 设置一个较粗的边框(border
    • 让大部分边框保持半透明(rgba(255, 255, 255, 0.3)
    • 只让顶部边框使用实色(border-top-color),形成旋转时的流动效果
  3. 添加旋转动画

    • 定义 spin 动画,通过 transform: rotate(360deg) 实现 360 度旋转
    • 使用 animation 属性应用动画,设置 1s 为一个周期,ease-in-out 缓动效果,infinite 无限循环

直接上代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>CSS Loading Spinner</title>
    <style>
      /* 基础样式设置 */
      body {
        margin: 0;
        padding: 0;
        min-height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: #f0f2f5;
      }

      /* 加载动画容器 */
      .loading-spinner {
        /* 动画大小 */
        width: 50px;
        height: 50px;

        /* 创建圆形边框 */
        border: 5px solid rgba(255, 255, 255, 0.3); /* 浅色边框 */
        border-top-color: #1677ff; /* 高亮边框(旋转时形成流动效果) */
        border-radius: 50%; /* 圆形 */

        /* 旋转动画 */
        animation: spin 1s ease-in-out infinite;
      }

      /* 旋转动画定义 */
      @keyframes spin {
        to {
          /* 360度旋转 */
          transform: rotate(360deg);
        }
      }
    </style>
  </head>
  <body>
    <!-- 加载动画元素 -->
    <div class="loading-spinner"></div>
  </body>
</html>

1133. ts 有哪些常用的关键词【热度: 178】【TypeScript】【出题公司: 美团】

关键词:ts 关键词

作者备注

这个问题主要是对 ts 类型熟悉程度的考察, 比直接问 number、string 等基础类型有意义。

TypeScript 在 JavaScript 基础上扩展了许多用于类型定义和类型控制的关键字,这些关键字是构建 TypeScript 类型系统的核心。以下是常用的关键词分类及说明:

以下是 TypeScript 常用关键字的分类汇总表:

| 分类 | 关键字/操作符 | 主要用途 | | ------------- | ------------------------------ | ------------------------------------------------------------ | ---------------------------- | | 基础类型 | number string boolean | 定义数字、字符串、布尔值类型 | | | null undefined | 定义空值类型 | | | void | 表示函数无返回值 | | | any unknown | any 关闭类型检查;unknown 安全的未知类型(需断言后使用) | | | never | 表示永远不会发生的类型(如抛出错误) | | 复合类型 | arrayT[]Array<T>) | 定义数组类型 | | | interface | 定义对象结构(可扩展、继承) | | | type | 类型别名(支持联合、交叉等复杂类型) | | | enum | 定义命名常量集合 | | | tuple[T1, T2]) | 固定长度和类型的数组 | | 类型操作 | |(联合类型) | 表示“或”关系(类型可选其一) | | | &(交叉类型) | 表示“且”关系(合并多个类型) | | | extends | 泛型约束;接口/类继承 | | | infer | 条件类型中推断类型(如提取函数返回值) | | | keyof | 获取对象类型的所有键名组成的联合类型 | | | typeof | 获取变量/属性的类型 | | | as | 类型断言(指定变量的实际类型) | | 函数/泛型 | function | 定义函数(指定参数和返回值类型) | | | 泛型(T U 等) | 定义通用类型,实现类型复用 | | 模块/命名 | export import | 模块导出/导入 | | | namespace | 定义命名空间(避免全局变量冲突) | | 其他常用 | readonly | 定义只读属性或数组 | | | declare | 声明全局变量/模块类型(用于 .d.ts 文件) | | | abstract | 定义抽象类/抽象方法(需子类实现) |

这个表涵盖了日常开发中最常用的关键字,可根据场景快速查阅。核心重点关注 类型声明number/interface 等)、类型操作extends/keyof/infer)和 泛型相关 关键字,它们是 TypeScript 类型系统的核心。

1134. 对比一下 ts 和 jsdoc【热度: 126】【TypeScript】

关键词:ts 对比 jsdoc

TypeScript(TS)和 JSDoc 都是用于增强 JavaScript 代码类型安全性的工具,但它们的实现方式和适用场景有显著差异。以下是两者的对比分析及优劣总结:

TypeScript 与 JSDoc 对比表

维度 TypeScript(TS) JSDoc
本质 是 JavaScript 的超集,需要编译为 JS 才能运行,自带完整的类型系统 是 JavaScript 的注释规范,通过注释标注类型,无需编译,浏览器/Node 可直接运行
类型定义方式 使用专门的类型语法(如 : numberinterfacetype 等) 使用注释标签(如 @param@returns@type 等)标注类型
类型检查时机 编译时强制类型检查,错误会在开发阶段暴露 依赖 IDE/编辑器(如 VS Code)的 TypeScript 服务进行类型检查,非强制
功能丰富度 类型系统强大(泛型、交叉/联合类型、条件类型、枚举等) 支持基础类型标注,复杂类型(如泛型约束、条件类型)表达能力有限
生态与工具 生态成熟,支持所有主流框架(React、Vue 等),有大量类型声明文件(.d.ts 依赖 TypeScript 语言服务,可复用 .d.ts 文件,但工具链集成较弱
学习成本 较高,需学习专门的类型语法和概念 较低,基于注释,语法简单,熟悉 JS 即可快速上手
项目侵入性 高,需将文件改为 .ts 后缀,可能需要调整构建流程 低,不改变 JS 代码结构,仅添加注释,原有 JS 项目可平滑接入
运行时影响 无(编译后为纯 JS),但类型信息会被完全擦除 无(注释不影响运行),类型信息仅存在于代码中
适用场景 大型项目、团队协作、对类型安全性要求高的场景 小型项目、快速原型、希望保持纯 JS 但需要基础类型提示的场景

核心优劣总结

TypeScript 的优势:
  1. 强类型检查:编译阶段强制校验,能提前发现更多类型错误,减少运行时问题。
  2. 丰富的类型功能:支持泛型、条件类型、枚举等高级特性,能精确描述复杂数据结构。
  3. 工具链完善:与主流 IDE、构建工具(Webpack、Vite)深度集成,开发体验好。
  4. 团队协作友好:类型定义作为“活文档”,清晰传达接口设计,降低沟通成本。
TypeScript 的劣势:
  1. 学习成本高:需要掌握额外的类型语法和概念(如 inferkeyof 等)。
  2. 项目侵入性强:需要修改文件后缀、配置编译流程,对纯 JS 项目迁移有一定成本。
  3. 编译开销:增加编译步骤,可能略微影响开发效率(尤其大型项目)。
JSDoc 的优势:
  1. 零成本接入:无需修改代码结构,仅通过注释标注类型,原有 JS 项目可直接使用。
  2. 无编译步骤:保留纯 JS 特性,浏览器/Node 可直接运行,适合快速迭代。
  3. 学习成本低:语法简单,基于注释标签,熟悉 JS 的开发者可快速上手。
JSDoc 的劣势:
  1. 类型能力有限:复杂类型(如泛型约束、条件类型)难以表达,类型检查较弱。
  2. 非强制检查:依赖 IDE 提示,无法在构建阶段强制报错,可能遗漏类型问题。
  3. 注释冗余:大量类型注释会增加代码体积,影响可读性(尤其复杂类型)。

总结建议

  • 若项目规模大、团队协作频繁、对类型安全要求高,优先选 TypeScript
  • 若项目小、追求快速开发、希望保持纯 JS 生态,可选 JSDoc 作为轻量方案。
  • 实际开发中,两者也可结合使用(如用 JSDoc 在 TS 项目中补充复杂类型注释)。

1136. react 开发的应用里面, 如何给系统设置一个全局的崩溃的提示页面【热度: 725】【web 框架】【出题公司: 小米】

关键词:崩溃提示

在 React 应用中设置全局崩溃提示页面,核心是通过 错误边界边界(Error Boundary) 来捕获子组件树中的 JavaScript 错误,并显示备用 UI。以下是具体实现方案:

实现说明

  1. 错误边界组件(ErrorBoundary)

    • 这是一个 React 类组件,利用 React 的错误捕获生命周期 getDerivedStateFromErrorcomponentDidCatch 捕获子组件错误
    • getDerivedStateFromError:用于更新状态,触发错误 UI 渲染
    • componentDidCatch:用于记录错误信息(可上报到服务端)
    • 提供重试和返回首页功能,提升用户体验
import React from "react";
import { Button } from "your-ui-library"; // 可替换为你的 UI 库或自定义按钮

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null,
    };
  }

  // 捕获子组件树中的错误
  static getDerivedStateFromError(error) {
    // 更新 state,下一次渲染将显示错误 UI
    return { hasError: true };
  }

  // 记录错误信息(可用于上报)
  componentDidCatch(error, errorInfo) {
    this.setState({
      error: error,
      errorInfo: errorInfo,
    });

    // 可选:将错误信息上报到服务端
    console.error("全局错误捕获:", error, errorInfo);
    // 实际项目中可以调用接口上报:
    // fetch('/api/log-error', {
    //   method: 'POST',
    //   body: JSON.stringify({ error: error.message, stack: errorInfo.componentStack })
    // });
  }

  // 重置错误状态(重新加载应用)
  resetErrorHandler = () => {
    this.setState({
      hasError: false,
      error: null,
      errorInfo: null,
    });
    // 可选:如果需要完全重置应用状态,可以刷新页面
    // window.location.reload();
  };

  render() {
    if (this.state.hasError) {
      // 错误发生时显示的崩溃页面
      return (
        <div className="global-error-container">
          <div className="error-content">
            <h2>😱 应用发生错误</h2>
            <p>很抱歉,页面出现了意外错误,请尝试刷新或联系管理员。</p>

            {/* 可选:显示错误详情(生产环境可隐藏) */}
            {process.env.NODE_ENV === "development" && (
              <details style={{ whiteSpace: "pre-wrap" }}>
                <summary>错误详情</summary>
                {this.state.error?.message}
                <br />
                {this.state.errorInfo?.componentStack}
              </details>
            )}

            <div className="error-actions">
              <Button onClick={this.resetErrorHandler} variant="primary">
                重试
              </Button>
              <Button onClick={() => (window.location.href = "/")} variant="secondary" style={{ marginLeft: "10px" }}>
                返回首页
              </Button>
            </div>
          </div>
        </div>
      );
    }

    // 如果没有错误,渲染子组件
    return this.props.children;
  }
}

export default ErrorBoundary;
  1. 全局应用

    • 在应用入口(App.jsx)用 ErrorBoundary 包裹整个应用,确保所有子组件的错误都能被捕获
    • 错误边界会自动捕获其内部所有组件(包括嵌套组件)的渲染错误、生命周期错误等
import React from "react";
import ErrorBoundary from "./ErrorBoundary";
import Router from "./Router"; // 你的路由组件
import GlobalStyle from "./GlobalStyle"; // 全局样式

function App() {
  return (
    // 用错误边界包裹整个应用
    <ErrorBoundary>
      <GlobalStyle />
      <Router />
    </ErrorBoundary>
  );
}

export default App;

注意事项

  • 错误边界不能捕获以下错误

    • 事件处理函数中的错误(需手动 try/catch)
    • 异步代码中的错误(如 setTimeout、Promise)
    • 错误边界自身的错误
    • 服务端渲染的错误
  • 对于异步错误(如 API 请求失败),需要额外在代码中处理(如 try/catch 或状态管理)

  • 可以根据需要扩展错误边界,例如:

    • 增加错误分类显示不同提示
    • 实现自动重试逻辑
    • 集成错误监控工具(如 Sentry)

通过这种方式,你的 React 应用就能拥有一个全局的崩溃处理机制,在发生错误时给用户友好的提示,而不是白屏或控制台报错。

高级开发者相关问题【共计 6 道题】

1122. 将网页 dom 元素转为图片, 有哪些办法【热度: 41】【web 应用场景】

关键词:dom 转图片

在前端开发中,将 DOM 元素转换为图片有以下几种常见的方法:

1. 使用 HTML5 Canvas API (推荐)

这是最常用的方法,通过 Canvas 的drawImagegetContext方法绘制 DOM 内容,然后导出为图片。这种方法需要先将 DOM 内容转换为 Canvas 可绘制的格式,通常使用html2canvas库简化这个过程。

实现步骤

  1. 安装 html2canvas
npm install html2canvas
  1. 示例代码
import html2canvas from "html2canvas";

// 点击按钮触发截图
document.getElementById("captureBtn").addEventListener("click", async () => {
  const element = document.getElementById("targetElement");

  try {
    // 将DOM元素转换为Canvas
    const canvas = await html2canvas(element);

    // 将Canvas转换为图片URL
    const imgData = canvas.toDataURL("image/png");

    // 创建下载链接
    const link = document.createElement("a");
    link.download = "screenshot.png";
    link.href = imgData;
    link.click();
  } catch (error) {
    console.error("截图失败:", error);
  }
});

优点:兼容性好,支持大多数现代浏览器。
缺点:复杂元素(如阴影、SVG、iframe)可能渲染不完整。

2. 使用 Canvas 直接绘制

如果你只需要绘制简单的文本或图形,可以直接使用 Canvas API 手动绘制:

document.getElementById("captureBtn").addEventListener("click", () => {
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");

  // 设置Canvas尺寸
  canvas.width = 300;
  canvas.height = 200;

  // 手动绘制内容
  ctx.fillStyle = "white";
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  ctx.fillStyle = "black";
  ctx.font = "20px Arial";
  ctx.fillText("Hello, World!", 100, 100);

  // 导出为图片
  const imgData = canvas.toDataURL("image/png");
  const link = document.createElement("a");
  link.download = "manual-drawing.png";
  link.href = imgData;
  link.click();
});

优点:无需依赖外部库,可控性强。
缺点:仅适用于简单场景,复杂 DOM 难以手动绘制。

3. 使用 SVG

将 DOM 转换为 SVG 格式,然后导出为图片:

document.getElementById("captureBtn").addEventListener("click", () => {
  const targetElement = document.getElementById("targetElement");

  // 创建SVG元素
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  svg.setAttribute("width", targetElement.offsetWidth);
  svg.setAttribute("height", targetElement.offsetHeight);

  // 创建foreignObject嵌入HTML
  const foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
  foreignObject.setAttribute("width", "100%");
  foreignObject.setAttribute("height", "100%");

  // 克隆目标元素并添加到foreignObject
  const clonedElement = targetElement.cloneNode(true);
  foreignObject.appendChild(clonedElement);
  svg.appendChild(foreignObject);

  // 转换为DataURL
  const svgData = new XMLSerializer().serializeToString(svg);
  const imgData = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgData)));

  // 下载图片
  const link = document.createElement("a");
  link.download = "svg-export.png";
  link.href = imgData;
  link.click();
});

优点:矢量图形,可无限缩放不失真。
缺点:对复杂 CSS 和 JavaScript 交互支持有限。

4. 使用第三方 API

对于服务器端渲染或复杂场景,可以使用第三方 API 如:

  • Puppeteer (Node.js 库):通过无头 Chrome 浏览器渲染页面并截图。
  • html2pdf.js:将 HTML 转换为 PDF 或图片。
  • ImgKit:基于 WebKit 的服务器端渲染服务。

示例(Puppeteer)

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.goto("https://example.com");
  await page.screenshot({ path: "page.png" });

  await browser.close();
})();

优点:渲染效果最接近浏览器,支持复杂场景。
缺点:需要服务器支持,增加了部署复杂度。

选择建议

  • 简单静态内容:使用 Canvas 直接绘制。
  • 复杂 DOM 元素:使用html2canvas库。
  • 需要高质量渲染:使用 Puppeteer 等服务器端方案。
  • 需要矢量图形:使用 SVG 方法。

根据你的具体需求选择最合适的方法即可。

1125. 介绍一下 git diff【热度: 396】【web 应用场景】

关键词:git diff

作者备注

这个比较冷门, 平常很多时候都用不上, 基本上可以当做科普了解 如果当面试官问到:代码有问题, 怎么排查是哪一个 commit 引入的, 可以参考以下的回答

git diff 是 Git 中用于查看文件修改差异的核心命令,能够展示不同版本、不同状态之间的代码变更,帮助你跟踪和理解代码的变化过程。

核心作用

比较不同版本或不同状态下的文件内容差异,主要场景包括:

  • 工作区与暂存区的差异
  • 暂存区与最新提交的差异
  • 不同提交之间的差异
  • 不同分支之间的差异

常用用法

1. 查看工作区与暂存区的差异(最常用)
git diff
  • 显示工作区中已修改但未暂存(未执行 git add)的文件与暂存区的差异
  • 输出格式:- 表示删除的内容,+ 表示新增的内容,行号用于定位位置
2. 查看暂存区与最新提交的差异
git diff --cached  # 或 git diff --staged
  • 显示已暂存(执行过 git add)但未提交的内容与最近一次提交(HEAD)的差异
  • 常用于提交前确认暂存的内容是否正确
3. 查看工作区与最新提交的差异
git diff HEAD
  • 同时显示未暂存已暂存的所有修改与最新提交的差异
  • 相当于 git diff(工作区 vs 暂存区) + git diff --cached(暂存区 vs HEAD)的合并结果
4. 比较两个提交之间的差异
git diff <提交ID1> <提交ID2>
  • 示例:比较 a1b2c3de4f5g6h 两个提交的差异
    git diff a1b2c3d e4f5g6h
    
  • 若只关心某个文件的差异,可在最后指定文件名:
    git diff a1b2c3d e4f5g6h src/index.js
    
5. 比较两个分支之间的差异
git diff <分支1> <分支2>
  • 示例:比较 feature/login 分支和 main 分支的差异
    git diff feature/login main
    
6. 查看某次提交相对于上一次提交的差异
git diff <提交ID>^ <提交ID>  # ^ 表示该提交的父提交
# 简化写法:
git diff <提交ID>~1 <提交ID>
  • 更简洁的方式:直接查看某次提交的修改内容
    git show <提交ID>  # 相当于 git diff <提交ID>^ <提交ID>
    

输出格式说明

git diff 的输出通常包含以下部分:

diff --git a/src/index.js b/src/index.js  # 比较的文件
index 1234567..abcdefg 100644            # 文件的索引信息
--- a/src/index.js                        # 源文件(旧版本)
+++ b/src/index.js                        # 目标文件(新版本)
@@ -5,7 +5,7 @@ function greet() {                # 差异所在的行范围
   console.log("Hello, world!");
-  console.log("This is old code");
+  console.log("This is new code");       # +表示新增内容
   return true;
 }

实用选项

  • -w:忽略空白字符的差异(如空格、换行的调整)
    git diff -w  # 忽略空白差异
    
  • --stat:只显示文件修改的统计信息(不显示具体内容)
    git diff --stat  # 例如:src/index.js | 2 +-(表示该文件有2行修改,1行新增1行删除)
    
  • -p:显示完整的差异内容(默认就是这个行为,可省略)

总结

git diff 是代码审查和变更跟踪的重要工具,核心是通过比较不同“版本快照”之间的差异,帮助你:

  • 提交前确认修改内容
  • 回顾历史变更
  • 了解分支之间的差异
  • 排查代码问题

熟练使用 git diff 能大幅提升对代码变更的掌控力,是日常 Git 操作中不可或缺的命令。

1127. less 是否支持条件判定【热度: 112】【web 应用场景】【出题公司: 腾讯】

关键词:less 条件判定

是的,Less 完全支持条件判定,其核心通过 when 关键字 实现,同时可结合比较运算符、逻辑运算符构建复杂的条件逻辑,主要用于动态控制样式规则的生效与否(如根据变量值切换样式、适配不同场景)。

一、核心语法:when 条件判断

Less 的条件判定并非像 JavaScript 那样的 if-else 语句,而是以 “条件附加在选择器/混合(Mixin)后” 的形式存在,只有当条件满足时,对应的样式才会被编译。

1. 基础语法结构
// 格式:选择器 / 混合名 when (条件) { 样式 }
选择器 when (条件) {
  // 条件满足时生效的样式
}

// 示例:当 @width 大于 500px 时,设置容器宽度
.container when (@width > 500px) {
  width: @width;
  padding: 20px;
}

二、支持的条件类型

Less 允许在 when 中使用 比较运算符逻辑运算符类型检查函数,覆盖绝大多数场景需求。

1. 比较运算符

用于数值(如长度、数字、百分比)的比较,支持 6 种运算符:

  • >:大于
  • <:小于
  • >=:大于等于
  • <=:小于等于
  • ==:等于(值和单位需完全匹配,如 500px == 500 不成立)
  • !=:不等于

示例:根据屏幕宽度变量适配样式

@screen-width: 1200px;

// 大屏幕(>1024px)
.header when (@screen-width > 1024px) {
  font-size: 18px;
  padding: 0 40px;
}

// 中屏幕(768px ~ 1024px)
.header when (@screen-width >= 768px) and (@screen-width <= 1024px) {
  font-size: 16px;
  padding: 0 20px;
}

// 小屏幕(<768px)
.header when (@screen-width < 768px) {
  font-size: 14px;
  padding: 0 10px;
}

编译后(因 @screen-width=1200px),仅大屏幕样式生效:

.header {
  font-size: 18px;
  padding: 0 40px;
}
2. 逻辑运算符

用于组合多个条件,支持 3 种逻辑关系:

  • and:逻辑“与”(所有条件均满足才生效)
  • ,(逗号):逻辑“或”(任意一个条件满足即生效,注意不是 or
  • not:逻辑“非”(否定单个条件,需用括号包裹)

示例:逻辑组合的应用

@theme: "dark";
@font-size: 16; // 无单位(后续需拼接)

// 条件1:主题为 dark OR 字体大小 >= 16
.text-style when (@theme == "dark"), (@font-size >= 16) {
  color: #fff;
  background: #333;
}

// 条件2:主题不是 light AND 字体大小 < 20
.text-style when not (@theme == "light") and (@font-size < 20) {
  font-weight: 500;
}

编译后(@theme=dark@font-size=16 满足所有条件):

.text-style {
  color: #fff;
  background: #333;
  font-weight: 500;
}
3. 类型检查函数

用于判断变量的 类型是否为数值,常见函数如下:

函数 作用 示例
isnumber(@value) 判断是否为数字(无论是否有单位) isnumber(123)true
isstring(@value) 判断是否为字符串 isstring("red")true
iscolor(@value) 判断是否为颜色值(如 #fffred iscolor(#333)true
isurl(@value) 判断是否为 URL(如 url(xxx.png) isurl(url(img.jpg))true
isunit(@value, 单位) 判断是否为指定单位的数值 isunit(50px, px)true

示例:类型检查控制样式

@border-width: 2px;
@border-color: "#000"; // 字符串类型的颜色

// 条件:边框宽度是 px 单位,且边框颜色是字符串(需转换为颜色)
.border when (isunit(@border-width, px)) and (isstring(@border-color)) {
  border: @border-width solid @border-color; // Less 会自动将字符串颜色转为颜色值
}

编译后:

.border {
  border: 2px solid #000;
}

三、进阶用法:结合混合(Mixin)

条件判定在 混合(Mixin) 中使用最广泛,可实现“动态复用样式”,甚至模拟“if-else 分支”。

1. 带条件的混合

定义仅在特定条件下生效的混合,调用时自动判断是否执行:

// 定义混合:仅当 @radius 是数字时,设置圆角
.border-radius(@radius) when (isnumber(@radius)) {
  border-radius: @radius * 1px; // 统一转为 px 单位
}

// 调用混合
.card {
  .border-radius(8); // 满足条件(8 是数字),编译为 border-radius: 8px
}

.button {
  .border-radius("8"); // 不满足条件("8" 是字符串),无样式输出
}
2. 模拟“if-else”分支

通过多个 when 条件的“互斥性”,实现类似 if-else 的逻辑(即“满足 A 则执行 A,否则执行 B”):

@is-disabled: true;

// 条件1:如果禁用(if)
.button-style when (@is-disabled = true) {
  background: #ccc;
  cursor: not-allowed;
  color: #999;
}

// 条件2:如果未禁用(else)
.button-style when (@is-disabled = false) {
  background: #007bff;
  cursor: pointer;
  color: #fff;
}

// 调用
.disabled-btn {
  .button-style; // 因 @is-disabled=true,编译为禁用样式
}

四、注意事项

  1. 条件仅支持“编译时判定”:Less 是预编译语言,条件判断基于 编译时的变量值,无法动态响应运行时(如浏览器窗口大小变化),运行时适配需结合 CSS @media 查询。

  2. 键名与变量的区别:条件中使用变量时,需确保变量已定义;若误写为未定义的键名(如 when (screen-width > 1000px)),Less 会视为 undefined,条件判定为 false

  3. 与 CSS @media 的分工

    • Less 条件:用于 编译时的静态变量控制(如主题切换、固定参数适配);
    • CSS @media:用于 运行时的动态环境适配(如屏幕宽度、设备像素比)。 两者可结合使用(如 Less 变量动态生成 @media 条件):
    @breakpoint: 768px;
    @media (max-width: @breakpoint) {
      .container when (@columns = 2) {
        // Less 条件 + CSS media
        display: grid;
        grid-template-columns: repeat(2, 1fr);
      }
    }
    

总结

Less 的条件判定通过 when 关键字实现,支持比较、逻辑、类型检查,核心价值是 在编译时动态控制样式的生成,尤其适合与混合结合实现可复用的条件样式。日常开发中,需根据“是否需要编译时变量控制”选择 Less 条件(静态)或 CSS @media(动态),两者配合可覆盖绝大多数适配场景。

1128. less 有那些高级特性, 驱使你们项目工程去使用它【热度: 336】【web 应用场景】【出题公司: 腾讯】

关键词:less 特性

Less 作为一款流行的 CSS 预处理器,核心价值在于通过增强 CSS 的可编程性、复用性和可维护性,简化样式开发流程。除了基础的变量、嵌套语法,它还提供了诸多“高级特性”,这些特性能应对复杂场景(如组件样式封装、主题切换、动态样式计算等)。以下是 Less 核心高级特性的详细解析,结合使用场景和示例帮助理解:

一、条件判定(Guards)

Less 不支持传统编程语言的 if-else 语句,但通过 Guards(守卫) 实现了“基于条件匹配样式规则”的能力,分为「规则守卫」和「混合守卫」,核心是通过表达式判断是否应用样式。

1. 规则守卫(Guards on Rulesets)

给选择器添加条件,只有满足条件时,该选择器下的样式才会生效。
语法& when (条件表达式)& 代表当前选择器)
支持的运算符>, <, >=, <=, ==, !=,以及逻辑运算符 and, or, not

示例:根据屏幕宽度动态调整字体大小

// 定义变量存储断点
@sm: 768px;
@md: 1024px;

.container {
  font-size: 14px; // 默认样式

  // 屏幕 >= 768px 时生效
  & when (@media-width >= @sm) {
    font-size: 16px;
  }

  // 屏幕 >= 1024px 时生效(and 连接多条件)
  & when (@media-width >= @md) and (@theme = "dark") {
    font-size: 18px;
    color: #fff;
  }
}
2. 混合守卫(Guards on Mixins)

给混合(Mixin)添加条件,只有满足条件时,混合中的样式才会被注入。常用于“动态复用样式片段”。

示例:根据主题切换按钮样式

// 定义带条件的混合
.button-style(@theme) when (@theme = "primary") {
  background: #1890ff;
  border: 1px solid #1890ff;
}

.button-style(@theme) when (@theme = "danger") {
  background: #ff4d4f;
  border: 1px solid #ff4d4f;
}

// 使用混合(传入不同主题,触发不同条件)
.btn-primary {
  .button-style("primary");
  color: #fff;
}

.btn-danger {
  .button-style("danger");
  color: #fff;
}

二、高级变量特性

Less 的变量不仅支持“值存储”,还支持变量插值变量作用域变量运算,灵活应对动态样式场景。

1. 变量插值(Variable Interpolation)

将变量值插入到选择器名、属性名、URL、字符串中,实现“动态生成标识符”。
语法@{变量名}

示例:动态生成选择器和 URL

// 1. 动态选择器(如组件前缀)
@component-prefix: "my-btn";

.@{component-prefix} {
  // 最终编译为 .my-btn
  padding: 8px 16px;
}

.@{component-prefix}-disabled {
  // 最终编译为 .my-btn-disabled
  opacity: 0.6;
  cursor: not-allowed;
}

// 2. 动态 URL(如图片路径)
@img-path: "../assets/img";

.logo {
  background: url("@{img-path}/logo.png"); // 最终编译为 url("../assets/img/logo.png")
}

// 3. 动态属性名(如主题色属性)
@property: "color";
@theme-color: #1890ff;

.title {
  @{property}: @theme-color; // 最终编译为 color: #1890ff
}
2. 变量作用域(Variable Scope)

Less 变量遵循“就近原则”:局部作用域(如选择器、混合内部)的变量会覆盖全局作用域的变量,且支持“向上查找”(局部没有时,查找父级作用域)。

示例:作用域优先级

@color: red; // 全局变量

.container {
  @color: blue; // 局部变量(覆盖全局)
  .box {
    color: @color; // 优先使用局部变量,最终为 blue
  }
}

.text {
  color: @color; // 无局部变量,使用全局变量,最终为 red
}
3. 变量运算(Operations)

支持对数字、颜色、长度单位进行算术运算(+, -, *, /),自动处理单位兼容(如 pxrem 混合运算)。

示例:动态计算样式值

@base-padding: 10px;
@base-font-size: 14px;

.card {
  // 数字运算(padding = 基础值 * 1.5)
  padding: @base-padding * 1.5; // 最终 15px

  // 颜色运算(深色 = 基础色降低亮度)
  @base-color: #1890ff;
  background: @base-color - #333; // 最终 #0066cc

  // 单位混合运算(font-size = 基础值 + 2rem)
  font-size: @base-font-size + 2rem; // 最终 16px(Less 自动统一单位)
}

三、混合(Mixins)进阶

混合是 Less 的核心复用特性,除了基础的“样式片段复用”,还支持带参数混合、默认参数、剩余参数,甚至可以“返回值”(通过变量传递)。

1. 带参数混合(Parametric Mixins)

给混合定义参数,使用时传入不同值,实现“个性化复用”。

示例:通用圆角混合

// 定义带参数的混合(@radius 为参数)
.border-radius(@radius) {
  -webkit-border-radius: @radius;
  -moz-border-radius: @radius;
  border-radius: @radius;
}

// 使用混合(传入不同半径值)
.btn {
  .border-radius(4px); // 小圆角
}

.card {
  .border-radius(8px); // 大圆角
}
2. 默认参数(Default Values)

给混合参数设置默认值,使用时可省略该参数(自动使用默认值)。

示例:带默认值的阴影混合

// 定义混合(@color 默认 #000,@opacity 默认 0.2)
.box-shadow(@x: 0, @y: 0, @blur: 4px, @color: #000, @opacity: 0.2) {
  box-shadow: @x @y @blur rgba(@color, @opacity);
}

// 使用混合(省略部分参数,使用默认值)
.card {
  .box-shadow(0, 2px); // 省略 @blur(默认 4px)、@color(默认 #000)、@opacity(默认 0.2)
  // 最终编译为:box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
}
3. 剩余参数(Variadic Arguments)

当混合参数数量不确定时,用 ... 接收“剩余所有参数”,类似 JavaScript 的 rest 参数。

示例:灵活的过渡动画混合

// 定义混合(@props 接收所有过渡属性,@duration 默认 0.3s)
.transition(@props..., @duration: 0.3s) {
  transition: @props @duration ease;
}

// 使用混合(传入多个过渡属性)
.btn {
  .transition(color, background); // @props 接收 [color, background]
  // 最终编译为:transition: color background 0.3s ease
}

.card {
  .transition(transform, opacity, 0.5s); // 自定义 duration 为 0.5s
  // 最终编译为:transition: transform opacity 0.5s ease
}

四、导入(Import)进阶

Less 的 @import 不仅能导入其他 Less 文件,还支持条件导入引用导入导入变量/混合,灵活管理样式模块。

1. 条件导入(Conditional Import)

结合 Guards 实现“满足条件时才导入文件”,常用于“按需加载主题/适配样式”。

示例:根据主题导入不同样式文件

@theme: "dark"; // 可动态切换为 "light"

// 条件:主题为 dark 时,导入深色主题文件
@import (multiple) "theme-dark.less" when (@theme = "dark");

// 条件:主题为 light 时,导入浅色主题文件
@import (multiple) "theme-light.less" when (@theme = "light");
  • 注:(multiple) 表示“允许重复导入同一文件”(默认不允许)。
2. 引用导入(Reference Import)

@import (reference) 导入文件时,仅引用文件中的混合、变量,不编译文件本身的样式,避免冗余代码。

示例:引用工具类文件(仅用混合,不编译工具类样式)

// 导入工具类文件(reference 表示仅引用,不编译 utils.less 中的选择器)
@import (reference) "utils.less";

// 使用 utils.less 中的混合(如 .clearfix)
.container {
  .clearfix(); // 仅注入 .clearfix 的样式,utils.less 其他样式不编译
}
3. 导入变量/混合(Import for Variables/Mixins)

导入文件时,可直接使用目标文件中的变量和混合,实现“样式模块拆分”(如将变量、混合、组件样式分别放在不同文件)。

示例:模块化拆分样式

// 1. variables.less(存储全局变量)
@primary-color: #1890ff;
@font-size-base: 14px;

// 2. mixins.less(存储通用混合)
.clearfix() {
  &::after {
    content: "";
    display: table;
    clear: both;
  }
}

// 3. main.less(导入并使用)
@import "variables.less";
@import "mixins.less";

.btn {
  color: @primary-color; // 使用 variables.less 的变量
  font-size: @font-size-base;
}

.container {
  .clearfix(); // 使用 mixins.less 的混合
}

五、循环(Loops)

Less 没有专门的 for/while 循环语法,但通过混合自调用(混合内部调用自身)实现循环效果,常用于“生成重复样式”(如网格系统、层级样式)。

示例 1:生成 1-5 级标题样式

// 定义循环混合(@n 为当前层级,@max 为最大层级)
.generate-heading(@n, @max) when (@n <= @max) {
  // 动态生成选择器(h1, h2, ..., h@max)
  h@{n} {
    font-size: 16px + (@n - 1) * 4px; // 每级标题增大 4px
    margin-bottom: 8px + (@n - 1) * 2px;
  }
  // 自调用(层级 +1)
  .generate-heading(@n + 1, @max);
}

// 触发循环(生成 h1-h5 样式)
.generate-heading(1, 5);

编译结果

h1 {
  font-size: 16px;
  margin-bottom: 8px;
}
h2 {
  font-size: 20px;
  margin-bottom: 10px;
}
h3 {
  font-size: 24px;
  margin-bottom: 12px;
}
h4 {
  font-size: 28px;
  margin-bottom: 14px;
}
h5 {
  font-size: 32px;
  margin-bottom: 16px;
}

示例 2:生成网格系统(col-1 到 col-12)

.generate-col(@n) when (@n <= 12) {
  .col-@{n} {
    width: (@n / 12) * 100%; // 每列宽度 = (n/12)*100%
    float: left;
  }
  .generate-col(@n + 1);
}

.generate-col(1); // 生成 col-1 到 col-12

六、内置函数(Built-in Functions)

Less 提供了丰富的内置函数,覆盖颜色处理、字符串操作、数值计算等场景,无需手动编写复杂逻辑。以下是常用内置函数分类:

函数类别 常用函数 功能说明 示例
颜色处理 darken(@color, @percent) 降低颜色亮度(百分比) darken(#1890ff, 10%) → #096dd9
lighten(@color, @percent) 提高颜色亮度(百分比) lighten(#1890ff, 10%) → #3ba0ff
rgba(@color, @alpha) 设置颜色透明度 rgba(#1890ff, 0.5) → rgba(24,144,255,0.5)
字符串操作 replace(@str, @find, @replace) 替换字符串内容 replace("hello", "h", "H") → "Hello"
upper(@str) 字符串转大写 upper("hello") → "HELLO"
数值计算 ceil(@num) 向上取整 ceil(2.3) → 3
floor(@num) 向下取整 floor(2.7) → 2
percentage(@num) 小数转百分比 percentage(0.25) → 25%
其他 typeof(@value) 判断值的类型(number/string/color 等) typeof(#fff) → "color"

示例:用内置函数处理主题色

@primary: #1890ff;

.btn {
  background: @primary;
  //  hover 时加深 10% 亮度
  &:hover {
    background: darken(@primary, 10%);
  }
  //  active 时降低透明度到 0.8
  &:active {
    background: rgba(@primary, 0.8);
  }
}

总结

Less 的高级特性围绕“复用、动态、可维护”三大核心设计,适合复杂项目的样式开发:

  • 动态切换样式(如主题、响应式):用「条件判定」「变量插值」「条件导入」;
  • 复用样式片段(如组件、工具类):用「带参数混合」「剩余参数」「引用导入」;
  • 生成重复样式(如网格、层级):用「循环」;
  • 处理颜色/数值:用「内置函数」「变量运算」。

合理搭配这些特性,能大幅减少冗余 CSS 代码,提升样式开发效率和可维护性。

1132. ts 里面 infer 是什么关键词, 怎么用【热度: 975】【TypeScript】【出题公司: 美团】

关键词:ts infer

在 TypeScript 中,infer 是一个用于类型推断的关键字,通常与条件类型(Conditional types)配合使用,用于从泛型类型中提取或推断出某个具体类型。它的核心作用是“让 TypeScript 自动推导出我们需要的类型”,而无需需手动指定。

基本语法与作用

infer 只能在条件类型的 extends 子句中使用,语法格式如下:

type 类型名<T> = T extends 某个类型<infer 待推断类型> ? 待推断类型 : 其他类型;
  • infer X 表示“声明一个需要推断的类型变量 X
  • TypeScript 会自动分析 T 的结构,推导出 X 的具体类型

典型使用场景

1. 提取函数的返回值类型

最常见的场景之一:从函数类型中提取其返回值类型。

// 定义一个条件类型,提取函数的返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

// 使用示例
function getUser() {
  return { name: "张三", age: 20 };
}

// 推断 getUser 函数的返回值类型
type User = ReturnType<typeof getUser>;
// User 的类型为 { name: string; age: number }
  • T extends (...args: any[]) => infer R 表示:如果 T 是一个函数,就推断其返回值类型为 R
  • 最终 User 被推断为函数 getUser 的返回值类型
2. 提取函数的参数类型

类似地,可以提取函数的参数类型(单个参数或参数列表)。

// 提取单个参数类型
type ParamType<T> = T extends (param: infer P) => any ? P : never;

// 提取参数列表类型(返回元组)
type ParamsType<T> = T extends (...args: infer P) => any ? P : never;

// 使用示例
function sum(a: number, b: string): boolean {
  return a + Number(b) > 10;
}

type SumParam = ParamType<typeof sum>; // 错误!因为函数有多个参数,这里会返回 never
type SumParams = ParamsType<typeof sum>; // [number, string](参数列表组成的元组)
type SumReturn = ReturnType<typeof sum>; // boolean(返回值类型)
3. 提取数组的元素类型

从数组类型中推断出元素的类型。

// 提取数组元素类型
type ArrayItem<T> = T extends Array<infer Item> ? Item : T;

// 使用示例
type NumberItem = ArrayItem<number[]>; // number
type StringItem = ArrayItem<string[]>; // string
type UserItem = ArrayItem<{ name: string }[]>; // { name: string }
type Primitive = ArrayItem<boolean>; // boolean(非数组类型则返回自身)
4. 提取 Promise 的 resolve 类型

Promise 类型中推断出其最终解析(resolve)的类型。

// 提取 Promise 解析的类型
type PromiseResolve<T> = T extends Promise<infer R> ? R : T;

// 使用示例
type Resolve1 = PromiseResolve<Promise<string>>; // string
type Resolve2 = PromiseResolve<Promise<{ id: number }>>; // { id: number }
type Resolve3 = PromiseResolve<number>; // number(非 Promise 类型则返回自身)
5. 嵌套推断(复杂结构)

infer 支持多层嵌套推断,可用于复杂类型结构的提取。

// 从 { data: T } 结构中提取 T
type ExtractData<T> = T extends { data: infer D } ? D : T;

// 嵌套推断:从 Promise<{ data: T }> 中提取 T
type ExtractPromiseData<T> = T extends Promise<{ data: infer D }> ? D : T;

// 使用示例
type Data1 = ExtractData<{ data: { name: string } }>; // { name: string }
type Data2 = ExtractPromiseData<Promise<{ data: number[] }>>; // number[]

注意事项

  1. 只能在条件类型中使用infer 不能单独使用,必须放在 T extends ... 的子句中。

  2. 推断的不确定性:如果 TypeScript 无法明确推断类型(如多种可能的匹配),会返回 never 或联合类型。

    type Ambiguous<T> = T extends (a: infer A, b: infer A) => any ? A : never;
    type Test = Ambiguous<(x: number, y: string) => void>; // number | string(联合类型)
    
  3. 与内置工具类型的关系:TypeScript 内置的很多工具类型(如 ReturnTypeParameters)都是基于 infer 实现的,例如:

    // TypeScript 内置的 Parameters 实现
    type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
    

总结

infer 是 TypeScript 类型系统中用于自动推断类型的强大工具,核心价值在于:

  • 从复杂类型(如函数、数组、Promise 等)中“提取”我们需要的具体类型
  • 减少手动编写重复类型的工作量,提升类型定义的灵活性和可维护性

它最常见的应用场景包括提取函数参数/返回值、数组元素、Promise 解析值等,是编写高级类型工具的基础。

1135. TypeScript 中,ReturnType 的作用和用法【TypeScript】

关键词:ts RetrunType

在 TypeScript 中,ReturnType 是一个内置的工具类型,用于提取函数的返回值类型。它可以自动推断并返回函数的返回值类型,无需手动手动编写重复的类型定义,是处理函数类型时非常实用的工具。

作用

  • 从给定的函数类型中提取其返回值的类型,避免手动定义与函数返回值相同的类型,减少冗余代码。
  • 当函数返回值类型修改时,通过 ReturnType 提取的类型会自动同步更新,保证类型一致性。

用法

基础语法
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
  • 接收一个泛型参数 T,该参数必须是一个函数类型(通过 extends (...args: any) => any 约束)。
  • 使用 infer R 推断函数的返回值类型 R,最终返回 R
实际示例
  1. 提取普通函数的返回值类型

    // 定义一个返回对象的函数
    function getUser() {
      return { name: "张三", age: 20, isStudent: false };
    }
    
    // 提取函数返回值类型
    type User = ReturnType<typeof getUser>;
    // User 的类型为:{ name: string; age: number; isStudent: boolean }
    
  2. 提取箭头函数的返回值类型

    const calculate = (a: number, b: number) => a + b;
    
    // 提取返回值类型(number)
    type Result = ReturnType<typeof calculate>; // Result = number
    
  3. 提取泛型函数的返回值类型

    function createArray<T>(length: number, value: T): T[] {
      return Array(length).fill(value);
    }
    
    // 提取特定调用的返回值类型
    type StringArray = ReturnType<typeof createArray<string>>; // StringArray = string[]
    
  4. 在类型定义中复用

    // 定义一个回调函数类型
    type FetchData = (url: string) => Promise<{ code: number; data: string }>;
    
    // 提取该函数返回的 Promise 内部类型
    type FetchResult = ReturnType<FetchData>; // FetchResult = Promise<{ code: number; data: string }>
    
    // 进一步提取 Promise 的 resolve 类型(结合 Awaited)
    type Data = Awaited<FetchResult>; // Data = { code: number; data: string }
    

注意事项

  1. 仅支持函数类型ReturnType 的参数必须是函数类型,否则会报错。

    type Invalid = ReturnType<string>; // 报错:string 不是函数类型
    
  2. typeof 配合使用:当需要提取具体函数的返回值类型时,需用 typeof 获取该函数的类型(如 typeof getUser)。

  3. 内置工具类型ReturnType 是 TypeScript 内置的,无需手动定义,直接使用即可。

总结

ReturnType 的核心价值是自动同步函数返回值类型,尤其适合以下场景:

  • 函数返回值类型复杂,避免手动重复定义。
  • 函数返回值可能频繁修改,通过 ReturnType 确保依赖其类型的地方自动更新。
  • 在类型层面复用函数返回值结构,提升代码可维护性。
❌
❌