前端面试必须掌握的手写题:进阶篇

news2026/4/2 3:06:06

本文是前端面试必须掌握的手写题系列的最后一篇,这个系列几乎将我整理和遇到的题目都包含到了,这里还是想强调一下,对于特别常见的题目最好能“背”下来,不要眼高手低,在面试的时候不需要再进行推导分析直接一把梭,后续会整理分享一些其他的信息,希望对你能有所帮助

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:web前端面试题库

🔥请求并发控制

多次遇到的题目,而且有很多变种,主要就是同步改异步

function getUrlByFetch() {
  let idx = maxLoad;

  function getContention(index) {
    fetch(pics[index]).then(() => {
      idx++;
      if(idx < pics.length){
        getContention(idx);
      }
    });
  }
  function start() {
    for (let i = 0; i < maxLoad; i++) {
      getContention(i);
    }
  }
  start();
}

🔥带并发限制的promise异步调度器

上一题的其中一个变化

function taskPool() {
  this.tasks = [];
  this.pool = [];
  this.max = 2;
}

taskPool.prototype.addTask = function(task) {
  this.tasks.push(task);
  this.run();
}

taskPool.prototype.run = function() {
  if(this.tasks.length === 0) {
    return;
  }
  let min = Math.min(this.tasks.length, this.max - this.pool.length);
  for(let i = 0; i<min;i++) {
    const currTask = this.tasks.shift();
    this.pool.push(currTask);
    currTask().finally(() => {
      this.pool.splice(this.pool.indexOf(currTask), 1);
      this.run();
    })
  }
}

🔥🔥🔥实现lazy链式调用: person.eat().sleep(2).eat()

解法其实就是将所有的任务异步化,然后存到一个任务队列里

function Person() {
  this.queue = [];
  this.lock = false;
}

Person.prototype.eat = function () {
  this.queue.push(() => new Promise(resolve => { console.log('eat'); resolve(); }));
  // this.run();
  return this;
}

Person.prototype.sleep = function(time, flag) {
  this.queue.push(() => new Promise(resolve => {
    setTimeout(() => {
      console.log('sleep', flag);
      resolve();
    }, time * 1000)
  }));
  // this.run();
  return this;
}

Person.prototype.run = async function() {
  if(this.queue.length > 0 && !this.lock) {
    this.lock = true;
    const task = this.queue.shift();
    await task();
    this.lock = false;
    this.run();
  }
}

const person = new Person();
person.eat().sleep(1, '1').eat().sleep(3, '2').eat().run();

方法二

class Lazy {
    // 函数调用记录,私有属性
    #cbs = [];
    constructor(num) {
        // 当前操作后的结果
        this.res = num;
    }

    // output时,执行,私有属性
    #add(num) {
        this.res += num;
        console.log(this.res);
    }

    // output时,执行,私有属性
    #multipy(num) {
        this.res *= num;
        console.log(this.res)
    }

    add(num) {

        // 往记录器里面添加一个add函数的操作记录
        // 为了实现lazy的效果,所以没有直接记录操作后的结果,而是记录了一个函数
        this.#cbs.push({
            type: 'function',
            params: num,
            fn: this.#add
        })
        return this;
    }
    multipy(num) {

        // 和add函数同理
        this.#cbs.push({
            type: 'function',
            params: num,
            fn: this.#multipy
        })
        return this;
    }
    top (fn) {

        // 记录需要执行的回调
        this.#cbs.push({
            type: 'callback',
            fn: fn
        })
        return this;
    }
    delay (time) {

        // 增加delay的记录
        this.#cbs.push({
            type: 'delay',

            // 因为需要在output调用是再做到延迟time的效果,利用了Promise来实现
            fn: () => {
                return new Promise(resolve => {
                    console.log(`等待${time}ms`);
                    setTimeout(() => {
                        resolve();
                    }, time);
                })
            }
        })
        return this;
    }

    // 关键性函数,区分#cbs中每项的类型,然后执行不同的操作
    // 因为需要用到延迟的效果,使用了async/await,所以output的返回值会是promise对象,无法链式调用
    // 如果需实现output的链式调用,把for里面函数的调用全部放到promise.then的方式
    async output() {
        let cbs = this.#cbs;
        for(let i = 0, l = cbs.length; i < l; i++) {
            const cb = cbs[i];
            let type = cb.type;
            if (type === 'function') {
                cb.fn.call(this, cb.params);
            }
            else if(type === 'callback') {
                cb.fn.call(this, this.res);
            }
            else if(type === 'delay') {
                await cb.fn();
            }
        }

        // 执行完成后清空 #cbs,下次再调用output的,只需再输出本轮的结果
        this.#cbs = [];
    }
}
function lazy(num) {
    return new Lazy(num);
}

