offer收割之手写代码

718 阅读6分钟

反转字符串 ‘I love you’

将‘I love you’变为’you love I‘

关键点用join把数组转变为字符串(join可以指定字符串的连接符)

const str = 'i love you';

const temp = str.split(' ');
temp.reverse();

console.log(temp.join(' '))  // you love i

将[1,2,3,4,5,6,7,8,9,10]转变为二维数组[[1,2,3],[4,5,6],[7,8,9],[10]]

思路: 将一维数组每隔三项放一起,可以利用arr.slice(i, i+3),所以关键是slice的索引设置,这时候用forEach和map不合适因为不需要对每一项遍历,所以使用for循环

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];

const result = [];
// 循环的i每次都+3
for (let i = 0; i <= arr.length - 1; i += 3) {
  result.push(arr.slice(i, i + 3))
}

console.log(result) // [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ], [ 10, 11, 12 ], [ 13 ] ]

简短优雅地利用js实现 sleep 函数

最佳方案:利用Promise()结合setTimeout 实现sleep 然后在async await中使用

const log = console.log;
const sleep = (timeout) => {
  return new Promise((resolve)=>{
    setTimeout(()=>{
      resolve();
    }, timeout)
  })
}

const main = async()=>{
  await sleep(1000);
  log(1);
  await sleep(2000);
  log(2);
  await sleep(3000);
  log(3);
}

字符串替换

str='aa{a.b}ccdd{a.c}aa123{a.d}',obj={b: 789,c:56}, 输出'aa789ccdd56aa123',即括号里的用obj里面的值替换?

function parseString(str, obj) {
  Object.keys(obj).forEach(key => {
    str = str.replace(new RegExp(`{a.${key}}`, 'g'), obj[key]);
  });
  return str;
}

字符串中的第一个唯一字符

给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在则返回 -1。

示例 1:
输入: s = "leetcode"
输出: 0

示例 2:
输入: s = "loveleetcode"
输出: 2

示例 3:
输入: s = "aabb"
输出: -1

// 遍历字符串的每一项 判断lastIndexOf 和 indexOf这两项是否相等, 如果相等就证明是第一个唯一的字符串

var firstUniqChar = function(s) {
for(let i=0;i<s.length;i++){
    if(s.lastIndexOf(s[i])==s.indexOf(s[i])){
        let a = s.indexOf(s[i])
        return a;
    }
}return -1;
};
console.log(firstUniqChar("loveleetcode"));

1 compose

题目描述:实现一个 compose 函数

// 用法如下:
function fn1(x) {
  return x + 1;
}
function fn2(x) {
  return x + 2;
}
function fn3(x) {
  return x + 3;
}
function fn4(x) {
  return x + 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1+4+3+2+1=11
复制代码

实现代码如下:

function compose(...fn) {
  if (!fn.length) return (v) => v;
  if (fn.length === 1) return fn[0];
  // 相当于每一个函数的执行结果又作为下一个函数的参数
  return fn.reduce(
    (pre, cur) =>
      (...args) =>
        pre(cur(...args))
  );
}
复制代码

2 settimeout 模拟实现 setinterval(带清除定时器的版本)

题目描述:setinterval 用来实现循环定时调用 可能会存在一定的问题 能用 settimeout 解决吗

实现代码如下:

function mySettimeout(fn, t) {
  let timer = null;
  function interval() {
    fn();
    timer = setTimeout(interval, t);
  }
  interval();
  return {
    cancel:()=>{
      clearTimeout(timer)
    }
  }
}
// let a=mySettimeout(()=>{
//   console.log(111);
// },1000)
// let b=mySettimeout(() => {
//   console.log(222)
// }, 1000)
复制代码

扩展:我们能反过来使用 setinterval 模拟实现 settimeout 吗?

const mySetTimeout = (fn, time) => {
  const timer = setInterval(() => {
    clearInterval(timer);
    fn();
  }, time);
};
// mySetTimeout(()=>{
//   console.log(1);
// },1000)
复制代码

扩展思考:为什么要用 settimeout 模拟实现 setinterval?setinterval 的缺陷是什么?

答案请自行百度哈 这个其实面试官问的也挺多的 小编这里就不展开了

4 数组去重

实现代码如下:

function uniqueArr(arr) {
  return [...new Set(arr)];
}
复制代码

5 数组扁平化

题目描述:实现一个方法使多维数组变成一维数组

最常见的递归版本如下:

function flat(arr, depth = 1) {
  if (depth > 0) {
    // 以下代码还可以简化,不过为了可读性,还是....
    return arr.reduce((pre, cur) => {
      return pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur);
    }, []);
  }
  return arr.slice();
}

