前端面试题摘录

254 阅读6分钟

1.快速排序


function quickSort(arr) {
    if (arr.length <= 1) {
        return arr;
    }
    let pivot = arr[0];
    let left = [];
    let right = [];
    for(let i = 1; i < arr.length; i++) {
        if (arr[i] < pivot) {
            left.push(arr[i]);
        } else {
            right.push(arr[i]);
        }
    }
    return [...quickSort(left), pivot, ...quickSort(right)]
}

2.实现new方法

function New(contractor, ...args) {
    const obj = Object.create(contractor.prototype);
    const result = contractor.apply(obj, args);
    return typeof result === 'object'? result : obj;
}

3.斐波拉契数列

3.1 递归实现

function fibonacci(n) {
    if (n < 2) return n;
    return fibonacci(n-1) + fibonacci(n-2);
}

3.2 ES6的数组交换实现

function fib(max) {
    let a = 0; b = 1; arr = [0, 1];
    while (arr.length < max) {
        [a, b] = [b, a + b];
        arr.push(b);
    }
    return arr;
}

4.防抖函数

function debounce(fn, delay) {
    let timer = null;
    return function () {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, arguments);
        })
    }
}

5.节流函数

实现一

function throttle(fn, delay) {
    let lastTime = 0;
    return function () {
        let nowTime = Date.now();
        if (nowTime - lastTime > delay) {
            fn.apply(this, arguments);
            lastTime = nowTime;
        }
    }
}

实现二

function throttle(fn, delay) {
    let flag = true;
    return function () {
        if (flag) {
            flag = false
            setTimeout(() => {
                fn.apply(this, arguments);
                flag = true;
            }, delay)
        }
    }
}

6.对象深比较

function _isObj(obj) {
    return Object.prototype.toString.call(obj) === '[object Object]';
}

function _deepCompare(a, b) {
    // 1.判断一个或者两个都不是对象
    if (!_isObj(a) ||!_isObj(b)) {
        return a === b;
    }
    // 2.同一个对象
    if (a === b) {
        return true;
    }
    // 3.不是同一个对象
    if (Object.keys(a).length !== Object.keys(b).length) {
        return false;
    }
    for (let key in a) {
        if (!_deepCompare(a[key], b[key])) {
            return false;
        }
    }
    return true;
}

7.虚拟滚动实现(item定高)

7.1 Transform