const lazyFun = lazy(2).add(2).top(console.log).delay(1000).multipy(3)
console.log('start');
console.log('等待1000ms');
setTimeout(() => {
    lazyFun.output();
}, 1000);

🔥函数柯里化

毫无疑问,需要记忆

function curry(fn, args) {
  let length = fn.length;
  args = args || [];

  return function() {
    let subArgs = args.slice(0);
    subArgs = subArgs.concat(arguments);
    if(subArgs.length >= length) {
      return fn.apply(this, subArgs);
    } else {
      return curry.call(this, fn, subArgs);
    }
  }
}

// 更好理解的方式
function curry(func, arity = func.length) {
  function generateCurried(preArgs) {
    return function curried(nextArgs) {
      const args = [...preArgs, ...nextArgs];
      if(args.length >= arity) {
        return func(...args);
      } else {
        return generateCurried(args);
      }
    }
  }
  return generateCurried([]);
}

es6实现方式

// es6实现
function curry(fn, ...args) {
  return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}

lazy-load实现

img标签默认支持懒加载只需要添加属性 loading="lazy",然后如果不用这个属性,想通过事件监听的方式来实现的话,也可以使用IntersectionObserver来实现,性能上会比监听scroll好很多

const imgs = document.getElementsByTagName('img');
const viewHeight = window.innerHeight || document.documentElement.clientHeight;

let num = 0;

function lazyLoad() {
  for (let i = 0; i < imgs.length; i++) {
    let distance = viewHeight - imgs[i].getBoundingClientRect().top;
    if(distance >= 0) {
      imgs[i].src = imgs[i].getAttribute('data-src');
      num = i+1;
    }
  }
}
window.addEventListener('scroll', lazyLoad, false);

实现简单的虚拟dom

给出如下虚拟dom的数据结构,如何实现简单的虚拟dom,渲染到目标dom树

// 样例数据
let demoNode = ({
    tagName: 'ul',
    props: {'class': 'list'},
    children: [
        ({tagName: 'li', children: ['douyin']}),
        ({tagName: 'li', children: ['toutiao']})
    ]
});

构建一个render函数,将demoNode对象渲染为以下dom

<ul class="list">
  <li>douyin</li>
  <li>toutiao</li>
</ul>

通过遍历,逐个节点地创建真实DOM节点

function Element({tagName, props, children}){
   // 判断必须使用构造函数
    if(!(this instanceof Element)){
        return new Element({tagName, props, children})
    }
    this.tagName = tagName;
    this.props = props || {};
    this.children = children || [];
}

Element.prototype.render = function(){
    var el = document.createElement(this.tagName),
        props = this.props,
        propName,
        propValue;
    for(propName in props){
        propValue = props[propName];
        el.setAttribute(propName, propValue);
    }
    this.children.forEach(function(child){
        var childEl = null;
        if(child instanceof Element){
            childEl = child.render();
        }else{
            childEl = document.createTextNode(child);
        }
        el.appendChild(childEl);
    });
    return el;
};

// 执行
var elem = Element({
    tagName: 'ul',
    props: {'class': 'list'},
    children: [
        Element({tagName: 'li', children: ['item1']}),
        Element({tagName: 'li', children: ['item2']})
    ]
});
document.querySelector('body').appendChild(elem.render());

实现SWR 机制

SWR 这个名字来自于 stale-while-revalidate:一种由 HTTP RFC 5861 推广的 HTTP 缓存失效策略

const cache = new Map();

