Skip to content
Go back

冰岩前端笔试题目

Edit page

注:这是一份面试题集,内容较多,包含了HTML、CSS、JavaScript、React、Vue、TypeScript等多个方面的题目和答案。

Part 0 HTML & CSS

简答题

  1. 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动态视口的内联尺寸和块尺寸。
特殊单位frCSS Grid 布局中的弹性单位,按比例分配剩余空间。Grid 布局中定义轨道尺寸。
cap相对于当前字体的 “大写字母” 高度。
ic相对于当前字体的 “水” 字宽度(适用于 CJK 文字)。
  1. 简述 HTML 中块级元素、行内元素、行内块级元素的区别。
特性块级元素 (Block)行内元素 (Inline)行内块元素 (Inline-Block)
​排列方式​独占一行,前后换行不换行,并排排列不换行,并排排列
​尺寸设置​✅ 可设置宽高❌ 不可设置宽高✅ 可设置宽高
​包含关系​可包含任何元素通常只包含文本或其他行内元素可包含任何元素
​典型标签​div, p, h1-h6, ul, lispan, a, strong, emimg, input, button
  1. 作为 CSS 选择器的一种扩展,说说你在什么场景下会使用 CSS 伪类,什么场景下会使用 CSS 伪元素?

mdn-伪类 mdn-伪元素

感觉伪类主要是做限制 比如:active :focus``:hover等 伪元素常见写样式 比如::before``::after 尤其是AI经常用这种 加个左边圆润的边框

  1. 你知道 css 选择器优先级吗?请介绍常用的选择器优先级,以及优先级的计算方式。
选择器类型​​示例​​优先级权重​
!importantcolor: red !important;最高(破坏性,慎用)
​行内样式​<div style="color: blue;">1000
​ID 选择器​#header0100
​类/伪类/属性选择器​.btn, :hover, [type="text"]0010
​元素/伪元素选择器​div, ::before0001
​通配符/继承样式​*, body继承的样式0000

优先级由 ​​4 级权重(A-B-C-D)​​ 计算,比较时从左到右逐级对比:

  1. ​A (ID 选择器)​
    • 每有一个 ID 选择器,A + 1
    • 示例:#navA=1, #main #sidebarA=2
  2. ​B (类/伪类/属性选择器)​
    • 每有一个类、伪类或属性选择器,B + 1
    • 示例:.activeB=1, input[type="text"]B=2
  3. ​C (元素/伪元素选择器)​
    • 每有一个元素或伪元素选择器,C + 1
    • 示例:div pC=2, li::afterC=1
  4. ​D (通配符/继承样式)​
    • 不影响优先级(权重为 0)

代码题

  1. 🍟开发时意识到在卡片式布局或列表视图中,每个项目的描述文本可能因内容长度差异导致整体布局不整齐,请问该如何编写 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;         /* 省略号 */
}
  1. 这段代码实现的效果中,两个元素之间相距多少 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

  1. BentoUI,指”以清晰的分区形式组织元素,多分区并列展示”的一种设计风格,其中的每个元素都有其目的与功能,类似便当里的各种食物,精致有序地排列。 请你使用 HTML 和 CSS 实现与给出示例大致相同的UI界面(只需要实现布局,不需要实现卡片中的内容)

图片

年初写了 这次不写了

  1. 如何让元素垂直和水平居中,列举你知道的方法,并给出其中一种的具体实现。
  1. :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;
}
  1. CSSBattle 是一个非常有趣的 CSS 练习网站!你可以用 HTML + CSS 构造出非常多酷炫的几何图案。请尝试用 HTML + CSS 画出冰岩作坊的 logo(不能使用 SVG/Canvas/clip-path path() 🤫)。

图片

Part 1 JavaScript

简答题

  1. 分别列举你知道的 JavaScript 中的基本类型和对象类型

一、基本类型 (Primitive Types)​

  1. number​ - 数值类型
  2. string​ - 字符串类型
  3. boolean​ - 布尔类型
  4. null​ - 空值
  5. undefined​ - 未定义
  6. symbol​ - 唯一值类型 (ES6)
  7. bigint​ - 大整数类型 (ES2020)

​二、对象类型 (Object Types)​

  1. Object​ - 普通对象

  2. Array​ - 数组对象

  3. Function​ - 函数对象

  4. Date​ - 日期对象

  5. RegExp​ - 正则表达式对象

  6. Map​ - 键值对集合 (ES6)

  7. Set​ - 值集合 (ES6)

  8. Promise​ - 异步处理对象 (ES6)

  9. Error​ - 错误对象

  10. ​其他内置对象​​:MathJSONArrayBuffer

  11. 简述 null,undefined,NaN 分别是什么,有什么样的特点

  1. 简述你对 JS class 的理解 面向对象写法 好用 直接抽象一个类 用instance 区别于面向过程 面向对象一般来说只需要考虑初始状态和结束状态 抽象的好处是不需要考虑那么多 而且更加适合工程化项目 方便维护 位置统一
// 注意私有属性的使用只能在类内调用
# private
public 
static
  1. 简述一下你对 JS 事件循环(Event Loop)机制的理解

1. 核心模型​