复制代码

扩展思考:能用迭代的思路去实现吗?

实现代码如下:

function flatter(arr) {
  if (!arr.length) return;
  while (arr.some((item) => Array.isArray(item))) {
    arr = [].concat(...arr);
  }
  return arr;
}
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));
复制代码

7 实现有并行限制的 Promise 调度器

入参是tasks 里面是一个promise对象的数组,limit是限制并发的数量,

  • runningCount:用于跟踪当前正在执行的任务数量。
  • completedCount:记录已经完成的任务数量。
  • results:用于存储成功完成的任务的结果。
  • failedTasks:存储失败的任务及其相关错误信息。

executeNextTask 函数:

  • 当还有可执行任务(runningCount < limit 且 completedCount < tasks.length)时,获取下一个任务。
  • 执行任务,如果成功,更新相关状态并执行下一个任务;如果失败,将任务和错误信息存入 failedTasks 并执行下一个任务。

retryFailedTasks 函数:

  • 当有失败任务且还有执行能力(runningCount < limit)时,取出失败任务重新执行。
  • 成功则更新状态,失败则再次放入 failedTasks 。

在主逻辑中:

  • 首先通过循环启动最多 limit 个任务的执行。
  • 然后通过一个定时检查的 interval 来判断是否所有任务都完成(包括成功和失败处理完毕)。
  • 如果完成则清除定时并解析 results ;否则调用 retryFailedTasks 尝试重新执行失败任务。
function limitedConcurrencyExecutor(tasks, limit) {
    let runningCount = 0;
    let completedCount = 0;
    let results = [];
    let failedTasks = [];

    function executeNextTask() {
        if (runningCount < limit && completedCount < tasks.length) {
            const task = tasks[completedCount];
            runningCount++;

            task.then(result => {
                runningCount--;
                results.push(result);
                completedCount++;
                executeNextTask();
            }).catch(error => {
                runningCount--;
                failedTasks.push({ task, error });
                executeNextTask();
            });
        }
    }

    for (let i = 0; i < limit; i++) {
        executeNextTask();
    }

    function retryFailedTasks() {
        while (failedTasks.length > 0 && runningCount < limit) {
            const { task, error } = failedTasks.shift();
            runningCount++;

            task.then(result => {
                runningCount--;
                results.push(result);
                retryFailedTasks();
            }).catch(error => {
                runningCount--;
                failedTasks.push({ task, error });
                retryFailedTasks();
            });
        }
    }

    return new Promise((resolve) => {
        const interval = setInterval(() => {
            if (completedCount === tasks.length && failedTasks.length === 0) {
                clearInterval(interval);
                resolve(results);
            } else {
                retryFailedTasks();
            }
        }, 100);
    });
}

// 示例用法
const tasks = [
    new Promise((resolve) => setTimeout(() => resolve('Task 1 result'), 1000)),
    new Promise((reject) => setTimeout(() => reject('Task 2 error'), 2000)),
    new Promise((resolve) => setTimeout(() => resolve('Task 3 result'), 500))
];

limitedConcurrencyExecutor(tasks, 2).then(results => console.log(results));

8 new 操作符