async function swr(cacheKey, fetcher, cacheTime) {
  let data = cache.get(cacheKey) || { value: null, time: 0, promise: null };
  cache.set(cacheKey, data);
  
  // 是否过期
  const isStaled = Date.now() - data.time > cacheTime;
  if (isStaled && !data.promise) {
    data.promise = fetcher()
      .then((val) => {
        data.value = val;
        data.time = Date.now();
      })
      .catch((err) => {
        console.log(err);
      })
      .finally(() => {
        data.promise = null;
      });
  }
  
  if (data.promise && !data.value) await data.promise;
  return data.value;
}

const data = await fetcher();
const data = await swr('cache-key', fetcher, 3000);

实现一个只执行一次的函数

// 闭包
function once(fn) {
  let called = false;
  return function _once() {
    if (called) {
      return _once.value;
    }
    called = true;
    _once.value = fn.apply(this, arguments);
  }
}

//ES6 的元编程 Reflect API 将其定义为函数的行为
Reflect.defineProperty(Function.prototype, 'once', {
  value () {
    return once(this);
  },
  configurable: true,
})

LRU 算法实现

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

class LRUCahe {
  constructor(capacity) {
    this.cache = new Map();
    this.capacity = capacity;
  }

  get(key) {
    if (this.cache.has(key)) {
      const temp = this.cache.get(key);
      this.cache.delete(key);
      this.cache.set(key, temp);
      return temp;
    }
    return undefined;
  }

  set(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
      // map.keys() 会返回 Iterator 对象
      this.cache.delete(this.cache.keys().next().value);
    }
    this.cache.set(key, value);
  }
}

🔥发布-订阅

发布者不直接触及到订阅者、而是由统一的第三方来完成实际的通信的操作,叫做发布-订阅模式

class EventEmitter {
  constructor() {
    // handlers是一个map,用于存储事件与回调之间的对应关系
    this.handlers = {}
  }

  // on方法用于安装事件监听器,它接受目标事件名和回调函数作为参数
  on(eventName, cb) {
    // 先检查一下目标事件名有没有对应的监听函数队列
    if (!this.handlers[eventName]) {
      // 如果没有,那么首先初始化一个监听函数队列
      this.handlers[eventName] = []
    }

    // 把回调函数推入目标事件的监听函数队列里去
    this.handlers[eventName].push(cb)
  }

  // emit方法用于触发目标事件,它接受事件名和监听函数入参作为参数
  emit(eventName, ...args) {
    // 检查目标事件是否有监听函数队列
    if (this.handlers[eventName]) {
      // 这里需要对 this.handlers[eventName] 做一次浅拷贝,主要目的是为了避免通过 once 安装的监听器在移除的过程中出现顺序问题
      const handlers = this.handlers[eventName].slice()
      // 如果有,则逐个调用队列里的回调函数
      handlers.forEach((callback) => {
        callback(...args)
      })
    }
  }

  // 移除某个事件回调队列里的指定回调函数
  off(eventName, cb) {
    const callbacks = this.handlers[eventName]
    const index = callbacks.indexOf(cb)
    if (index !== -1) {
      callbacks.splice(index, 1)
    }
  }

  // 为事件注册单次监听器
  once(eventName, cb) {
    // 对回调函数进行包装,使其执行完毕自动被移除
    const wrapper = (...args) => {
      cb(...args)
      this.off(eventName, wrapper)
    }
    this.on(eventName, wrapper)
  }
}

观察者模式

const queuedObservers = new Set();

const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});

function set(target, key, value, receiver) {
  const result = Reflect.set(target, key, value, receiver);
  queuedObservers.forEach(observer => observer());
  return result;
}

单例模式

核心要点: 用闭包和Proxy属性拦截

function getSingleInstance(func) {
  let instance;
  let handler = {
    construct(target, args) {
      if(!instance) instance = Reflect.construct(func, args);
      return instance;
    }
  }
  return new Proxy(func, handler);
}

洋葱圈模型compose函数

function compose(middleware) {
  return function(context, next) {
    let index = -1;
    return dispatch(0);
    function dispatch(i) {
      // 不允许执行多次中间件
      if(i <= index) return Promise.reject(new Error('next() called multiple times'));
      // 更新游标
      index = i;
      let fn = middle[i];
      // 这个next是外部的回调
      if(i === middle.length) fn = next;
      if(!fn) return Promsie.resolve();
      try{
        return Promise.resove(fn(context, dispatch.bind(null, i+1)));
      }catch(err){
        return Promise.reject(err);
      }
    }
  }
}

