注:这是一份面试题集,内容较多,包含了HTML、CSS、JavaScript、React、Vue、TypeScript等多个方面的题目和答案。
Part 0 HTML & CSS
简答题
- rem、vw、vh分别是什么?它们和 px 单位的区别是什么?
| 类别 | 单位 | 描述 | 典型应用场景 |
|---|---|---|---|
| 绝对单位 | px | 像素,最常用的绝对单位,1px 相当于屏幕上的一个物理像素点。 | 边框、阴影等需要精确控制的尺寸。 |
pt | 点,1pt = 1/72 英寸。 | 主要用于打印媒体。 | |
pc | 派卡,1pc = 12pt。 | 印刷行业,网页开发极少使用。 | |
in | 英寸,1in = 2.54cm = 96px。 | 打印样式。 | |
cm | 厘米。 | 打印样式。 | |
mm | 毫米。 | 打印样式。 | |
| 相对单位 | % | 相对于父元素对应属性的百分比值。 | 宽度、高度、外边距、内边距等布局属性。 |
em | 相对于当前元素的 font-size(用于非字体属性时),或相对于其父元素的 font-size(用于 font-size 本身时)。 | 组件内部与字体大小成比例的间距、尺寸。 | |
rem | 相对于根元素 (html) 的 font-size。 | 全局的字体大小、间距、布局,响应式设计的理想选择。 | |
ch | 相对于当前字体中 “0” 字符的宽度。 | 设置字符宽度限制,如代码容器。 | |
ex | 相对于当前字体的 “x” 字符高度。 | 较少使用。 | |
lh | 相对于当前元素的 line-height 值。 | 与行高相关的垂直布局。 | |
rlh | 相对于根元素的 line-height 值。 | 全局与行高相关的垂直布局。 | |
| 视口单位 | vw | 相对于视口宽度的 1%,1vw = 1% 视口宽度。 | 全屏宽度布局(如Banner图)。 |
vh | 相对于视口高度的 1%,1vh = 1% 视口高度。 | 全屏高度布局(如登录页背景)。 | |
vmin | 相对于视口宽度和高度中较小值的 1%。 | 确保在横屏和竖屏下都能显示完全的元素(如正方形)。 | |
vmax | 相对于视口宽度和高度中较大值的 1%。 | 占用最大视口空间。 | |
| 动态视口单位 | svh / svw | 小视口高度/宽度,浏览器UI(地址栏、工具栏)完全展开时的尺寸。 | 确保关键内容在最小可视区域内可见。 |
lvh / lvw | 大视口高度/宽度,浏览器UI完全收起时的尺寸。 | 创建沉浸式全屏体验。 | |
dvh / dvw | 动态视口高度/宽度,值会根据浏览器UI的当前状态(展开或收起)自动变化。 | 通用布局,解决传统 vh 单位在移动端的布局抖动问题,比如神秘网址Input的占位。 | |
vi / vb | 视口内联尺寸(水平书写模式下的宽度)和块尺寸(水平书写模式下的高度)的 1%。 | 与书写模式相关的布局。 | |
svi / svb | 小视口的内联尺寸和块尺寸。 | ||
lvi / lvb | 大视口的内联尺寸和块尺寸。 | ||
dvi / dvb | 动态视口的内联尺寸和块尺寸。 | ||
| 特殊单位 | fr | CSS Grid 布局中的弹性单位,按比例分配剩余空间。 | Grid 布局中定义轨道尺寸。 |
cap | 相对于当前字体的 “大写字母” 高度。 | ||
ic | 相对于当前字体的 “水” 字宽度(适用于 CJK 文字)。 |
- 简述 HTML 中块级元素、行内元素、行内块级元素的区别。
| 特性 | 块级元素 (Block) | 行内元素 (Inline) | 行内块元素 (Inline-Block) |
|---|---|---|---|
| 排列方式 | 独占一行,前后换行 | 不换行,并排排列 | 不换行,并排排列 |
| 尺寸设置 | ✅ 可设置宽高 | ❌ 不可设置宽高 | ✅ 可设置宽高 |
| 包含关系 | 可包含任何元素 | 通常只包含文本或其他行内元素 | 可包含任何元素 |
| 典型标签 | div, p, h1-h6, ul, li | span, a, strong, em | img, input, button |
- 作为 CSS 选择器的一种扩展,说说你在什么场景下会使用 CSS 伪类,什么场景下会使用 CSS 伪元素?
感觉伪类主要是做限制 比如:active :focus``:hover等
伪元素常见写样式 比如::before``::after 尤其是AI经常用这种 加个左边圆润的边框
- 你知道 css 选择器优先级吗?请介绍常用的选择器优先级,以及优先级的计算方式。
| 选择器类型 | 示例 | 优先级权重 |
|---|---|---|
!important | color: red !important; | 最高(破坏性,慎用) |
| 行内样式 | <div style="color: blue;"> | 1000 |
| ID 选择器 | #header | 0100 |
| 类/伪类/属性选择器 | .btn, :hover, [type="text"] | 0010 |
| 元素/伪元素选择器 | div, ::before | 0001 |
| 通配符/继承样式 | *, body继承的样式 | 0000 |
优先级由 4 级权重(A-B-C-D) 计算,比较时从左到右逐级对比:
- A (ID 选择器)
- 每有一个 ID 选择器,A + 1
- 示例:
#nav→A=1,#main #sidebar→A=2
- B (类/伪类/属性选择器)
- 每有一个类、伪类或属性选择器,B + 1
- 示例:
.active→B=1,input[type="text"]→B=2
- C (元素/伪元素选择器)
- 每有一个元素或伪元素选择器,C + 1
- 示例:
div p→C=2,li::after→C=1
- D (通配符/继承样式)
- 不影响优先级(权重为 0)
代码题
- 🍟开发时意识到在卡片式布局或列表视图中,每个项目的描述文本可能因内容长度差异导致整体布局不整齐,请问该如何编写 CSS 代码实现多行文本溢出时隐藏并用省略号代替溢出部分?
- 单行文本溢出
.ellipsis {
white-space: nowrap; /* 禁止换行 */
overflow: hidden; /* 隐藏溢出 */
text-overflow: ellipsis; /* 显示省略号 */
}
- 多行文本溢出
.multi-line-ellipsis {
display: -webkit-box; /* 旧版 Flex 盒模型(需前缀) */
-webkit-line-clamp: 3; /* 限制显示行数 */
-webkit-box-orient: vertical; /* 垂直排列 */
overflow: hidden; /* 隐藏溢出 */
text-overflow: ellipsis; /* 省略号 */
}
- 使用
::after伪元素实现 兼容性就会更好一些 - 使用
tailwind就直接line-clamp-xxx文档
- 这段代码实现的效果中,两个元素之间相距多少 px(按 border 之间的距离)?为什么?给 bottom 元素添加什么 CSS 属性可以使它们相距 70 px?
<body>
<div class="top">top</div>
<div class="bottom">bottom</div>
</body>
<style>
.top,
.bottom {
border: 1px solid black;
width: 100px;
height: 100px;
padding: 10px;
}
.top {
margin-bottom: 20px;
}
.bottom {
margin-top: 50px;
/* */
}
</style>
50px overflow:hidden 触发BFC
- BentoUI,指”以清晰的分区形式组织元素,多分区并列展示”的一种设计风格,其中的每个元素都有其目的与功能,类似便当里的各种食物,精致有序地排列。 请你使用 HTML 和 CSS 实现与给出示例大致相同的UI界面(只需要实现布局,不需要实现卡片中的内容)

