Skip to content

前端面试手写题集合

0. promise 执行过程

js
console.log('scriot start')

async function async1() {
  await async2()
  console.log('async1 end');
}

async function async2() {
  console.log('async2 end');
}

async1()

setTimeout(function() {
  console.log('setTimeout')
}, 0)

new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function () {
    console.log('promise1')
  })
   .then(function() {
     console.log('promise2')
   })

console.log('script end')

alt text

0.1 手写PromiseAll方法

https://juejin.cn/post/7069805387490263047#heading-5

js
Promise.MyAll = (promises)=>{
  let arr = [];
  let count = 0;
  return new Promise((resolve, reject)=>{
    promises.forEach((item, i) => {
      Promise.resolve(item).then(res=>{
        arr[i] = res
        count +=1
        if (count === promises.length) {
          resolve(arr)
        }
      }).catch(reject)
    });
  })
}

const p1 = Promise.resolve('p1')

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2 延时一秒')
  }, 1000)
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p3 延时两秒')
  }, 2000)
})

const p4 = Promise.reject('p4 rejected')


Promise.MyAll([p1, p2, p3, p4])
  .then(res => {
    console.log(res)
  })
  .catch(err => console.log(err)) // p4 rejected

alt text

a. 数据结构转换问题

js
const data = [
  { pid: 0, id: 1, name: "a" },
  { pid: 1, id: 2, name: "b" },
  { pid: 0, id: 3, name: "c" },
  { pid: 3, id: 4, name: "d" },
  { pid: 4, id: 5, name: "e" },
];

function formatDataTree(items) {
  let res = [];
  const getChildren = (res, pid) => {
    for (const i of items) {
      if (i.pid === pid) {
        const newItem = { ...i, children: [] };
        res.push(newItem);
        getChildren(newItem.children, newItem.id);
      }
    }
  };

  getChildren(res, 0);
  return res;
}

console.log(formatDataTree(data));

// 转换成 dataTree
const res1 = [
  {
    pid: 0,
    id: 1,
    name: "a",
    children: [{ pid: 1, id: 2, name: "b", children: [] }],
  },
  {
    pid: 0,
    id: 3,
    name: "c",
    children: [
      { pid: 3, id: 4, name: "d", children: [{ pid: 4, id: 5, name: "e" }] },
    ],
  },
];

0. 实现 lazy 链式调用 :

person.eat().sleep(2).eat()

js
// 函数的链式调用

1. 实现 find 方法,根据 id 查找 data 树中的任意一项

js
// 实现find方法,根据id 查找data树中的任意一项
const data = [
  {
    id: "1000",
    name: "深圳",
    children: [
      {
        id: "1001",
        name: "宝安区",
      },
      {
        id: "1002",
        name: "南山区",
      },
    ],
  },
  {
    id: "2000",
    name: "广州",
    children: [
      {
        id: "2001",
        name: "越秀区",
        children: [
          {
            id: "2002",
            name: "大东街道办事处",
          },
        ],
      },
      {
        id: "2003",
        name: "荔湾区",
      },
    ],
  },
];

const find = (data, id) => {
  for (let item of data) {
    if (item.id === id) {
      return item.name;
    }
    if (item.children?.length) {
      const res = find(item.children, id);
      if (res && res !== "未找到") {
        return res;
      }
    }
  }
  return "未找到";
};

const r1 = find(data, "1000"); // 深圳
const r2 = find(data, "1001"); // 宝安区
const r3 = find(data, "2001"); // 越秀区
const r4 = find(data, "2002"); // 大东街道办事处
const r5 = find(data, "3003"); // 未找到

console.log(r1);
console.log(r2);
console.log(r3);
console.log(r4);
console.log(r5);

链式调用函数的方式, 进行加减乘除运算

js
// 链式调用函数的方式, 进行加减乘除运算
class MyCalculator {
  constructor(number) {
    this.number = number;
  }
  add(num) {
    this.number = this.number + num;
    return this;
  }
  minus(num) {
    this.number = this.number - num;
    return this;
  }
  multi(num) {
    this.number = this.number * num;

    return this;
  }
  div(num) {
    this.number = this.number / num;
    return this;
  }