题目描述:手写 new 操作符实现

实现代码如下:

function myNew(fn, ...args) {
  let obj = Object.create(fn.prototype);
  let res = fn.call(obj, ...args);
  if (res && (typeof res === "object" || typeof res === "function")) {
    return res;
  }
  return obj;
}
用法如下:
// // function Person(name, age) {
// //   this.name = name;
// //   this.age = age;
// // }
// // Person.prototype.say = function() {
// //   console.log(this.age);
// // };
// // let p1 = myNew(Person, "lihua", 18);
// // console.log(p1.name);
// // console.log(p1);
// // p1.say();
复制代码

10 深拷贝(考虑到复制 Symbol 类型)

题目描述:手写 new 操作符实现

实现代码如下:


function deepCopy(obj) {
  if (typeof obj!== 'object' || obj === null) {
    return obj;
  }

  let newObj;
  if (Array.isArray(obj)) {
    newObj = [];
    for (let item of obj) {
      newObj.push(deepCopy(item));
    }
  } else {
    newObj = {};
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        newObj[key] = deepCopy(obj[key]);
      }
    }
  }
  return newObj;
}

复制代码

12 柯里化

题目描述:柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。

实现代码如下:

function currying(fn, ...args) {
  const length = fn.length;
  let allArgs = [...args];
  const res = (...newArgs) => {
    allArgs = [...allArgs, ...newArgs];
    if (allArgs.length === length) {
      return fn(...allArgs);
    } else {
      return res;
    }
  };
  return res;
}

// 用法如下:
// const add = (a, b, c) => a + b + c;
// const a = currying(add, 1);
// console.log(a(2,3))
复制代码

16 快排--时间复杂度 nlogn~ n^2 之间

题目描述:实现一个快排

实现思想: 任意选一个数字作为中间数,然后递归把所有小于中间数的放左边,大于的放右边,每个数字都重复这一步

实现代码如下:

function quickSort(arr) {
  // 前面的if判断必须加 否则filter会报错 :Maximum call stack size exceeded
  if (arr.length < 2) {
    return arr;
  }
  const cur = arr[arr.length - 1];
  const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1);
  const right = arr.filter((v) => v > cur);
  return [...quickSort(left), cur, ...quickSort(right)];
}
// console.log(quickSort([3, 6, 2, 4, 1]));
复制代码

20 防抖节流

题目描述:手写防抖节流

实现代码如下:

// 防抖
function debounce(fn, delay = 300) {
  //默认300毫秒
  let timer;
  return function () {
    const args = arguments;
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this, args); // 改变this指向为调用debounce所指的对象,且apply的第二个参数可以是数组也可以是类数组对象,所以没必要把arguments转为数组
    }, delay);
  };
}

window.addEventListener(
  "scroll",
  debounce(() => {
    console.log(111);
  }, 1000)
);

// 节流
// 设置一个标志
// throttle.js


// 使用时间戳





window.addEventListener(
  "scroll",
  throttle(() => {
    console.log(111);
  }, 1000)
);
复制代码

21 写版本号排序的方法

题目描述:有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。现在需要对其进行排序,排序的结果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']

实现代码如下:

arr.sort((a, b) => {
  let i = 0;
  const arr1 = a.split(".");
  const arr2 = b.split(".");

  while (true) {
    const s1 = arr1[i];
    const s2 = arr2[i];
    i++;
    if (s1 === undefined || s2 === undefined) {
      return arr2.length - arr1.length;
    }

    if (s1 === s2) continue;

    return s2 - s1;
  }
});
console.log(arr);
复制代码


// 节流

   function throttle(func, delay) {
   
   let lastCallTime = 0; 
   
   return function (...args) { 
   const now = new Date().getTime(); 
   if (now - lastCallTime >= delay)
   { 
   func.apply(this, args);
   lastCallTime = now;
   } 
   };
   }

实现如何取消 promise