总结

当你看到这里的时候,几乎前端面试中常见的手写题目基本都覆盖到了,对于社招的场景下,其实手写题的题目是越来越务实的,尤其是真的有hc的情况下,一般出一些常见的场景题的可能性更大,所以最好理解➕记忆,最后欢迎评论区分享一些你遇到的题目

至此,手写题系列分享结束,希望对你有所帮助

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:web前端面试题库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1267357.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自定义Windows服务启动失败

文章目录 自定义Windows服务启动失败报错内容解决方案管理员身份运行cmd进入到InstallUtil.exe的路径&#xff0c;使用cd命令。使用InstallUtil.exe工具安装服务。 自定义Windows服务启动失败 报错内容 “无法从命令行或调试器启动服务&#xff0c;必须首先安装Windows服务(使…

《opencv实用探索·四》Mat图像数据类型转换和归一化显示

一种数据类型转为另一种数据类型&#xff0c;不改变图像大小&#xff0c;但每个像素值可能会变 src.convertTo(dst, type, scale, shift);Scale和shitf默认为0&#xff08;这两个参数也相当于对比度和亮度&#xff09; 现在有个8位图像&#xff0c;把8位转成32位 可以看到像素…

【EI稳定检索】第三届绿色能源与电力系统国际学术会议(ICGEPS 2024)

第三届绿色能源与电力系统国际学术会议&#xff08;ICGEPS 2024&#xff09; 2024 3rd International Conference on Green Energy and Power Systems 绿色能源是指可以直接用于生产和生活的能源。它包括核能和“可再生能源”。随着世界各国能源需求的不断增长和环境保护意识…

人工智能 -- 技术概览

1、我们身处人工智能的时代 人们从早期做web开发&#xff0c;到移动端的开发&#xff1b;之后随着数据量的增大&#xff0c;人们开始研究高并发的问题&#xff1b;当数据量不断的增大&#xff0c;而人们希望数据不被浪费时&#xff0c;产生了大数据的技术&#xff0c;包括&…

国标GB28181协议/RTSP视频监控汇聚平台EasyCVR(V.3.4)页面UI大更新

为提高用户体验&#xff0c;增强平台功能&#xff0c;旭帆科技的Easy系列平台也在不断优化更新中。在最新的EasyCVR&#xff08;V.3.4&#xff09;中&#xff0c;其最显著的区别即为首页UI的调整。 其亮点是在【配置中心】-【基础配置】-【展示信息】中&#xff0c;首页UI可分…

Spark经典案例分享

Spark经典案例 链接操作案例二次排序案例 链接操作案例 案例需求 数据介绍 代码如下&#xff1a; package base.charpter7import org.apache.hadoop.conf.Configuration import org.apache.hadoop.fs.{FileSystem, Path} import org.apache.spark.SparkContext import org.a…

品牌全渠道营销系统如何与不同经销商ERP打通

品牌商在与各经销商ERP系统打通方面面临的挑战。传统的ERP系统往往使得数据收集和合作变得繁琐且低效&#xff0c;导致市场响应迟缓&#xff0c;影响整体的供应链管理和市场决策。我们的解决方案旨在破解这一难题&#xff0c;提供一个全渠道营销系统&#xff0c;它能自动与各类…

啊哒-MISC-bugku-解题步骤

——CTF解题专栏—— 题目信息&#xff1a; 题目&#xff1a;啊哒 作者&#xff1a;第七届山东省大学生网络安全技能大赛 提示&#xff1a;无 解题附件&#xff1a; 解题思路&#xff1a; 图片的话还是老三样斧winwalk、010Editor、Stegsolve。ok直接开搞&#xff01; 解题…

Typora .MD笔记中本地图片批量上传到csdn (.PNG格式)(无需其他任何图床软件)

Typora .MD笔记中本地图片批量上传到csdn &#xff08;.PNG格式&#xff09;(无需其他任何图床软件) 截图软件推荐 qq 截图 快捷键 ctrlshiftA. 步骤一 设置Typora 的图片 点击文件. 点击偏好设置 ->图像 我们可以选择将图片复制到我们的文件夹中。 建议刚写好文件标题就…