import &#34;./styles.css&#34;;
import { useEffect, useState, useRef, useMemo } from &#34;react&#34;;
export default function App(props) {
  const { list, itemHeight } = props;
  const [start, setStart] = useState(0);
  const [count, setCount] = useState(0);
  const scrollRef = useRef(null);
  const contentRef = useRef(null);
  const totalHeight = useMemo(() => list.length * itemHeight, [list.length]);

  useEffect(() => {
    setCount(Math.ceil(scrollRef.current.clientHeight / itemHeight));
  }, [itemHeight]);

  const scrollHandle = () => {
    const { scrollTop } = scrollRef.current;
    const newStart = Math.floor(scrollTop / itemHeight);
    setStart(newStart);

    contentRef.current.style.transform = `translate3d(0, ${
      newStart * itemHeight
    }px, 0)`;
  };

  const subList = list.slice(start, start + count);
  return (
    <div>
      <div>
        <div>
          {subList.map((item, index) => {
            return (
              <div>
                {item}
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

7.2 Absolute

import &#34;./styles.css&#34;;
import { useEffect, useState, useRef, useMemo } from &#34;react&#34;;
export default function App(props) {
  const { list, itemHeight } = props;
  const [start, setStart] = useState(0);
  const [count, setCount] = useState(0);
  const scrollRef = useRef(null);
  const totalHeight = useMemo(() => list.length * itemHeight, [list.length]);

  useEffect(() => {
    setCount(Math.ceil(scrollRef.current.clientHeight / itemHeight));
  }, [itemHeight]);

  const scrollHandle = () => {
    const { scrollTop } = scrollRef.current;
    const newStart = Math.floor(scrollTop / itemHeight);
    setStart(newStart);
  };

  const subList = list.slice(start, start + count);
  return (
    <div>
      <div>
          {subList.map((item, index) => {
            return (
              <div>
                {item}
              </div>
            );
          })}
      </div>
    </div>
  );
}

7.3 Padding

import &#34;./styles.css&#34;;
import { useState, useRef, useMemo } from &#34;react&#34;;
export default function App(props) {
  const { list, itemHeight, count } = props;
  const [start, setStart] = useState(0);
  const scrollRef = useRef(null);
  const totalHeight = useMemo(() => list.length * itemHeight, [list.length]);
  const currentHeight = useMemo(() => count * itemHeight, [itemHeight, count]);
  const paddingTop = useMemo(() => itemHeight * start, [start])
  const paddingBottom = useMemo(() => totalHeight - currentHeight - itemHeight * start, [start]);

  const scrollHandle = () => {
    const { scrollTop, clientHeight } = scrollRef.current;
    if (
        scrollTop + clientHeight >= itemHeight * (start + count) ||
        scrollTop <= itemHeight * start
    ) {
        const newStart = Math.floor(scrollTop / itemHeight);
        setStart(newStart);
    }
  };

  const subList = list.slice(start, start + count);
  return (
    <div>
      <div>
          {subList.map((item, index) => {
            return (
              <div>
                {item}
              </div>
            );
          })}
      </div>
    </div>
  );
}

8.倒计时

import React, { useState, useEffect } from &#34;react&#34;;

const App = () => {
  const [timeLeft, setTimeLeft] = useState(getTimeLeft());

  useEffect(() => {
    const timerId = setInterval(() => {
      setTimeLeft(getTimeLeft());
    }, 1000);

    return () => clearInterval(timerId);
  }, []);

  function getTimeLeft() {
    const futureDate = new Date(&#34;2024/1/1&#34;);
    const currentDate = new Date();
    const diffTime = Math.abs(futureDate - currentDate);
    const days = Math.floor(diffTime / (1000 * 60 * 60 * 24));
    const hours = Math.floor(
      (diffTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
    );
    const minutes = Math.floor((diffTime % (1000 * 60 * 60)) / (1000 * 60));
    const seconds = Math.floor((diffTime % (1000 * 60)) / 1000);

    return {
      days,
      hours,
      minutes,
      seconds
    };
  }

  const { days, hours, minutes, seconds } = timeLeft;

  return (
    <div>
      <h1>2024年1月1日倒计时</h1>
      <div>
        {days} days {hours} hours : {minutes} minutes : {seconds} seconds
      </div>
    </div>
  );
};

export default App;

9.图片懒加载

9.1 方式一

let imgs= document.getElementsByTagName(&#34;img&#34;);
let clientHeight = document.documentElement.clientHeight || document.body.clientHeight;

function lazyLoad() {
     Array.from(imgs).forEach(function (img) {
        let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
        let height = clientHeight + scrollTop - img.offsetTop;
        if (height > 0 ) {
            img.src = img[data-src];
        }
    })
}

window.onscroll = lazyLoad

9.2 方式二

let imgs= document.getElementsByTagName(&#34;img&#34;);
let clientHeight = document.documentElement.clientHeight || document.body.clientHeight;

function isIn(e) {
    let bound =  e.getBoundingClientRect();
    return bound.top < clientHeight;
}

function lazyLoad() {
    Array.from(imgs).forEach(function (img) {
        if (isIn(img)) {
            img.src = img[data-src];
        }
    })
}

window.onscroll = lazyLoad

10.控制请求并发数

10.1 方式一

function sendRequest(requests, limits, callBack) {
    // 定义执行队列,表示所有待执行的任务
    const promises = requests.slice(0);
     // 定义开始时能执行的并发数
    const counterNum = Math.min(promises.length, limits);
    let currentCounterNum = 0; // 当前并发数

     // 启动初次能执行的任务
    function startRunTask() {
        let i = 0;
        while (i < counterNum) {
            i++;
            runTask();
        }
    }

    // 取出任务并推送到执行器
    runTask = () => {
        const task = promises.shift();
        task && runner(task);
    }

    // 执行器,这里去执行任务
    runner = async (task) => {
        try {
            currentCounterNum++;
            await task();
        } catch (error) {
            
        } finally {
            currentCounterNum--;
            pickTask();
        }
    }

    // 捞起下一个任务
    pickTask = () => {
        if (currentCounterNum < counterNum && promises.length > 0) {
            runTask();
        } else if (promises.length === 0 && currentCounterNum === 0) {
            callBack && callBack();
        }
    }

    // 开始执行
    startRunTask();
}

10.2 方式二

async function sendRequest(requestList, limit, callBack) {
    // 维护一个promise队列
    const promises = [];
    // 当前的并发池,用Set结构方便删除
    const pool = new Set();
    // 开始并发执行所有的任务
    for (let request of requestList) {
         // 开始执行前,先await 判断当前的并发任务是否超过限制
        if (pool.size >= limit) {
            await Promise.race(pool)
        }

        const cb = () => {
            pool.delete(request);// 删除请求结束后,从pool里面移除
        }
    
        request.then(cb, cb);
        pool.add(request);
        promises.push(request);
    }

    Promise.allSettled(promises).then(callBack, callBack);
}

测试代码

let requests = [];
for (let i = 0; i < 10; i++) {
    requests.push(new Promise((resolve) => {
        setTimeout(() => resolve(i), 200);
    }))
}

function callBack() {
    console.log('success');
}

sendRequest(requests, 3, callBack);

11.实现bind方法

Function.prototype.myBind = function() {
    var thatFunc = this; //存外部this
    var thatArg = arguments[0]; //获取上下文
    var args = Array.prototype.slice.call(arguments, 1);
    // 判断调用对象是否为函数
    if (typeof thatFunc != 'function') {
        throw new TypeError(&#34;caller must be function&#34;);
    }
    var fBound = function() {
        return thatFunc.apply(this instanceof fBound ? 
            this : thatArg, args.concat(Array.prototype.slice.call(arguments)));
    }
    var fNOP = function () {};
    fNOP.prototype = thatFunc.prototype; 
    fBound.prototype = new fNOP();//继承构造函数原型
    return fBound;
}

测试代码

var obj = { name:&#34;xiao ming&#34; };
var greeting = function(site, lang){
    this.value = 'cheering';
    console.log(&#34;Welcome &#34; + this.name + &#34; to &#34; + site +&#34; learning &#34; + lang);
};
var objGreeting = greeting.myBind(obj, 'juejin'); 
objGreeting(&#34;JS&#34;)
//var newObj = new objGreeting('JS');
console.log(newObj.value);

12.版本号排序

arr.sort((a, b) => {
    const arr1 = a.split('.').map(Number);
    const arr2 = b.split('.').map(Number);

    const maxLen = Math.max(arr1.length, arr2.length);

    for (let i = 0; i < maxLen; i++) {
        const n1 = arr1[i] ?? 0;
        const n2 = arr2[i] ?? 0;

        if (n1 !== n2) {
            return n2 - n1; // 降序
        }
    }
    return 0;
});

13.获取网络文件并下载到本地

const getBlob = (url) => {
    return new Promise((resolve) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.responseType = &#34;blob&#34;;
        xhr.onload = () => {
            if (xhr.status === 200) {
                resolve(xhr.response);
            }
        }
        xhr.send();
    })
}

const saveAs = (blob, fileName) => {
    let link = document.createElement(&#34;a&#34;);
    link.href = window.URL.createObjectURL(blob);
    link.download = fileName;
    link.click();
}

//测试代码
getBlob(&#34;http://www.example.com/hello.jpg&#34;).then((blob) => saveAs(blob, &#34;hello&#34;))

14. JS保留2位小数

14.1 toFixed()

let num1 = 3.1415926;
let num2 = 3.1455926;
console.log(num1.toFixed(2)); // 3.14
console.log(num2.toFixed(2)); // 3.15

14.2 Math.round()

let num1 = Math.round(3.1415926 * 100) / 100;
let num2 = Math.round(3.1455926 * 100) / 100;
console.log(num1); // 3.14
console.log(num2); // 3.15

14.3 Math.floor()

let num1 = Math.floor(3.1415926 * 100) / 100;
let num2 = Math.floor(3.1455926 * 100) / 100;
console.log(num1); // 3.14
console.log(num2); // 3.14

14.4 正则表达式

let num1 = (3.1415926).toString().match(/^\d+(?:.\d{0,2})?/);
let num2 = (3.1455926).toString().match(/^\d+(?:.\d{0,2})?/);
console.log(num1[0]); // 3.14
console.log(num2[0]); // 3.14

15. 扁平数据结构转Tree

如下数据结构:

let arr = [
    {id: 1, name: '部门1', pid: 0},
    {id: 2, name: '部门2', pid: 1},
    {id: 3, name: '部门3', pid: 1},
    {id: 4, name: '部门4', pid: 3},
    {id: 5, name: '部门5', pid: 4},
]

输出结果:

[
    {
        &#34;id&#34;: 1,
        &#34;name&#34;: &#34;部门1&#34;,
        &#34;pid&#34;: 0,
        &#34;children&#34;: [
            {
                &#34;id&#34;: 2,
                &#34;name&#34;: &#34;部门2&#34;,
                &#34;pid&#34;: 1,
                &#34;children&#34;: []
            },
            {
                &#34;id&#34;: 3,
                &#34;name&#34;: &#34;部门3&#34;,
                &#34;pid&#34;: 1,
                &#34;children&#34;: [
                    // 结果 ,,,
                ]
            }
        ]
    }
]

15.1 递归遍历查找

/**
 * 递归查找,获取children
 */
const getChildren = (data, result, pid) => {
  for (const item of data) {
    if (item.pid === pid) {
      const newItem = {...item, children: []};
      result.push(newItem);
      getChildren(data, newItem.children, item.id);
    }
  }
}

/**
* 转换方法
*/
const arrayToTree = (data, pid) => {
  const result = [];
  getChildren(data, result, pid)
  return result;
}

15.2 数据转Map

function arrayToTree(items) {
  const result = [];   // 存放结果集
  const itemMap = {};  
    
  // 先转成map存储
  for (const item of items) {
    itemMap[item.id] = {...item, children: []}
  }
  
  for (const item of items) {
    const id = item.id;
    const pid = item.pid;
    const treeItem =  itemMap[id];
    if (pid === 0) {
      result.push(treeItem);
    } else {
      if (!itemMap[pid]) {
        itemMap[pid] = {
          children: [],
        }
      }
      itemMap[pid].children.push(treeItem)
    }
  }
  return result;
}

15.3 数据转Map优化

function arrayToTree(items) {
  const result = [];   // 存放结果集
  const itemMap = {}; 
  for (const item of items) {
    const id = item.id;
    const pid = item.pid;

    if (!itemMap[id]) {
      itemMap[id] = {
        children: [],
      }
    }

    itemMap[id] = {
      ...item,
      children: itemMap[id]['children']
    }

    const treeItem =  itemMap[id];
    if (pid === 0) {
      result.push(treeItem);
    } else {
      if (!itemMap[pid]) {
        itemMap[pid] = {
          children: [],
        }
      }
      itemMap[pid].children.push(treeItem)
    }
  }
  return result;
}

其他

1.相同的前端代码部署后在http和https中表现有什么不一样? 
  https可以请求servicework,调用http资源,摄像头和地理位置等
2.前端监控是怎么做的?
  监控时机:页面加载/运行时(js执行,页面重绘重排,动画帧率)/错误异常(js,promise,资源,接口请求) 
  监控方式:Sentry, Performance,WebVitals 
  监控指标:FCP/LCP/CLS/FID/NIP/TBT
3.servicework的生命周期 
 注册/安装/等待/激活/运行/终止
4.做过Next项目吗? SSR SSG ISR 使用场景,性能优化
5.react渲染对象遇到性能问题怎么办?
  使用immer
6.vit原理
  1.内置服务器拦截http请求 import createApp from 'vue'; node_modules路径转移 import createApp from '/@modules/vue';
  2.import *.vue替换 es-module-lexer分析import
  3.核心编译技巧 ESBuild+ESModule+按需 
  4.热更新原理websocket