  toString() {
    // 比较时候会触发隐式转换,需要自定义toString方法进行输出
    return this.number;
  }
}

const calculator = new MyCalculator(121);
if (calculator.add(1).minus(2).multi(3).div(4) == 90) {
  console.log("恭喜, 回答正确");
}

1. 发布订阅模式

js

2. 防抖函数

js
const func = function (e) {
  // @TODO
  console.log(e.target.value);
  rederCity(this.value);
};
const debounce = function (func, wait) {
  let timerId;
  return function (...args) {
    let _this = this;
    clearTimeout(timerId);
    timerId = setTimeout(() => {
      func.apply(_this, args);
    }, wait);
  };
};

const debounceFunc = debounce(func, 500);

document.getElementById("search-input").addEventListener("input", debounceFunc);

3. 节流函数

js
const func = function (e) {
  console.log("timeupdate触发");
  console.log("e:", e);
  localStorage.setItem("currentTime", this.currentTime);
};

function throttle(func, wait = 0) {
  // TODO
  let timeId;
  return function (...args) {
    let _this = this;
    if (timeId) {
      return;
    }

    timeId = setTimeout(() => {
      func.apply(_this, args);
      timeId = undefined;
    }, wait);
  };
}

const throttleFn = throttle(func, 1000);
video.addEventListener("timeupdate", throttleFn);

4. 数组去重

方法一:使用 Set

js
function uniqueArray(arr) {
  // @TODO
  return [...new Set(arr)];
}

方法二:使用 filter

js
function uniqueArray(arr) {
  // @TODO
  return arr.filter((item, index, self) => {
    return self.indexOf(item) === index;
  });
}

方法三:使用 reduce

js
function uniqueArray(arr) {
  // @TODO
  return arr.reduce((prev, curr) => {
    return prev.includes(curr) ? prev : [...prev, curr];
  }, []);
}

4.1 实现数组的 myMap 方法

js
Array.prototype.myMap = function (fn, ctx) {
  let ret = [];

  for (let i = 0; i < this.length; i++) {
    ret.push(fn.call(ctx, this[i], i, this));
  }

  return ret;
};

// 测试代码
const nums = [1, 2, 3, 4, 5];
console.log(
  "Map:",
  nums.myMap((x) => x * 2),
  [1, 2]
);

4.2 实现数组的 myFilter 方法

js
Array.prototype.myFilter = function (fn, ctx) {
  let ret = [];

  for (let i = 0; i < this.length; i++) {
    const ret1 = fn.call(ctx, this[i], i, this);
    if (ret1) {
      ret.push(this[i]);
    }
  }

  return ret;
};

// 测试代码
const nums = [1, 2, 3, 4, 5];
console.log(
  "Filter:",
  nums.myFilter((x) => x > 2)
);

5. 数组扁平化

js
function flatten(arr) {
  // @TODO
  const result = [];

  function flat(arr) {
    for (let i = 0; i < arr.length; i++) {
      if (Array.isArray(arr[i])) {
        flat(arr[i]);
      } else {
        result.push(arr[i]);
      }
    }
  }

  flat(arr);
  return result;
}

// 简化版本
function flattenES6(arr) {
  return arr.flat(Infinity);
}

6. 手写 Promise.all

js
Promise.myAll = function (promises) {
  // @TODO
};

7. 手写 Promise.allSettled

js
Promise.myAllSettled = function (promises) {
  // @TODO
};

8. 手写 call 方法

js
const person = {
  name: "张三",
  age: 18,
  sex: "男",
};

Function.prototype.myCall = function (context = window, ...args) {
  // @TODO
  const key = Symbol();
  context[key] = this;
  const res = context[key](...args);
  delete context[key];
  return res;
};

const res = func.myCall(person, 10, 20);
console.log("res 返回值:", res);