Promise.race()方法可以用来竞争 Promise 可以借助这个特性 自己包装一个 空的 Promise 与要发起的 Promise 来实现

function wrap(pro) {
  let obj = {};
  // 构造一个新的promise用来竞争
  let p1 = new Promise((resolve, reject) => {
    obj.resolve = resolve;
    obj.reject = reject;
  });

  obj.promise = Promise.race([p1, pro]);
  return obj;
}

let testPro = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(123);
  }, 1000);
});

let wrapPro = wrap(testPro);
wrapPro.promise.then((res) => {
  console.log(res);
});
wrapPro.resolve("被拦截了");
复制代码

promise.all的原理

  1. Promise.all 接受一个可迭代对象(通常是一个数组)作为参数,其中每个元素都是一个 Promise 实例。
  2. 它会创建一个新的 Promise 对象来跟踪所有传入的 Promise 的状态。
  3. 然后遍历传入的 Promise 数组。
  4. 对于每个 Promise,它会等待其完成(要么成功完成,要么失败)。
  5. 成功完成时,将结果保存起来;失败时,立即拒绝新创建的跟踪 Promise 。
  6. 只有当所有的 Promise 都成功完成时,新创建的跟踪 Promise 才会成功完成,并将所有成功的结果以数组的形式返回。
  7. 如果其中任何一个 Promise 失败,新创建的跟踪 Promise 就会立即失败,并返回第一个失败的 Promise 的错误原因。

33 实现一个对象的 flatten 方法

题目描述:

const obj = {
 a: {
        b: 1,
        c: 2,
        d: {e: 5}
    },
 b: [1, 3, {a: 2, b: 3}],
 c: 3
}

flatten(obj) 结果返回如下
// {
//  'a.b': 1,
//  'a.c': 2,
//  'a.d.e': 5,
//  'b[0]': 1,
//  'b[1]': 3,
//  'b[2].a': 2,
//  'b[2].b': 3
//   c: 3
// }
复制代码

实现代码如下:

function isObject(val) {
  return typeof val === "object" && val !== null;
}

function flatten(obj) {
  if (!isObject(obj)) {
    return;
  }
  let res = {};
  const dfs = (cur, prefix) => {
    if (isObject(cur)) {
      if (Array.isArray(cur)) {
        cur.forEach((item, index) => {
          dfs(item, `${prefix}[${index}]`);
        });
      } else {
        for (let k in cur) {
          dfs(cur[k], `${prefix}${prefix ? "." : ""}${k}`);
        }
      }
    } else {
      res[prefix] = cur;
    }
  };
  dfs(obj, "");

  return res;
}
flatten();
复制代码

React实现点击按钮数字+1

import React, { useState } from 'react';

const Test = function () {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1)
  }

  return <div onClick={handleClick}>click me {count}</div>;
};

实现每间隔1s打印当前时间

import React, { useState, useRef, useEffect } from 'react';

const Test = () => {
    const [time, setTime] = useState(new Date());
    // ref解决闭包陷阱问题,这样能保证ref一直是一个引用值
    let timer = useRef();
    useEffect(() => {
        timer.current = setInterval(() => {
            setTime(new Date());
        }, 1000);
        return () => clearInterval(timer.current);
    }, [])
    return (
        <div>{time.toLocaleString()}</div>
    )
}

ts实现两个数相加的函数,两个参数的类型必须同时为字符串或者数字

type Combinable = string | number;

function add<T extends Combinable>(a: T, b: T): Combinable {
  if (typeof a === 'string') {
    return a + b;
  } else {
    return (a as number) + (b as number);
  }
}
 
let a: Combinable = 123;
let b: Combinable = 123;
 
add(a, b);

要求输出数组,数组里面的每一项是由随机数生成

