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

1,602 阅读48分钟

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.stringifyObjectMap 无法直接序列化,需手动转换为对象,Object 原生支持。

总结

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

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

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

关键词:less 与 scss

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

一、语法差异

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

特性LessSCSS(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)判断是否为颜色值(如 #fffrediscolor(#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 确保依赖其类型的地方自动更新。
  • 在类型层面复用函数返回值结构,提升代码可维护性。