9. 手写 apply 方法

myCall方法一致,只是参数不同

js
Function.prototype.myApply = function (context = window, args = []) {
  // @TODO
  const key = Symbol();
  context[key] = this;
  const res = context[key](...args);
  delete context[key];
  return res;
};

10. 手写 bind 方法

js
// 1. 定义myBind方法
// 2. 返回绑定this的新函数
// 3. 合并绑定和新传入的参数

Function.prototype.myBind = function (context = window, ...args1) {
  // @TODO
  return (...regsArgs) => this.call(context, ...args1, ...regsArgs);
};

const bindFunc1 = func1.myBind(person, 10, 20);
const res1 = bindFunc1(30);
console.log("res1 返回值:", res1);

apply实现

js
Function.prototype.myBind = function (context = window, ...args1) {
  // @TODO
  return (...regsArgs) => this.apply(context, [...args1, ...regsArgs]);
};

// 调用并获取返回值
const bindFunc1 = func1.myBind(person, 10, 20);
const res1 = bindFunc1(30);
console.log("res1 返回值:", res1);

11. 手写 new 操作符

js
function myNew(constructor, ...args) {
  // @TODO
}

12. 手写 instanceof

js
function myInstanceof(obj, constructor) {
  // @TODO
}

13. 函数柯里化

将多个参数的函数,转换为单个参数的函数

js
function curriedAdd(a) {
  // @TODO
  return function (b) {
    return function (c) {
      return a + b + c;
    };
  };
}

console.log(curriedAdd(1)(2)(3)); // 6
js
// 需求: 改写函数,实现如下效果

// 思路:
// 1. 保存不定长参数 array
// 2. 是否达到长度
// 3. 累加5个参数, 返回函数

let nums = [];
function sum(...args) {
  // @TODO
  nums.push(...args);
  if (nums.length >= 5) {
    const res = nums.slice(0, 5).reduce((p, v) => p + v, 0);
    nums = []; // 注意累加之后需要清空数组,保证下次累加的值是正确的
    return res;
  } else {
    return sum;
  }
}

// 使用示例
function sum(a, b, c, d, e) {
  return a + b + c + d + e;
}
sum(1)(2)(3)(4)(5);
sum(1)(2, 3)(4)(5);
sum(1)(2, 3, 4)(5);
sum(1, 2, 3)(4, 5);

alt text

优化版本

js
function sumMaker(length) {
  let nums = [];
  function sum(...args) {
    // @TODO
    nums.push(...args);
    if (nums.length >= length) {
      const res = nums.slice(0, length).reduce((p, v) => p + v, 0);
      nums = []; // 注意累加之后需要清空数组,保证下次累加的值是正确的
      return res;
    } else {
      return sum;
    }
  }
  return sum;
}

const sum6 = sumMaker(6);
sum6(1)(2)(3)(4)(5);

const sum4 = sumMaker(4);
sum4(1)(2)(3)(4);

柯里化应用--判断类型

参数复用: 为函数预制通用参数,供给多次重复调用

alt text

alt text

js
const typeOfTest = function (type) {
  return function (obj) {
    // return Object.prototype.toString.call(obj) === `[object ${type}]`
    return typeof obj === type;
  };
};

const typeOfTest = (type) => (thing) => typeof thing === type;

const isString = typeOfTest("string");
const isNumber = typeOfTest("number");
const isBoolean = typeOfTest("boolean");
const isUndefined = typeOfTest("undefined");
const isFunction = typeOfTest("function");

const isObject = (type) => (thing) =>
  thing !== null && typeof thing === "object";

14. 实现深拷贝

js
function cloneDeep(oldObj) {
  // TODO
  let newObj = Array.isArray(oldObj) ? [] : {};

  for (let k in oldObj) {
    if (typeof oldObj[k] === "object") {
      newObj[k] = cloneDeep(oldObj[k]);
    } else {
      newObj[k] = oldObj[k];
    }
  }

  return newObj;
}

15. 最大并发数限制的请求调度器

js