const arr = [];
for(let i=0;i<100;i++){
    const arrNum=parseInt(Math.random()*100)+1;
    const flag=true;
    for(let j=0;j<=arr.length;j++){
        if(arrNum==arr[j]) {
            flag = false; 
            break;
        }
    }
     if(flag){
        arr.push(arrNum);
     }else{
         i--;
     }
}

一个数组里面有若干个对象,要求去除重复的对象

遍历数组然后使用JSON.stringify()将每一项转为字符串,然后对比字符串是否相等来取除


function removeDuplicateObjects(arr) { 
  const uniqueObjects = []; 
  const seen = new Set(); 
  for (const obj of arr) { 
    const objString = JSON.stringify(obj);
    if (!seen.has(objString)) {
      uniqueObjects.push(obj); 
      seen.add(objString);
    } 
  }  
 return uniqueObjects; 
}

实现斐波那契树列

前端面试官

斐波那契数列(Fibonacci sequence),又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、…… ,在数学上,斐波那契数列以如下递推的方法定义:F(0)=0F(0)=0F(1)=1F(1)=1, F(n)=F(n1)+F(n2)F(n)=F(n - 1)+F(n - 2)n2n ≥ 2nNn ∈ N*)

以下是使用 JavaScript 递归实现斐波那契数列的代码:

JAVASCRIPTCopy