年初写了 这次不写了
- 如何让元素垂直和水平居中,列举你知道的方法,并给出其中一种的具体实现。
- flex justify-center items-center
- 父元素relative 自身absolute top-[50%] left-[50%] transform: translate(-50%, -50%)
- grid place-items-center 这个最简单
- 有个水平居中的方法: mx-auto 竖直居中需要使用其他方法
- :hover 是一个在日常开发中很常见的伪类。当我们使用 :hover 时,有时会遇到元素抖动的问题(如下图),你觉得这个问题可以用什么样的方式解决?
附代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="src/style.css" />
</head>
<body>
<div></div>
</body>
</html>
div {
height: 200px;
width: 300px;
background-color: aqua;
}
div:hover {
transition: all 0.1s;
box-shadow: 6px 4px 6px slategray;
cursor: pointer;
transform: rotateZ(3deg);
border: 2px solid rgb(95, 138, 181);
}

box-sizing: border-box
一般来说 写项目之前都会弄个统一的index.css
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
- CSSBattle 是一个非常有趣的 CSS 练习网站!你可以用 HTML + CSS 构造出非常多酷炫的几何图案。请尝试用 HTML + CSS 画出冰岩作坊的 logo(不能使用 SVG/Canvas/clip-path path() 🤫)。

Part 1 JavaScript
简答题
- 分别列举你知道的 JavaScript 中的基本类型和对象类型
一、基本类型 (Primitive Types)
-
number - 数值类型 -
string - 字符串类型 -
boolean - 布尔类型 -
null - 空值 -
undefined - 未定义 -
symbol - 唯一值类型 (ES6) -
bigint - 大整数类型 (ES2020)
二、对象类型 (Object Types)
-
Object - 普通对象 -
Array - 数组对象 -
Function - 函数对象 -
Date - 日期对象 -
RegExp - 正则表达式对象 -
Map - 键值对集合 (ES6) -
Set - 值集合 (ES6) -
Promise - 异步处理对象 (ES6) -
Error - 错误对象 -
其他内置对象:
Math、JSON、ArrayBuffer等 -
简述 null,undefined,NaN 分别是什么,有什么样的特点
- null一般都是自己写的 表示空
- undefined不一定 可能是自己写的 可能是报的错 一般来说需要注意undefined的错误处理
- NaN基本就报错了 用TS就基本出现不了了 JS少用
- 简述你对 JS class 的理解 面向对象写法 好用 直接抽象一个类 用instance 区别于面向过程 面向对象一般来说只需要考虑初始状态和结束状态 抽象的好处是不需要考虑那么多 而且更加适合工程化项目 方便维护 位置统一
// 注意私有属性的使用只能在类内调用
# private
public
static
- 简述一下你对 JS 事件循环(Event Loop)机制的理解
1. 核心模型
- 单线程:JS 一次只做一件事。
- 非阻塞:通过 事件循环 处理异步,避免卡死。
2. 三个关键部分
- 调用栈 (Stack):执行同步代码。
- 任务队列 (Queue):存放待执行的异步回调。
- 宏任务:
setTimeout、setInterval、I/O - 微任务:
Promise.then、MutationObserver
- 宏任务:
- **事件循环 (Loop)
- 同步代码立即执行(填满调用栈)
- 栈空后,立即执行所有微任务
- 执行一个宏任务
- 重复2-3
代码题
- 请用 reduce 方法实现数组扁平化
/*
举个栗子:
若testArr=[1,[2,[3,4,5],6],[7,8],9,10]
则flatArr(testArr)结果为[1,2,3,4,5,6,7,8,9,10]
*/
const flatArr = (arr) => {
return arr.reduce(
/*请在此区域内补充代码*/
);
}
const flatArr = (arr) => {
return arr.reduce(
(pre, cur) =>
pre.concat(Array.isArray(cur) ? flatArr(cur) : cur),
[]
);
}
- 请给出以下代码的输出结果并说明原因。
const obj = {
x: 10,
normal: function () {
return () => this.x;
},
arrow: () => {
return () => typeof this.x;
}
};
const f1 = obj.normal();
const f2 = obj.arrow();
console.log(f1());
console.log(f2());
const tricky = (() => ({
value: 42
}))();
console.log(tricky.value);
const weird = (() => { value: 100 })();
console.log(weird);
10
undefined 箭头函数没自己的this
42
undefined return需要用()包一下
- 请完成以下代码,实现矩形元素的拖动
- 鼠标左键按下时,开始移动
- 按住并拖动鼠标,矩形元素跟随鼠标移动
- 鼠标左键抬起时,停止移动
- 不考虑触控(当然你也是可以考虑触控的)
<div id="rect"></div>
<script>
const rect = document.getElementById('rect');
/*请在此区域内补充代码*/
</script>
<style>
#rect {
width: 100px;
height: 100px;
background-color: red;
position: fixed;
cursor: move;
}
</style>
- 懒了 不写这个了
let 和 var ,分清楚了吗?
- 请写出 1 和 2 各自的输出结果,并且解释原因
// 1
for(var i=0; i<5; i++)
{
setTimeout(function(){
console.log(i)
});
}
// 2
for(let i=0; i<5; i++)
{
setTimeout(function(){
console.log(i)
});
}
- 写出输出结果并说明原因
var a = 1;
function fn() {
console.log(a);
var a = 5;
console.log(a);
a++;
var a;
fn3();
fn2();
console.log(a);
function fn2() {
console.log(a);
a = 20;
}
}
function fn3() {
console.log(a)
a = 200;
}
fn();
console.log(a);
5 5 5 5 5 0 1 2 3 4 var是全局作用域 打印的是全局所有++结束之后的i 也就是5 let是块级作用域 每次打印用的是新创建的i
What the F JS ?!
- 在每个表达式右侧用注释的方式给出表达式的真假 你可以运行这些表达式,而不是猜一个答案。希望你能了解为什么会出现相应的结果。
// example
true === true // true 什么?这个还需要怀疑?
// -------- 下面是正式的题 ---------------
1 == true // true
1 + 1 == true // false
'' == false // true
0 == '' // true
0 == '0' // true
0 === '0' // false
['a'] === 'a' // false
['a'] == 'a' // true
[] == [] // false
[] == true // false
[] == '' // false ✖️ true✔️
![] == '' // true
[] == ![] // false ✖️ true ✔️ 谢谢你js
(0 == undefined || null) == 0 // false
null == undefined ?? NaN // true
接下来的题有点意思,enjoy it !
- 写出输出的结果并解释原因。
const obj = {
foo: 1,
get bar() {
return this.foo + 1;
},
set bar(v) {
this.foo = v * 2;
}
};
const key1 = { x: 10 };
const key2 = { y: 20 };
obj[key1] = "A";
obj[key2] = "B";
obj.bar = obj[key1].length;
const result = [
Object.keys(obj),
obj[key1],
obj[key2],
obj.bar,
obj.foo
];
console.log(result);
// 涉及隐式转换 key1和key2会被转成字符串"[object Object]"
[
[ "foo", "bar", "[object Object]" ], "B", "B", 3, 2
]
- 写出输出的结果并解释原因,注意:
- 按照顺序给出每个 log 的输出结果。
- 这里还考察了浏览器的相关知识,请仔细。
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>How Good is Your Event Loop?</title>
<script>
setTimeout(() => {
console.log("setTimeout, ", document.body);
});
Promise.resolve().then(() => {
console.log("Promise, ", document.body);
});
console.log(document.body);
</script>
</head>
<body>
Hello!
</body>
</html>
null
Promise, null
setTimeout, <body>" Hello! "<!-- Code injected by live-server --><script>…</script></body>
先解析script 这时候没有解析body console.log(document.body);就是null setTimeout是宏任务
Promise是微任务 先微任务 此时还是null 之后解析body Promise log出来
- 水豚🦫之前沉迷 Cypress,用 Cypress 写了很多 E2E 测试。下面是一个典型的 Cypress 的调用:
cy.visit('url').get('selector').click()
这些链式调用中,既有 get 这样的同步任务,也有 visit、click 这样会发起网络请求或模拟用户操作的异步任务。无论是同步还是异步,它总能保证任务按照链式调用的顺序执行。 你需要在这里实现一个简易的 Cypress Class,其实例可以链式调用 task 方法。 task(name, delay) 方法接受一个任务名称和一个延迟时间(毫秒)。调用 task 的时候立刻打印 name,延迟 delay 后再执行下一个任务:
class Cypress {
/* 请在下方补充代码 */
}
/* 测试代码 */
const cy = new Cypress();
cy.task("任务A", 2000)
.task("任务B", 1000)
.task("任务C");
/* 预期输出 */
任务A
// 等待 2000 ms
任务B
// 等待 1000 ms
任务C
class Cypress {
/* 请在下方补充代码 */
constructor() {
this.tasks = Promise.resolve();
}
task(name, duration) {
this.tasks = this.tasks.then(() => {
console.log(name);
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, duration);
});
});
return this;
}
}
- 不要使用 Date,实现 day.js 的 diff 方法:
/*
* https://day.js.org/docs/zh-CN/display/difference
* 虽然链接放在这里,但是实际上实现的东西和这个并不一样。
* 在这里,我们会输入两个形如 '2006-01-02 15:04:05' 这样的表示时间的字符串
* 在两个 string 后,我们会输入一个可选的参数,表示输出的精度。
* 请你用第一个日期减去第二个,输出它们的差。
*/
/**
* 时间差计算
* @param {string} minuend - 被减数
* @param {string} subtrahend - 减数
* @param {string} unit
* - 单位,默认为 "hour"
* - 还可取为 ["year", "month", "day", "hour", "minute", "second"]
* @returns {number} - 数字值,请把计算结果向下取整。
*/
function diff(minuend, subtrahend, unit = "hour") {
const units = ["year", "month", "day", "hour", "minute", "second"];
if (!units.includes(unit)) {
throw new Error("Invalid unit");
}
const msInUnit = {
second: 1,
minute: 60,
hour: 3600,
day: 86400,
month: 2592000,
year: 31104000
};
const minuendParts = minuend.split(/[- :]/).map(Number);
const subtrahendParts = subtrahend.split(/[- :]/).map(Number);
const [y1, M1, d1, h1, m1, s1] = minuendParts;
const [y2, M2, d2, h2, m2, s2] = subtrahendParts;
const totalSeconds1 =
s1 +
m1 * 60 +
h1 * 3600 +
d1 * 86400 +
(y1 * 12 + (M1 - 1)) * 2592000;
const totalSeconds2 =
s2 +
m2 * 60 +
h2 * 3600 +
d2 * 86400 +
(y2 * 12 + (M2 - 1)) * 2592000;
const diffSeconds = totalSeconds1 - totalSeconds2;
return Math.floor(diffSeconds / msInUnit[unit]);
}
- 页面「国际化 (i18n)」指「适应各地的语言差异」,你可以简单地理解为翻译。其核心是一个 t函数。一般的使用方法如下:
// 翻译的资源
const resources = {
en: {
hello: 'Hello',
baraIs: 'bara is {{cnt}} {{what}}',
setting: {
pleaseUpdate: 'please update!'
}
},
zh: {
hello: '你好',
baraIs: '水豚你是{{cnt}}个{{what}}',
setting: {
pleaseUpdate: '请更新!'
}
}
}
// 创建实例
const i18n = new I18n({ resources })
// 设定语言为 cn
i18n.setLanguage('cn')
// 执行翻译
// (1) 找到 cn.hello
i18n.t('hello') // => '你好'
// (2) 找到 cn.homepage.pleaseUpdate
i18n.t('setting.pleaseUpdate') // => '请更新!'
// (3) 模版语法,可以填充。不被填充的部分返回原样模版
i18n.t('baraIs', { cnt: '一', what: '宝宝' }) // => '水豚你是一个宝宝'
i18n.t('baraIs', { cnt: '一', what: '饭团' }) // => '水豚你是一个饭团'
i18n.t('baraIs', { what: '小主教' }) // => '水豚你是{{cnt}}个小主教'
i18n.t('baraIs', { cnt: '一' }) // => '水豚你是一个{{what}}'
i18n.t('baraIs') // => '水豚你是{{cnt}}个{{what}}'
// (4) 没找到资源,返回原样的文本
i18n.t('setting.notExist') // => 'setting.notExist'
我们需要实现一个简化版的 class I18n。它有如下的实例方法:
- setLanguage(lang)用来设定语言。遇到不在 resources里的语言则使用默认的 ‘en’
- t(key, options)用来翻译
请你补完下面的这两个方法:
class I18n {
lang = 'en'
resources = {}
constructor({ resources }) {
this.resources = resources
}
setLanguage(lang) {
this.lang = lang
}
t(key, options = {}) {
const keys = key.split('.')
let translation = this.resources[this.lang]
for (const k of keys) {
translation = translation[k] || `{{${k}}}`
}
return translation.replace(/{{(\w+)}}/g, (_, k) => options[k] || `{{${k}}}`)
}
}
Part 2 前端框架
如果你没有学过框架,也可以聊聊单纯使用 “前端三件套” 进行开发时遇到的一些痛点(任何方面都可以谈)。
React:优雅的函数式编程
-
简述一下 useState 和 useRef 的区别,介绍一下二者的使用场景。
useState主要用来进行重新渲染 一般用于需要渲染的变量useRef主要用来定位变量或者不需要渲染的变量 类似于一个幕后的useState -
在 React 中如何解决 props 层级过深的问题? context或者store
-
在 React 中如何避免组件无意义的重复渲染?
useCallbackoruseMemo使用useReducer替代复杂useState -
请指出下面代码存在的问题,并给出修正后的代码。
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 1); // setNumber(prev => prev + 1);
setNumber(number + 1); // setNumber(prev => prev + 1);
setNumber(number + 1); // setNumber(prev => prev + 1);
// or setNumber(number + 3);
}}>+3</button>
</>
)
}
- 写出下面控制台的输出结果(在非严格模式下)
function App() {
const [bowl, setBowl] = useState("bowl");
useEffect(() => {
setBowl("bowll");
});
console.log(bowl);
return (
<>
<div>{bowl}</div>
</>
);
}
bowl
bowll
bowll
// 双重渲染检查副作用问题说是
Vue:流行的渐进式前端框架
这里出题以 Vue3 为准。如果你只会 Vue2,也可以回答一部分。
-
v-model 语法糖为我们的开发带来了便利,请简单介绍一下其实现原理。(不用写源码) 双向绑定 既绑定又监听变化
-
说说v-show和v-if的区别。 v-show是display:none v-if就是不渲染
-
下面是一个单文件组件(.vue文件)的片段。请说明这里v-for和v-if结合使用有什么问题。你能修改这段代码让它符合预期吗?(注:如果需要,你可以使用任何单文件组件的语法)
<li v-for="student in students" v-if="student.isHUSTer">
{{ todo.name }},你现在的首要任务是学在口口口
</li>
优先级问题 建议v-if改为filter一下students
- 由于前端组人手不够,不得不请游戏组的🐹老师来帮忙开发一个Vue项目,他发现ref()和reactive()都能用来声明响应式数据,你能告诉🐹老师他们之间的区别吗?如果可以,请简要说明 Vue3 的响应式是通过什么原生 JS 实现的。
ref()和 reactive()的区别
| 特性 | ref() | reactive() |
|---|---|---|
| 适用对象 | 基本类型(string、number等)和对象 | 只能用于 对象或数组 |
| 访问方式 | 需要通过 .value访问(在 <script>中) | 直接访问属性(无需 .value) |
| 模板中使用 | 自动解包,无需 .value | 直接使用属性 |
| 重新赋值 | 可以整体替换(ref.value = newObj) | 不能直接替换整个对象(需用 Object.assign) |
| 响应式原理 | 内部调用 reactive()包装对象 | 直接使用 Proxy 代理对象 |
-
说一说 Transition 组件在什么条件下会向插槽内触发【进入或离开】事件。如果你使用过 Transition, 你应该知道这个组件中 css 和 js 一般有什么作用,请分别指出。 用的不多
-
更喜欢选项式API还是组合式API,为什么? 肯定用新不用旧啊
React vs Vue
-
小碗🥣最近接手了一个新项目,在React和Vue中犹豫不决,你能告诉小碗他们之间的异同吗? 看自己吧 React更优雅一些 Vue更容易上手
-
举例介绍一下自己封装自定义Hooks的经历(React或Vue皆可)。“talk is cheap,show me your code”😉
-
如果让你来封装一个鼠标悬浮时会出现的【Tip 组件】,即下图中的小气泡。请给出你的设计方案。