16. 实现异步队列

js

17. 实现 LRU 缓存

js

18. 解析 URL 参数

js
function parseURLParams(url) {
  // @TODO
  const params = {};
  // 提取URL中的查询字符串部分
  const queryString = url.split("?")[1];
  if (!queryString) return params;

  // 分割参数
  const pairs = queryString.split("&");

  for (const pair of pairs) {
    // 处理有等号和无等号的情况
    const [key, value = ""] = pair.split("=");
    // 解码URL编码的字符
    params[decodeURIComponent(key)] = decodeURIComponent(value);
  }

  return params;
}

// 使用URLSearchParams API的简化版本
function parseURLParamsModern(url) {
  const params = new URLSearchParams(url.split("?")[1]);
  return Object.fromEntries(params.entries());
}

19. 实现睡眠函数

js
function sleep(ms) {
  // @TODO
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// 使用示例
async function demo() {
  console.log("开始");
  await sleep(1000);
  console.log("延迟1秒后");
}

20. 实现数组原型方法

forEach

js
Array.prototype.myForEach = function (callback, thisArg) {
  // @TODO
  for (let i = 0; i < this.length; i++) {
    callback.call(thisArg, this[i], i, this);
  }
};

map

js
Array.prototype.myMap = function (callback, thisArg) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    result.push(callback.call(thisArg, this[i], i, this));
  }
  return result;
};

filter

js
Array.prototype.myFilter = function (callback, thisArg) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    if (callback.call(thisArg, this[i], i, this)) {
      result.push(this[i]);
    }
  }
  return result;
};

21 jquery 渲染表格,模拟虚拟 DOM

html
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.10.0/jquery.min.js"></script>

<body>
  <div id="container"></div>
  <button id="change-btn">change</button>

  <script>
    var data = [
      {
        name: "张三",
        age: 18,
        sex: "男",
      },
      {
        name: "李四",
        age: 19,
        sex: "男",
      },
      {
        name: "王五",
        age: 20,
        sex: "男",
      },
    ];

    // 渲染函数*/
    var render = function (data) {
      // todo
      var $container = $("#container");

      // 清空容器
      $container.empty();

      // 使用字符串拼接创建整个表格
      var tableHTML = "<table>";

      // 添加表头
      tableHTML +=
        "<thead><tr><th>姓名</th><th>年龄</th><th>性别</th></tr></thead>";

      // 添加表格内容
      tableHTML += "<tbody>";
      data.forEach(function (item, index) {
        tableHTML += "<tr>";
        tableHTML += "<td>" + item.name + "</td>";
        tableHTML += "<td>" + item.age + "</td>";
        tableHTML += "<td>" + item.sex + "</td>";
        tableHTML += "</tr>";
      });
      tableHTML += "</tbody>";
      tableHTML += "</table>";

      // 将整个表格HTML添加到容器
      $container.append(tableHTML);
    };

    $("#change-btn").click(function () {
      data[0].name = "zhangsan";
      data[1].age = 21;
      render(data);
    });

    render(data);
  </script>
</body>

22 实现 (5).add(3).minus(2) ===> 输出 6

js
;(function(){
  // 每一个方法执行完,都要返回Number这个类的实例,这样才可以继续调用
  // Number类原型中的方法(链式写法)
  function check(num){
    num = Number(num)
    return isNaN(num)?0: num
  }

  function add(num) {
    num = check(num)
    return this + num
  }

  function minus(num) {
    num = check(num)
    return this - num
  }


  Number.prototype.add = add

  Number.prototype.minus = minus

}());
const res = (5).add(3).minus(2)
console.log(res)

23 如何把一个字符串的大小写取反(大写变小写, 小写变大写, 例如 AbC 变为 aBc)

方法1:使用循环

遍历字符串中的每个字符,然后根据其是否为大写或小写来决定是转换为大写还是小写

js
const str = 'AbCdeeeEEf&&&'