element ui 表格合计项合并

如图所示&#xff1a; 代码&#xff1a; <el-table height"400px" :data"tableData " borderstyle"width: 100%"stripe show-summaryref"table"id"table"> </el-table>监听表格 watch: { //监听table这个对象…

OBC、DCDC自动化测试解决方案!

OBC(车载充电机&#xff09;和DCDC&#xff08;直流-直流变换器&#xff09;是电动汽车的核心部件&#xff0c;DCDC和OBC的功能质量对于整车的性能和安全性至关重要。在OBC和DCDC&#xff0c;以及整车开发测试过程中&#xff0c;需要对OBC和DCDC进行功能和性能方面进行全面的测…

银河麒麟高级服务器操作系统V10安装达梦数据库管理系统DM8——单实例

一、介绍 之前介绍过供个人学习在VMware虚拟机上安装银河麒麟高级服务器操作系统V10&#xff0c;有兴趣的可以去看看&#xff08;银河麒麟V10安装&#xff09;&#xff0c;本次主要学习在银河麒麟V10上安装达梦数据库-DM8。DM8是达梦公司在总结DM系列产品研发与应用经验的基础…

明天就删,限时领取。zui全拼多多直播问题答疑文档合集。

直播流程是什么&#xff1f;什么时间要做什么事&#xff1f;直播带货播出什么数据才算好?怎么提高直播间流量指标&#xff1f;付费起号还是自然起号好&#xff1f;大小循环话术和场控话术怎么说?今天为大家分享一份“zui全直播500问”&#xff1a; 以上内容为“zui全直播500问…

消息队列进阶-3.消息队列常见问题解决方案

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理&#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44…

产品软文撰写思路,媒介盒子分享

产品软文的目的是为了将产品卖出去&#xff0c;然而想把产品卖出去&#xff0c;不是靠几句话就能实现的&#xff0c;还需要进行多方面分析&#xff0c;今天媒介盒子就来和大家分享&#xff1a;产品软文撰写思路。 一、 产品体验分享 自己要成为自己产品的深度用户并不是一句空…

洗牙器亚马逊UL1431测试报告检测标准

洗牙器是一种电动口腔清洁工具&#xff0c;用于移除食物残渣和牙菌斑&#xff0c;提高口腔卫生水平。 亚马逊要求商家上架的产品检测报告必须是ISO17025/ILAC ISO 17025标准认可的实验室出具的合格报告。 UL测试报告是根据产品选用相应的UL标准进行测试合格后&#xff0c;出具…

【MySQL源码】使用CLion 远程调试MySQL源码

目录 0 准备工作 1 IDE 2 下载MySQL源码 ​编辑 一 配置CLion 1 添加远程服务器 2 配置远程服务器环境 3 升级gdb版本 4 升级CMake版本 5 修改远程服务器文件上传的目录的对应关系 5 配置cmake 7 初始化MySQL 8 启动MySQL 作为DBA工作多年&#xff0c;如果还是停…

InnoDB存储引擎中的锁

文章目录 概要一、需要解决的问题二、共享锁和独占锁1.1 锁定读1.2 表级别的共享锁、独占锁 三、行锁3.1 数据准备3.2 几种常见的行级锁3.3 行锁升级为表锁 概要 关于MySQL涉及到的锁&#xff0c;大致可以总结如下&#xff1a; MyISAM存储引擎在开发过程中几乎很少使用了&…

虾知(知虾):助力Shopee卖家实现市场分析和选品策略优化的神器

在如今的电商市场竞争激烈的背景下&#xff0c;卖家需要借助数据分析工具来了解市场需求、热销产品和竞争状况&#xff0c;以制定明智的选品策略。而虾知&#xff08;知虾&#xff09;作为一款专为Shopee卖家设计的数据分析工具&#xff0c;为卖家提供全面的市场分析、商品分析…

InstructDiffusion-多种视觉任务统一框架

论文:《InstructDiffusion: A Generalist Modeling Interface for Vision Tasks》 github&#xff1a;https://github.com/cientgu/InstructDiffusion InstructPix2Pix&#xff1a;参考 文章目录 摘要引言算法视觉任务统一引导训练集重构统一框架 实验训练集关键点检测分割图像…