- 冰岩前端组打算设计一个前端框架,你希望他们引入哪些新的特性或对现有前端框架进行哪些改进?
Part 3 TypeScript
- JS 里面使用 const 声明的对象,内部的属性仍然可以被修改,你知道如何使用 TS 实现一个真正的只读对象吗?更进一步,请思考如果是一个嵌套层级很深的对象,如何确保嵌套对象的属性也是只读的呢?
/* 请补充 MyReadonly 的实现 */
type MyReadonly<T> = //...
type Profile = {
name: string;
age: number;
associations: string | string[];
intershipExp: Array<{
company: string;
city: string;
duration: string;
projects: string[];
}>
}
const bara: MyReadonly<Profile> = {
name: 'bara',
age: 21,
associations: 'Bingyan',
intershipExp: [
{
company: 'ByteDance',
city: 'Shanghai',
duration: '4020/8-4020-12',
projects: ['byte', 'dance']
}
]
}
bara.name = 'capy'; // ❌ Cannot assign to 'name' because it is a read-only property.
const firstIE = bara.intershipExp[0];
firstIE.company = 'Tencent'; // ❌ Cannot assign to 'company' because it is a read-only property.
firstIE.projects[0] = 'Penguin'; // ❌ Index signature in type 'readonly string[]' only permits reading.
- 柯里化(Currying)是一个很有用的技巧,在本题中,柯里化后的函数每次调用只能接收一个参数,当参数数量达到原函数参数数量时立即执行。下面是一个简单的柯里化例子:
const add = (a: number, b: number) => a + b;
const curriedAdd = Currying(add);
const ten = curriedAdd(4)(6); // ✅
const err1 = curriedAdd(4, 6) // ❌ 传参过多
const err2 = curriedAdd('4')(6) // ❌ 参数类型错误
用 JS 实现一个柯里化工具函数或许难不倒你,但是在 TS 中,这个工具函数的类型应该如何去定义呢?
/* 请给出 Currying 函数的正确类型声明 */
declare function Currying(fn: any): any
/* 自测用例 */
const curried1 = Currying((a: string, b: number, c: boolean) => true)
// ^?const curried1: (arg: string) => (arg: number) => (arg: boolean) => true
const curried2 = Currying((a: string, b: number, c: boolean, d: boolean, e: boolean, f: string, g: boolean) => true)
// ^?(arg: string) => (arg: number) => (arg: boolean) => (arg: boolean) => (arg: boolean) => (arg: string) => (arg: boolean) => true
const curried3 = Currying(() => true)
// ^?const curried3: () => true
type CurriedFn<P extends any[], R> =
P extends [infer First, ...infer Rest]
? (arg: First) => CurriedFn<Rest, R>
: R;
function curry<Fn extends (...args: any[]) => any>(fn: Fn):
Fn extends (...args: infer P) => infer R
? CurriedFn<P, R>
: never {
return function curried(...args: any[]): any {
if (args.length >= fn.length) {
return fn(...args);
} else {
return (...nextArgs: any[]) => curried(...args, ...nextArgs);
}
} as any;
}