const test = (str)=>{
    let s = ''
    for(let i = 0; i< str.length ; i++){
        let item = str[i]
        if (item === item.toUpperCase()) {
            s += item.toLowerCase()
        }else if(item === item.toLowerCase()) {
            s += item.toUpperCase()
        }else{
            s += item
        }
    }

    return s
}

const res = test(str)
console.log('res:',  res)

方法1:使用ASCII码值进行转换

另一种方法是基于字符的ASCII码值进行转换,这种方法在某些情况下也能工作,但不是最直观的方法

js
const str = 'AbCdeeeEEf'

const test = (str)=>{
    let s = ''
    for (let i = 0; i < str.length; i++) {
        const item = str[i];
        const itemCharCode = item.charCodeAt()
        if (itemCharCode >= 65 && itemCharCode<= 90 ) {
            s += String.fromCharCode(itemCharCode + 32);
        }
        else if (itemCharCode >= 97 && itemCharCode<=122 ) {
            s += String.fromCharCode(itemCharCode - 32);
        }
        else{
            s += String.fromCharCode(itemCharCode)
        }
    }

    return s
}

const res = test(str)
console.log('res:',  res)

24 实现一个$attr(name, value) 遍历

html
<body>
    <div id="AA" class="box clearfix"></div>
    <div myIn="1"></div>
    <div class="box clearfix"></div>
    <div name="BB"></div>
    <div></div>
    <div id="AA"></div>
    <div myIn="1" class="clearfix"></div>
    <div class="box"></div>
    <div myIn="2"></div>
    <div name="BB"></div>


    <script>
        /*
            实现一个$attr(name, value) 遍历
            属性为name
            值为value的元素集合

            例如如下示例
        */
        function $attr(property, value) {
            // =》获取当前页面中所有的标签
            let elements = document.getElementsByTagName('*');
            let arr = []
            // [].forEach.call(elements, item={});
            elements = Array.from(elements)// 把非数组转换为数组
            elements.forEach(item => {
                // 存储的是当前元素的property 对应属性值
                const itemValue = item.getAttribute(property)
                if (itemValue && itemValue.includes(value)) {
                    arr.push(item)
                }
            });
            return arr
        }

       console.log( $attr('myIn', '1'))
    </script>
</body>

25 某公司1-12月数据销售额

js
/*
某公司1-12月数据销售额,存到一个对象里面
如下:{
1: 222,
2: 123,
5: 888
}

请把数据结构处理为如下结构:
[222, 123, null, null, 888, 
null,null,null,null,null,null,null,]
*/

var obj = {
  1: 222,
  2: 123,
  5: 888,
};


let arr = new Array(12).fill(null).map((item, index)=>{
    return obj[index + 1]||null
})
console.log(arr)

// -------------------------
// var obj = {
//     1: 222,
//     2: 123,
//     5: 888,
// };

// obj.length = 13;
// let arr = Array.from(obj).splice(1).map(item=> {
//     return typeof item === 'undefined'?null: item
// })
// console.log(arr)

// -------------------------
// var obj = {
//     1: 222,
//     2: 123,
//     5: 888,
// };

// let arr = new Array(12).fill(null)
// Object.keys(obj).forEach(item=>{
//     arr[item - 1] = obj[item]
// })

// console.log(arr)

26 给定两个数组, 写一个方法计算它们的交集

js
// 给定两个数组, 写一个方法计算它们的交集
let num1 = [1,2,2,1];
let num2 = [2, 2];
// => 输出结果 [2, 2]


let res = []
num1.forEach(i=>{
 if (num2.includes(i)) {
    res.push(i)
 }
})

console.log(res)


// 给定两个数组, 写一个方法计算它们的交集

let num1 = [1,2,2,1];
let num2 = [2, 2];
// => 输出结果 [2, 2]

let res = []
for (let i = 0; i < num1.length; i++) {
    const item1 = num1[i];
    for (let j = 0; j < num2.length; j++) {
        const item2 = num2[j];
        if (item1 === item2) {
            res.push(item1)
            break
        }
        
    }
}
console.log(res)