2025.07.06 - 2025.08.31 更新前端面试问题总结(15 道题)
获取更多面试相关问题可以访问
github 地址: github.com/pro-collect...
gitee 地址: gitee.com/yanleweb/in...
目录:
中级开发者相关问题【共计 9 道题】
- 深层清理对象中的空值属性【热度: 234】【代码实现/算法】
- 介绍一下 git stash【热度: 386】【web 应用场景】
- JS 里面, 对于对象的读写, 是使用 object 好,还是 Map,性能差异如何?【热度: 610】【JavaScript】【出题公司: 阿里巴巴】
- less 与 scss 有何区别【热度: 61】【web 应用场景】【出题公司: 腾讯】
- less 与 css 有何区别【热度: 214】【web 应用场景】【出题公司: 腾讯】
- 用 css 实现一个 loading 动画, 该如何做(转圈)【热度: 180】【CSS】
- ts 有哪些常用的关键词【热度: 178】【TypeScript】【出题公司: 美团】
- 对比一下 ts 和 jsdoc【热度: 126】【TypeScript】
- react 开发的应用里面, 如何给系统设置一个全局的崩溃的提示页面【热度: 725】【web 框架】【出题公司: 小米】
高级开发者相关问题【共计 6 道题】
- 将网页 dom 元素转为图片, 有哪些办法【热度: 41】【web 应用场景】
- 介绍一下 git diff【热度: 396】【web 应用场景】
- less 是否支持条件判定【热度: 112】【web 应用场景】【出题公司: 腾讯】
- less 有那些高级特性, 驱使你们项目工程去使用它【热度: 336】【web 应用场景】【出题公司: 腾讯】
- ts 里面 infer 是什么关键词, 怎么用【热度: 975】【TypeScript】【出题公司: 美团】
- TypeScript 中,ReturnType 的作用和用法【TypeScript】
中级开发者相关问题【共计 9 道题】
1123. 深层清理对象中的空值属性【热度: 234】【代码实现/算法】
请实现一个函数 deepOmitNil
,要求如下:
-
功能:递归处理一个可能包含嵌套结构的对象(或数组),移除所有层级中值为
null
或undefined
的属性 -
要求:
- 支持对象和数组的嵌套结构
- 对于对象:移除值为
null
/undefined
的属性,保留其他属性并继续递归处理属性值 - 对于数组:递归处理每个元素,同时过滤掉值为
null
/undefined
的元素 - 不改变原始数据结构,返回处理后的新数据
-
示例:
javascript// 输入 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 工具库辅助开发。
解法
javascript
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
效果类似,但变更被临时保存了) - 之后可以随时从"栈"中恢复这些变更,继续之前的工作
常用命令
-
暂存当前变更
bashgit stash # 或添加描述(推荐,方便区分多个stash) git stash save "描述信息:例如「首页导航栏修改」"
执行后,工作区会恢复到干净状态,变更被存入 stash 栈。
-
查看所有暂存的变更
bashgit stash list
输出类似:
kotlinstash@{0}: On feature/login: 修复登录按钮样式 stash@{1}: On develop: 临时添加调试日志
stash@{n}
是每个暂存的唯一标识,n
越小表示越新。 -
恢复暂存的变更
-
恢复最新的 stash(
stash@{0}
),且保留 stash 记录:bashgit stash apply
-
恢复指定的 stash(例如
stash@{1}
):bashgit stash apply stash@{1}
-
恢复最新的 stash 并删除该 stash 记录(推荐用完即删的场景):
bashgit stash pop
-
-
删除暂存的变更
-
删除最新的 stash:
bashgit stash drop
-
删除指定的 stash:
bashgit stash drop stash@{1}
-
删除所有 stash:
bashgit stash clear
-
-
查看 stash 中的具体修改
查看最新 stash 与当前工作区的差异:
bashgit stash show
查看详细差异(显示具体修改的内容):
bashgit 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 中,Object
和 Map
都可以用于存储键值对,但它们的设计目标和性能特性存在差异,选择哪一种取决于具体使用场景。
核心差异与性能对比
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
快,尤其是键值对数量较多时。且
Map
的size
属性可直接获取长度(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 几乎零成本。
二、变量与作用域
两者都支持变量,但作用域规则和特性有差异。
-
变量符号:
- Less 用
@
(如@color: red;
); - SCSS 用
$
(如$color: red;
),避免与 CSS 原生@
规则(如@media
)冲突。
- Less 用
-
作用域行为:
-
Less :变量遵循「延迟加载」(Lazy Loading),即变量在使用前无需声明,作用域内后定义的变量会覆盖先定义的。
less.box { color: @color; // 允许使用后定义的变量 @color: red; }
-
SCSS :变量必须先声明后使用,作用域更严格(类似 JavaScript)。
scss.box { color: $color; // 报错:$color 未定义 $color: red; }
-
-
全局变量:
-
SCSS 需用
!global
关键字显式声明全局变量(局部作用域中):scss.box { $color: red !global; // 声明为全局变量 } .text { color: $color; } // 可访问
-
Less 中变量默认全局有效(局部变量会覆盖全局,但不会污染全局)。
-
三、混合(Mixins)与函数
两者都支持代码复用,但实现方式和功能有差异。
1. 混合(Mixins)
用于复用样式片段。
-
Less :
混合无需特殊关键字,直接定义类或 id 选择器,使用时加括号(可选):
less// 定义混合 .border-radius(@radius: 4px) { border-radius: @radius; } // 使用混合(可省略括号) .btn { .border-radius; // 或 .border-radius(8px) }
-
SCSS :
混合必须用
@mixin
定义,用@include
调用,语法更明确:scss// 定义混合 @mixin border-radius($radius: 4px) { border-radius: $radius; } // 使用混合 .btn { @include border-radius(8px); }
2. 函数(Functions)
用于计算值并返回结果(不直接生成样式)。
-
Less :函数功能较弱,主要依赖内置函数(如
darken()
、lighten()
),自定义函数需通过混合模拟(不支持返回值)。 -
SCSS :支持用
@function
自定义函数,可返回值,功能更强大:scss// 自定义函数:计算百分比宽度 @function col-width($n) { @return ($n / 12) * 100%; } .col-6 { width: col-width(6); // 50% }
四、条件与循环
处理动态逻辑的能力不同。
1. 条件判断
-
Less :通过
when
关键字实现条件(Guards),语法较特殊:less.theme(@type) when (@type = "dark") { background: #333; } .box { .theme("dark"); }
-
SCSS :支持
@if
/@else
语句,更接近传统编程语言:scss@mixin theme($type) { @if $type == "dark" { background: #333; } @else { background: #fff; } } .box { @include theme("dark"); }
2. 循环
-
Less:通过混合自调用实现循环,语法较繁琐:
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
多种循环语法,更直观:scss// @for 循环 @for $i from 1 through 3 { .item-#{$i} { width: $i * 10px; } }
五、模块化与导入
处理样式文件拆分的方式。
-
Less:
-
用
@import "file.less";
导入文件,支持条件导入(结合when
):less@import "theme.less" when (@theme = "dark");
-
无内置模块化机制,需通过工具(如 Webpack)实现按需加载。
-
-
SCSS:
-
用
@import "file.scss";
导入文件,支持嵌套导入(在选择器内导入,作用域受限):scss.box { @import "partial.scss"; // 仅在 .box 内生效 }
-
支持
@use
和@forward
(Sass 3.8+),实现更严格的模块化(类似 ES6 模块),避免变量冲突:scss// 导入并命名空间 @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
)。
- 由 Ruby 开发(后部分用 C 重写),但现在主流通过
-
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 :
编写重复代码多(如相同颜色、尺寸需多次书写),修改时需全局搜索替换,维护成本高。
示例(重复代码问题):
css.btn { background: #1890ff; } .card { border-color: #1890ff; } .title { color: #1890ff; } /* 若要修改主题色,需逐个修改 */
-
Less :
通过变量、混合等特性减少重复代码,修改时只需调整一处,开发和维护效率大幅提升。
示例(解决重复问题):
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 :
层级嵌套需通过后代选择器实现,代码冗长且层级不直观。
示例:
css.header { padding: 20px; } .header .logo { width: 100px; } .header .nav { margin-left: 20px; } .header .nav li { display: inline-block; }
-
Less :
支持嵌套语法,结构与 HTML 一致,层级清晰,减少选择器冗余。
示例:
less.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
动画的核心实现思路如下:
-
创建圆形元素:
- 使用
width
和height
设置相同的尺寸 - 通过
border-radius: 50%
将方形元素变成圆形
- 使用
-
设计边框样式:
- 设置一个较粗的边框(
border
) - 让大部分边框保持半透明(
rgba(255, 255, 255, 0.3)
) - 只让顶部边框使用实色(
border-top-color
),形成旋转时的流动效果
- 设置一个较粗的边框(
-
添加旋转动画:
- 定义
spin
动画,通过transform: rotate(360deg)
实现 360 度旋转 - 使用
animation
属性应用动画,设置1s
为一个周期,ease-in-out
缓动效果,infinite
无限循环
- 定义
直接上代码:
html
<!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
| 表示永远不会发生的类型(如抛出错误) | | 复合类型 | array
(T[]
或 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 可直接运行 |
类型定义方式 | 使用专门的类型语法(如 : number 、interface 、type 等) |
使用注释标签(如 @param 、@returns 、@type 等)标注类型 |
类型检查时机 | 编译时强制类型检查,错误会在开发阶段暴露 | 依赖 IDE/编辑器(如 VS Code)的 TypeScript 服务进行类型检查,非强制 |
功能丰富度 | 类型系统强大(泛型、交叉/联合类型、条件类型、枚举等) | 支持基础类型标注,复杂类型(如泛型约束、条件类型)表达能力有限 |
生态与工具 | 生态成熟,支持所有主流框架(React、Vue 等),有大量类型声明文件(.d.ts ) |
依赖 TypeScript 语言服务,可复用 .d.ts 文件,但工具链集成较弱 |
学习成本 | 较高,需学习专门的类型语法和概念 | 较低,基于注释,语法简单,熟悉 JS 即可快速上手 |
项目侵入性 | 高,需将文件改为 .ts 后缀,可能需要调整构建流程 |
低,不改变 JS 代码结构,仅添加注释,原有 JS 项目可平滑接入 |
运行时影响 | 无(编译后为纯 JS),但类型信息会被完全擦除 | 无(注释不影响运行),类型信息仅存在于代码中 |
适用场景 | 大型项目、团队协作、对类型安全性要求高的场景 | 小型项目、快速原型、希望保持纯 JS 但需要基础类型提示的场景 |
核心优劣总结
TypeScript 的优势:
- 强类型检查:编译阶段强制校验,能提前发现更多类型错误,减少运行时问题。
- 丰富的类型功能:支持泛型、条件类型、枚举等高级特性,能精确描述复杂数据结构。
- 工具链完善:与主流 IDE、构建工具(Webpack、Vite)深度集成,开发体验好。
- 团队协作友好:类型定义作为"活文档",清晰传达接口设计,降低沟通成本。
TypeScript 的劣势:
- 学习成本高 :需要掌握额外的类型语法和概念(如
infer
、keyof
等)。 - 项目侵入性强:需要修改文件后缀、配置编译流程,对纯 JS 项目迁移有一定成本。
- 编译开销:增加编译步骤,可能略微影响开发效率(尤其大型项目)。
JSDoc 的优势:
- 零成本接入:无需修改代码结构,仅通过注释标注类型,原有 JS 项目可直接使用。
- 无编译步骤:保留纯 JS 特性,浏览器/Node 可直接运行,适合快速迭代。
- 学习成本低:语法简单,基于注释标签,熟悉 JS 的开发者可快速上手。
JSDoc 的劣势:
- 类型能力有限:复杂类型(如泛型约束、条件类型)难以表达,类型检查较弱。
- 非强制检查:依赖 IDE 提示,无法在构建阶段强制报错,可能遗漏类型问题。
- 注释冗余:大量类型注释会增加代码体积,影响可读性(尤其复杂类型)。
总结建议
- 若项目规模大、团队协作频繁、对类型安全要求高,优先选 TypeScript。
- 若项目小、追求快速开发、希望保持纯 JS 生态,可选 JSDoc 作为轻量方案。
- 实际开发中,两者也可结合使用(如用 JSDoc 在 TS 项目中补充复杂类型注释)。
1136. react 开发的应用里面, 如何给系统设置一个全局的崩溃的提示页面【热度: 725】【web 框架】【出题公司: 小米】
关键词:崩溃提示
在 React 应用中设置全局崩溃提示页面,核心是通过 错误边界边界(Error Boundary) 来捕获子组件树中的 JavaScript 错误,并显示备用 UI。以下是具体实现方案:
实现说明
-
错误边界组件(ErrorBoundary)
- 这是一个 React 类组件,利用 React 的错误捕获生命周期
getDerivedStateFromError
和componentDidCatch
捕获子组件错误 getDerivedStateFromError
:用于更新状态,触发错误 UI 渲染componentDidCatch
:用于记录错误信息(可上报到服务端)- 提供重试和返回首页功能,提升用户体验
- 这是一个 React 类组件,利用 React 的错误捕获生命周期
jsx
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;
-
全局应用
- 在应用入口(App.jsx)用 ErrorBoundary 包裹整个应用,确保所有子组件的错误都能被捕获
- 错误边界会自动捕获其内部所有组件(包括嵌套组件)的渲染错误、生命周期错误等
jsx
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 的drawImage
或getContext
方法绘制 DOM 内容,然后导出为图片。这种方法需要先将 DOM 内容转换为 Canvas 可绘制的格式,通常使用html2canvas
库简化这个过程。
实现步骤:
- 安装 html2canvas:
bash
npm install html2canvas
- 示例代码:
javascript
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 手动绘制:
javascript
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 格式,然后导出为图片:
javascript
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):
javascript
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. 查看工作区与暂存区的差异(最常用)
bash
git diff
- 显示工作区中已修改但未暂存 (未执行
git add
)的文件与暂存区的差异 - 输出格式:
-
表示删除的内容,+
表示新增的内容,行号用于定位位置
2. 查看暂存区与最新提交的差异
bash
git diff --cached # 或 git diff --staged
- 显示已暂存(执行过
git add
)但未提交 的内容与最近一次提交(HEAD
)的差异 - 常用于提交前确认暂存的内容是否正确
3. 查看工作区与最新提交的差异
bash
git diff HEAD
- 同时显示未暂存 和已暂存的所有修改与最新提交的差异
- 相当于
git diff
(工作区 vs 暂存区) +git diff --cached
(暂存区 vs HEAD)的合并结果
4. 比较两个提交之间的差异
bash
git diff <提交ID1> <提交ID2>
-
示例:比较
a1b2c3d
和e4f5g6h
两个提交的差异bashgit diff a1b2c3d e4f5g6h
-
若只关心某个文件的差异,可在最后指定文件名:
bashgit diff a1b2c3d e4f5g6h src/index.js
5. 比较两个分支之间的差异
bash
git diff <分支1> <分支2>
-
示例:比较
feature/login
分支和main
分支的差异bashgit diff feature/login main
6. 查看某次提交相对于上一次提交的差异
bash
git diff <提交ID>^ <提交ID> # ^ 表示该提交的父提交
# 简化写法:
git diff <提交ID>~1 <提交ID>
-
更简洁的方式:直接查看某次提交的修改内容
bashgit show <提交ID> # 相当于 git diff <提交ID>^ <提交ID>
输出格式说明
git diff
的输出通常包含以下部分:
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
:忽略空白字符的差异(如空格、换行的调整)bashgit diff -w # 忽略空白差异
-
--stat
:只显示文件修改的统计信息(不显示具体内容)bashgit 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. 基础语法结构
less
// 格式:选择器 / 混合名 when (条件) { 样式 }
选择器 when (条件) {
// 条件满足时生效的样式
}
// 示例:当 @width 大于 500px 时,设置容器宽度
.container when (@width > 500px) {
width: @width;
padding: 20px;
}
二、支持的条件类型
Less 允许在 when
中使用 比较运算符 、逻辑运算符 和 类型检查函数,覆盖绝大多数场景需求。
1. 比较运算符
用于数值(如长度、数字、百分比)的比较,支持 6 种运算符:
>
:大于<
:小于>=
:大于等于<=
:小于等于==
:等于(值和单位需完全匹配,如500px == 500
不成立)!=
:不等于
示例:根据屏幕宽度变量适配样式
less
@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
),仅大屏幕样式生效:
css
.header {
font-size: 18px;
padding: 0 40px;
}
2. 逻辑运算符
用于组合多个条件,支持 3 种逻辑关系:
and
:逻辑"与"(所有条件均满足才生效),
(逗号):逻辑"或"(任意一个条件满足即生效,注意不是or
)not
:逻辑"非"(否定单个条件,需用括号包裹)
示例:逻辑组合的应用
less
@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
满足所有条件):
css
.text-style {
color: #fff;
background: #333;
font-weight: 500;
}
3. 类型检查函数
用于判断变量的 类型 或 是否为数值,常见函数如下:
函数 | 作用 | 示例 |
---|---|---|
isnumber(@value) |
判断是否为数字(无论是否有单位) | isnumber(123) → true |
isstring(@value) |
判断是否为字符串 | isstring("red") → true |
iscolor(@value) |
判断是否为颜色值(如 #fff 、red ) |
iscolor(#333) → true |
isurl(@value) |
判断是否为 URL(如 url(xxx.png) ) |
isurl(url(img.jpg)) → true |
isunit(@value, 单位) |
判断是否为指定单位的数值 | isunit(50px, px) → true |
示例:类型检查控制样式
less
@border-width: 2px;
@border-color: "#000"; // 字符串类型的颜色
// 条件:边框宽度是 px 单位,且边框颜色是字符串(需转换为颜色)
.border when (isunit(@border-width, px)) and (isstring(@border-color)) {
border: @border-width solid @border-color; // Less 会自动将字符串颜色转为颜色值
}
编译后:
css
.border {
border: 2px solid #000;
}
三、进阶用法:结合混合(Mixin)
条件判定在 混合(Mixin) 中使用最广泛,可实现"动态复用样式",甚至模拟"if-else 分支"。
1. 带条件的混合
定义仅在特定条件下生效的混合,调用时自动判断是否执行:
less
// 定义混合:仅当 @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"):
less
@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,编译为禁用样式
}
四、注意事项
-
条件仅支持"编译时判定" :Less 是预编译语言,条件判断基于 编译时的变量值 ,无法动态响应运行时(如浏览器窗口大小变化),运行时适配需结合 CSS
@media
查询。 -
键名与变量的区别 :条件中使用变量时,需确保变量已定义;若误写为未定义的键名(如
when (screen-width > 1000px)
),Less 会视为undefined
,条件判定为false
。 -
与 CSS
@media
的分工:- Less 条件:用于 编译时的静态变量控制(如主题切换、固定参数适配);
- CSS
@media
:用于 运行时的动态环境适配 (如屏幕宽度、设备像素比)。 两者可结合使用(如 Less 变量动态生成@media
条件):
less@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
。
示例:根据屏幕宽度动态调整字体大小
less
// 定义变量存储断点
@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)添加条件,只有满足条件时,混合中的样式才会被注入。常用于"动态复用样式片段"。
示例:根据主题切换按钮样式
less
// 定义带条件的混合
.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
less
// 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 变量遵循"就近原则":局部作用域(如选择器、混合内部)的变量会覆盖全局作用域的变量,且支持"向上查找"(局部没有时,查找父级作用域)。
示例:作用域优先级
less
@color: red; // 全局变量
.container {
@color: blue; // 局部变量(覆盖全局)
.box {
color: @color; // 优先使用局部变量,最终为 blue
}
}
.text {
color: @color; // 无局部变量,使用全局变量,最终为 red
}
3. 变量运算(Operations)
支持对数字、颜色、长度单位 进行算术运算(+
, -
, *
, /
),自动处理单位兼容(如 px
和 rem
混合运算)。
示例:动态计算样式值
less
@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)
给混合定义参数,使用时传入不同值,实现"个性化复用"。
示例:通用圆角混合
less
// 定义带参数的混合(@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)
给混合参数设置默认值,使用时可省略该参数(自动使用默认值)。
示例:带默认值的阴影混合
less
// 定义混合(@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
参数。
示例:灵活的过渡动画混合
less
// 定义混合(@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 实现"满足条件时才导入文件",常用于"按需加载主题/适配样式"。
示例:根据主题导入不同样式文件
less
@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)
导入文件时,仅引用文件中的混合、变量,不编译文件本身的样式,避免冗余代码。
示例:引用工具类文件(仅用混合,不编译工具类样式)
less
// 导入工具类文件(reference 表示仅引用,不编译 utils.less 中的选择器)
@import (reference) "utils.less";
// 使用 utils.less 中的混合(如 .clearfix)
.container {
.clearfix(); // 仅注入 .clearfix 的样式,utils.less 其他样式不编译
}
3. 导入变量/混合(Import for Variables/Mixins)
导入文件时,可直接使用目标文件中的变量和混合,实现"样式模块拆分"(如将变量、混合、组件样式分别放在不同文件)。
示例:模块化拆分样式
less
// 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 级标题样式
less
// 定义循环混合(@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);
编译结果:
css
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)
less
.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" |
示例:用内置函数处理主题色
less
@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
子句中使用,语法格式如下:
typescript
type 类型名<T> = T extends 某个类型<infer 待推断类型> ? 待推断类型 : 其他类型;
infer X
表示"声明一个需要推断的类型变量X
"- TypeScript 会自动分析
T
的结构,推导出X
的具体类型
典型使用场景
1. 提取函数的返回值类型
最常见的场景之一:从函数类型中提取其返回值类型。
typescript
// 定义一个条件类型,提取函数的返回值类型
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. 提取函数的参数类型
类似地,可以提取函数的参数类型(单个参数或参数列表)。
typescript
// 提取单个参数类型
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. 提取数组的元素类型
从数组类型中推断出元素的类型。
typescript
// 提取数组元素类型
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)的类型。
typescript
// 提取 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
支持多层嵌套推断,可用于复杂类型结构的提取。
typescript
// 从 { 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[]
注意事项
-
只能在条件类型中使用 :
infer
不能单独使用,必须放在T extends ...
的子句中。 -
推断的不确定性 :如果 TypeScript 无法明确推断类型(如多种可能的匹配),会返回
never
或联合类型。typescripttype Ambiguous<T> = T extends (a: infer A, b: infer A) => any ? A : never; type Test = Ambiguous<(x: number, y: string) => void>; // number | string(联合类型)
-
与内置工具类型的关系 :TypeScript 内置的很多工具类型(如
ReturnType
、Parameters
)都是基于infer
实现的,例如:typescript// 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
提取的类型会自动同步更新,保证类型一致性。
用法
基础语法
typescript
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
- 接收一个泛型参数
T
,该参数必须是一个函数类型(通过extends (...args: any) => any
约束)。 - 使用
infer R
推断函数的返回值类型R
,最终返回R
。
实际示例
-
提取普通函数的返回值类型
typescript// 定义一个返回对象的函数 function getUser() { return { name: "张三", age: 20, isStudent: false }; } // 提取函数返回值类型 type User = ReturnType<typeof getUser>; // User 的类型为:{ name: string; age: number; isStudent: boolean }
-
提取箭头函数的返回值类型
typescriptconst calculate = (a: number, b: number) => a + b; // 提取返回值类型(number) type Result = ReturnType<typeof calculate>; // Result = number
-
提取泛型函数的返回值类型
typescriptfunction createArray<T>(length: number, value: T): T[] { return Array(length).fill(value); } // 提取特定调用的返回值类型 type StringArray = ReturnType<typeof createArray<string>>; // StringArray = string[]
-
在类型定义中复用
typescript// 定义一个回调函数类型 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 }
注意事项
-
仅支持函数类型 :
ReturnType
的参数必须是函数类型,否则会报错。typescripttype Invalid = ReturnType<string>; // 报错:string 不是函数类型
-
与
typeof
配合使用 :当需要提取具体函数的返回值类型时,需用typeof
获取该函数的类型(如typeof getUser
)。 -
内置工具类型 :
ReturnType
是 TypeScript 内置的,无需手动定义,直接使用即可。
总结
ReturnType
的核心价值是自动同步函数返回值类型,尤其适合以下场景:
- 函数返回值类型复杂,避免手动重复定义。
- 函数返回值可能频繁修改,通过
ReturnType
确保依赖其类型的地方自动更新。 - 在类型层面复用函数返回值结构,提升代码可维护性。