function fibonacci(n) {
  if (n <= 0) {
    return '输入应为正整数';
  } else if (n === 1) {
    return 0;
  } else if (n === 2) {
    return 1;
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

for(let i = 1; i <= 10; i++) {
  console.log(fibonacci(i));
}

需要注意的是,递归实现斐波那契数列在数值较大时可能会出现性能问题,因为会有大量的重复计算。

20. 实现简单路由

⼀个简单的路由系统可以通过JavaScript对象和事件监听器来实现。下⾯是⼀个简单的基

于浏览器的路由⽰例,使⽤URL的hash部分来模拟路由变化,并触发相应的事件或函

数。



<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-sc

ale=1.0">

<title>Simple Router</title>

<script>

const routes = {

'/': function() {

document.getElementById('content').textContent = 'Home Page';
},

'/about': function() {

document.getElementById('content').textContent = 'About Page';

},

'/contact': function() {

document.getElementById('content').textContent = 'Contact Page';
}
};

function initRoute() {
const hash = window.location.hash.substr(1);

if (routes[hash]) {
routes[hash]();
} else {
routes['/']();
}
}

window.addEventListener('hashchange', function() {

initRoute();

});

window.onload = initRoute;

</script>

</head>

<body>

<nav>

<a href="#/">Home</a> |

<a href="#/about">About</a> |

<a href="#/contact">Contact</a>

</nav>

16. ⽤Promise实现图⽚的异步加载

在JavaScript中,我们可以使⽤Promise来实现图⽚的异步加载。Promise是⼀种表⽰异

步操作可能完成(或失败)的对象。

以下是⼀个简单的例⼦,说明如何使⽤Promise来加载图⽚:

function loadImage(url) {

return new Promise((resolve, reject) => {

let img = new Image()

img.onload = () => {

resolve(img)

}

img.onerror = (error) => {

reject(error)

}

img.src = url

})

}

// 使⽤⽅式

loadImage('example.com/path/to/ima…')

红灯 3s 亮⼀次,绿灯 1s 亮⼀次,⻩灯 2s 亮⼀次;如何让三个灯不断交替重复亮灯?

分别定义三个间隔定时器,然后分别是3s 2s 1s后让他透明度设置为0



let redLight = document.getElementById('red');

let greenLight = document.getElementById('green');

let yellowLight = document.getElementById('yellow');

let redInterval = setInterval(function() {

redLight.style.opacity = 1;

setTimeout(function() {

redLight.style.opacity = 0;

}, 3000);

}, 9000);

let greenInterval = setInterval(function() {

setTimeout(function() {

greenLight.style.opacity = 1;

setTimeout(function() {

greenLight.style.opacity = 0;

}, 1000);

}, 3000);

}, 9000);

let yellowInterval = setInterval(function() {

setTimeout(function() {

setTimeout(function() {2024前端⾼频—⼿写代码篇

26

yellowLight.style.opacity = 1;

setTimeout(function() {

yellowLight.style.opacity = 0;

}, 2000);

}, 4000);

}, 0);

}, 9000);

实现 add(1)(2)(3)

在JavaScript中,你可以通过创建⼀个返回函数的函数来实现这种链式调⽤的模式。这种

模式通常被称为柯⾥化(Currying)的⼀种形式

function add() {
  let sum = 0
  function innerAdd(num) {
    sum += num
    return innerAdd
  }

  innerAdd.getResult = function() {
    return sum
  }
  return innerAdd
}

let result = add(1)(2)(3)
console.log(result.getResult())

在这个例⼦中, add 函数返回了⼀个名为 innerAdd 的内部函数。 innerAdd 函数接受⼀个 数字参数,将其加到 sum 变量上,然后返回⾃⼰。因此,你可以连续调⽤ add(1)(2)(3) ,每次调⽤都会将新的数字添加到 sum 中。

需要注意的是,由于 innerAdd 是⼀个函数,如果你直接打印 add(1)(2)(3) ,它将输出函

数本⾝⽽不是结果。为了解决这个问题,我们覆盖了 innerAdd 的 getResult ⽅法,使其返

回 sum 的值。这样,当你尝试打印 add(1)(2)(3) 时,实际上会调⽤

innerAdd.getResult() ,从⽽得到结果。

实现倒计时组件(美团)

要求组件每隔1s刷新一次屏幕,props为starttime endtime 还有结束的回调


import React, { useState, useEffect } from 'react';

function Countdown({ startTime, endTime, onEnd }) {
    const [countdown, setCountdown] = useState(startTime);

    useEffect(() => {
        const interval = setInterval(() => {
            if (countdown > endTime) {
                setCountdown(countdown - 1);
            } else {
                clearInterval(interval);
                onEnd();
            }
        }, 1000);

        return () => clearInterval(interval);
    }, [countdown, endTime, onEnd]);

    return <div>{countdown}</div>;
}

export default Countdown;

实现一个用于组件间事件通信的class(美团)

方法要有事件监听 事件发出 事件取消

class EventEmitter {
    constructor() {
        this.events = {};
    }

    on(eventName, callback) {
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }
        this.events[eventName].push(callback);
    }

    emit(eventName, ...args) {
        if (this.events[eventName]) {
            this.events[eventName].forEach(callback => callback(...args));
        }
    }

    off(eventName, callback) {
        if (this.events[eventName]) {
            this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
        }
    }
}

// 使用示例
const emitter = new EventEmitter();

const callback = (data) => {
    console.log(`Received data: ${data}`);
};

emitter.on('customEvent', callback);

emitter.emit('customEvent', 'Hello from emitter!');

emitter.off('customEvent', callback);

对象过滤(京东)

把一个多层对象中空数组 空对象 空字符串去除掉,

const obj={
a: 1,b:'2',c:[],// xd:{
aa: 1,
bb:'2',
Cc:1l x
dd: {},il x
ee: 0,
e:{},11 x

// 定义一个过滤函数 
function filterEmpty(obj) {
    const filtered = {}; 
    Object.keys(obj).forEach(key => {
        const value = obj[key];
        if (Array.isArray(value)) {
            if (value.length > 0) {
                filtered[key] = value;
                // 如果是数组且非空,则保留
            }
        } else if (typeof value === 'object' && value !== null) {
            const nestedFiltered = filterEmpty(value); 
            if (Object.keys(nestedFiltered).length > 0) {
                filtered[key] = nestedFiltered;
                // 如果是对象且非空,则保留
            }
        } else if (value !== undefined && value !== null && value !== '') {
            filtered[key] = value;
            // 其他情况非空则保留
        }
    }); 
    return filtered;
}