​2. 三个关键部分​

  1. ​调用栈 (Stack)​​:执行同步代码。
  2. ​任务队列 (Queue)​​:存放待执行的异步回调。
    • ​宏任务​​:setTimeoutsetInterval、I/O
    • ​微任务​​:Promise.thenMutationObserver
  3. ​**​事件循环 (Loop)
    1. 同步代码立即执行(填满调用栈)
    2. 栈空后,​​立即执行所有微任务​
    3. 执行一个宏任务
    4. 重复2-3

代码题

  1. 请用 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),
        []
    );
}
  1. 请给出以下代码的输出结果并说明原因。
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需要用()包一下
  1. 请完成以下代码,实现矩形元素的拖动
  2. 鼠标左键按下时,开始移动
  3. 按住并拖动鼠标,矩形元素跟随鼠标移动
  4. 鼠标左键抬起时,停止移动
  5. 不考虑触控(当然你也是可以考虑触控的)
<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. 请写出 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)
   });
}
  1. 写出输出结果并说明原因
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 ?!

  1. 在每个表达式右侧用注释的方式给出表达式的真假 你可以运行这些表达式,而不是猜一个答案。希望你能了解为什么会出现相应的结果。
// 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 !

  1. 写出输出的结果并解释原因。
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
]
  1. 写出输出的结果并解释原因,注意:
<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出来
  1. 水豚🦫之前沉迷 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;
  }
}
  1. 不要使用 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]);
}
  1. 页面「国际化 (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。它有如下的实例方法:

请你补完下面的这两个方法:

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:优雅的函数式编程

  1. 简述一下 useState 和 useRef 的区别,介绍一下二者的使用场景。 useState 主要用来进行重新渲染 一般用于需要渲染的变量 useRef 主要用来定位变量或者不需要渲染的变量 类似于一个幕后的useState

  2. 在 React 中如何解决 props 层级过深的问题? context或者store

  3. 在 React 中如何避免组件无意义的重复渲染? useCallback or useMemo 使用 useReducer替代复杂 useState

  4. 请指出下面代码存在的问题,并给出修正后的代码。

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>
    </>
  )
}
  1. 写出下面控制台的输出结果(在非严格模式下)
function App() {
  const [bowl, setBowl] = useState("bowl");
  useEffect(() => {
    setBowl("bowll");
  });
  console.log(bowl);

  return (
    <>
      <div>{bowl}</div>
    </>
  );
}
bowl
bowll
bowll
// 双重渲染检查副作用问题说是

Vue:流行的渐进式前端框架

这里出题以 Vue3 为准。如果你只会 Vue2,也可以回答一部分。

  1. v-model 语法糖为我们的开发带来了便利,请简单介绍一下其实现原理。(不用写源码) 双向绑定 既绑定又监听变化

  2. 说说v-show和v-if的区别。 v-show是display:none v-if就是不渲染

  3. 下面是一个单文件组件(.vue文件)的片段。请说明这里v-for和v-if结合使用有什么问题。你能修改这段代码让它符合预期吗?(注:如果需要,你可以使用任何单文件组件的语法)

<li v-for="student in students" v-if="student.isHUSTer">
  {{ todo.name }},你现在的首要任务是学在口口口
</li>
优先级问题 建议v-if改为filter一下students
  1. 由于前端组人手不够,不得不请游戏组的🐹老师来帮忙开发一个Vue项目,他发现ref()和reactive()都能用来声明响应式数据,你能告诉🐹老师他们之间的区别吗?如果可以,请简要说明 Vue3 的响应式是通过什么原生 JS 实现的。

ref()reactive()的区别​

特性ref()reactive()
​适用对象​基本类型(stringnumber等)和对象只能用于 ​​对象或数组​
​访问方式​需要通过 .value访问(在 <script>中)直接访问属性(无需 .value
​模板中使用​自动解包,无需 .value直接使用属性
​重新赋值​可以整体替换(ref.value = newObj不能直接替换整个对象(需用 Object.assign
​响应式原理​内部调用 reactive()包装对象直接使用 Proxy 代理对象
  1. 说一说 Transition 组件在什么条件下会向插槽内触发【进入或离开】事件。如果你使用过 Transition, 你应该知道这个组件中 css 和 js 一般有什么作用,请分别指出。 用的不多

  2. 更喜欢选项式API还是组合式API,为什么? 肯定用新不用旧啊

React vs Vue

  1. 小碗🥣最近接手了一个新项目,在React和Vue中犹豫不决,你能告诉小碗他们之间的异同吗? 看自己吧 React更优雅一些 Vue更容易上手

  2. 举例介绍一下自己封装自定义Hooks的经历(React或Vue皆可)。“talk is cheap,show me your code”😉

  3. 如果让你来封装一个鼠标悬浮时会出现的【Tip 组件】,即下图中的小气泡。请给出你的设计方案。

图片

  1. 冰岩前端组打算设计一个前端框架,你希望他们引入哪些新的特性或对现有前端框架进行哪些改进?

Part 3 TypeScript

  1. 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.
  1. 柯里化(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;
}

Edit page
Share this post on:

Next Post
神秘校园网