天天看點

2021前端高頻面試題整理,附答案

大家好,我是若川。

廢話不多說,上貨!

手寫Promise

這道題說實話已經老掉牙了,但是還是有不少公司會問,而且還有相當一部分前端寫不出來
function Promise(callback){
    const pending = 'pending';
    const fulfilled = 'fulfilled';
    const rejected = 'rejected';
    // 目前狀态
    this.state = pending;
    // 目前值
    this.value = null;
    // 失敗原因
    this.reason = null;
    // 成功和失敗數組對象
    this.fulfilledCallback = [];
    this.rejectedCallback = [];

    // 成功處理
    this.resolve = (data)=>{
        setTimeout(()=>{
            if(this.state == pending){
                this.state = fulfilled;
                this.value = data;
                this.fulfilledCallback.map(fn=>fn(this.value));
            }
        })
    }
    // 失敗處理
    this.reject = (reason)=>{
        setTimeout(()=>{
            if(this.state == pending){
                this.state = rejected;
                this.reason = reason;
                this.rejectedCallback.map(fn=>fn(this.reason));
            }
        })
    }
    // 捕獲成功和失敗,扔到成功和失敗數組
    this.then = function(succesFn,errorFn){
        this.fulfilledCallback.push(succesFn);
        this.rejectedCallback.push(errorFn);
    }
    // 捕獲異常,直接扔到異常數組中
    this.catch = (errorFn)=>{
        this.rejectedCallback.push(errorFn);
    }
    // 預設需要執行一次resolve和reject
    callback(this.resolve,this.reject);
}

// 驗證結果
new Promise((resolve,reject)=>{
    setTimeout(()=>{resolve(10);},1000)
}).then((data)=>{
    console.log(data);
},(error)=>{
    console.log(error);
})      

注意事項:

  • Promise要暴露then/catch方法
  • Promise構造函數接收一個立即執行的函數callback
  • then/catch隻負責把回調放入數組即可
  • resolve/reject負責執行
  • resolve/reject 需要添加宏任務(setTimeout)

compose組合函數實作

  1. 後一個函數作為前一個函數的參數
  2. 最後一個函數可以接受多個參數,前面的函數隻能接受單個參數;後一個的傳回值傳給前一個
// Demo:
const add = num num  + 10
const multiply = num num * 2
const foo = compose(multiply, add)
foo(5) => 30      
// 聚合函數
export default function compose(...funcs) {
  // 如果是空,直接傳回空函數并接受一個參數
  if (funcs.length === 0) {
    return arg arg
  }
  // 如果有一個,直接執行并傳回結果
  if (funcs.length === 1) {
    return funcs[0]
  }
  // 如果對reduce不了解,可以先去看下技術文章
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}      

注意事項:

  • compose是一個聚合函數
  • compose執行後傳回一個函數(是以這就是為什麼當func.length==0的時候,要return一個箭頭函數)
  • reduce始終傳回一個箭頭函數,後一個函數先執行并把結果作為前一個函數的參數,依次進行

數組柯裡化Curry(求和)

阿裡面試題
實作如下效果:
sum(1,3).sumOf()  4
sum(1,3)(2,4).sumOf() 10      
function sum(){
    var arr = [].slice.apply(arguments);
    var fn = function(){
        arr = arr.concat([].slice.apply(arguments))
        return fn;
    }
    fn.sumOf = function(){
        return  arr.reduce((total,num)=>total+num,0);
    }
    return fn;
}      

實作一個LazyPig

阿裡面試題

實作一個LazyPig,可以按照以下方式調用:

LazyPig("Peggy") 
// 輸出:
> Hello,I'm Peggy!
LazyPig("Peggy").sleep(10).eat("dinner")
// 輸出
> Hello,I'm Peggy!
//等待10秒..
Wake up after 10
Eat dinner~      
function LazyPig(name){
    console.log(`Hello,I'm ${name}`)
    var fn = {}
    fn.sleep = function(time){
        console.log(`Wake up ${time}`)
        let start = Date.now()
        while(Date.now()-start<=time){}
        console.log(`Wake up down`)
        return fn;
    }
    fn.eat = function(food){
        console.log(`Eat ${food}`)
        return fn;
    }
    return fn;
}      

數組扁平化

let list = [1, 5, [9, 8], [2, [1, 9]], 7];
// 第一種方法:
console.log(list.toString().split(','));

// 第二種方法:
function flatten(list) {
    return list.reduce((prev, item) => {
        return prev.concat(Array.isArray(item) ? flatten(item) : item);
    }, [])
}
console.log(flatten(list));      

對象扁平化

/**
* 對象扁平化
* 說明:請實作 flatten(input) 函數,input 為一個 javascript 對象(Object 或者 Array),傳回值為扁平化後的結果。
* 示例:
* var input = {
* a: 1,
* b: [ 1, 2, { c: true }, [ 3 ] ],
* d: { e: 2, f: 3 },
* g: null,
* }
* var output = flatten(input);
* output如下
* {
* "a": 1,
* "b[0]": 1,
* "b[1]": 2,
* "b[2].c": true,
* "b[3][0]": 3,
* "d.e": 2,
* "d.f": 3,
* // "g": null, 值為null或者undefined,丢棄
* }
*/
解答:
```js
let result = {};
var flatten = (data, key) => {
    if (data instanceof Array) {
        data.forEach((param, index) => {
            if (param instanceof Object) {
                flatten(param, `${key}[${index}]`);
            } else {
                result[`${key}[${index}]`] = param;
            }
        });
    } else if (data instanceof Object) {
        for (var itemKey in data) {
            const itemValue = data[itemKey];
            if (itemValue instanceof Object) {
                flatten(itemValue, itemKey);
            } else if (itemValue) {
                if (key) {
                    result[`${key}.${itemKey}`] = flatten(itemValue, itemKey);
                } else {
                    result[itemKey] = itemValue;
                }
            }
        }
    } else {
        return data;
    }
};
flatten(input);
console.log(result)      

數組轉換為Tree

// 數組轉換成Tree
var list = [
    {
        id: 1, name: 'jack', pid: 0
    },
    {
        id: 2, name: 'jack', pid: 1
    },
    {
        id: 3, name: 'jack', pid: 1
    },
    {
        id: 4, name: 'jack', pid: 2
    },
]    

const getTree = (root, result, pid) => {
    for (let i = 0; i < root.length; i++) {
        if (root[i].pid == pid) {
            let item = { ...root[i], children: [] }
            result.push(item)
            getTree(root, item.children, item.id)
        }
    }
}

let array = [];
getTree(list, array, 0)
console.log(JSON.stringify(array))      

對象深拷貝

// 對象深度克隆
let obj = {
    name: 'jack',
    age: 10,
    fn: () {
        return this.name;
    },
    list: ['fe', 'node', 'small'],
    all: {
        child: true
    }
}
// 方法一:(面試官不想要)
JSON.parse(JSON.stringify(obj))
// 方法二:
function deepClone(obj){
    let result;
    if(typeof obj === 'object'){
        result = Array.isArray(obj) ? [] : {}
        for(let i in obj){
            result[i] = typeof obj[i] === 'object' ? deepClone(obj[i]):obj[i];
        }
    }else{
        result = obj;
    }
    return result;
}      

貪心算法(找零)

商店老闆有1、2、5、10面額的紙币,小夥買東西給了100花了80,計算如何找零是最佳(阿裡面試題)

function MinCoinChange(coins) {
  return function(amount) {
    let total = 0, change = []
    for(let i= coins.length; i>=0; i--) {
      let coin = coins[i]
      while(total + coin <= amount) {
        change.push(coin)
        total += coin
      }
    }
    return change
  }
}

MinCoinChange([1,2,5,10])(20)

傳回:10,10      

數組去重(兩次以上去重)

正常去重大家都知道Set
// 已知數組
var arr = [1,1,1,1,1,1,1,3,3,3,3,3,5,5];
// 方法一
function delRepeat(){
    arr = arr.sort();//先排序
    for(let i=0;i<arr.length;i++){
        if(arr[i] == arr[i+2]){
            arr.splice(i,1);
            i--;
        }
    }
    return arr;
}
// 方法二
function delRepeat(){
    var newArr = [];
    var obj = {};
    arr.map(item=>{
        if(obj[item]){
            obj[item] +=1 ;
        }else{
            obj[item] = 1;
        }
        obj[item]<=2?newArr.push(item):''
    })
    return newArr;
}      

大數相加算法

大數相加有很多考題形式,有整數、小數、平方根等(騰訊考題)

已知:​

​let a = "12345.12123",b="987654.92";​

function sum(a,b){
    let arr1 = a.split(''),arr2 = b.split('');
    let count = 0;
    let arr = [];
    let a1 = a.split('.')[1],b1 = b.split('.')[1];
    let len = a1.length - b1.length;
    if(len>0)arr2.push(’0’.repeat(len))
    if(len<0)arr1.push(’0’.repeat(Math.abs(len)))
    while(arr1.length || arr2.length){
        let m = arr1.pop() || 0,n = arr2.pop() || 0;
        if(m != '.'){
            let num = Number(m) + Number(n) + count;
            if(num > 9){
                count = 1;
                num%=10;
            }else{
                count = 0;
            } 
            arr.unshift(num);
        }else{
            arr.unshift('.');
        }
    }
    if(count>0)arr.unshift(count);
    let res = arr.join('');
    console.log(res);
}      
如果是正整數,BigInt會更友善處理

二叉樹求和

var treenode = {
    value: 1,
    left: {
        value: 2,
        left: {
            value: 4,
        },
        right: {
            value: 5,
            left: {
                value: 7,
            },
            right: {
                value: 8,
            },
        },
    },
    right: {
        value: 3,
        right: {
            value: 6,
        },
    },
}

function sum(root) {
    let list = []
    if (root) list.push(root.value);
    if (root.left) {
        list = list.concat(sum(root.left));
    }
    if (root.right) {
        list = list.concat(sum(root.right));
    }
    return list;
}
console.log(sum(treenode));      

爬樓梯

/*
假設你正在爬樓梯。需要 n 階你才能到達樓頂。
每次你可以爬 1 或 2 個台階。你有多少種不同的方法可以爬到樓頂呢?
思路:
f(1) : 1
f(2) : 11 , 2
f(3) : 12, 111, 21
f(4) : 121, 1111, 211, 112, 22
f(n) = f(n-1) + f(n-2)
*/
function fn(n) {
    if (n == 1) return 1;
    if (n == 2) return 2;
    return fn(n - 1) + fn(n - 2);
}
console.log(fn(4))      

簡易模闆引擎

const template = '嗨,{{ info.name.value }}您好,今天是星期 {{ day.value }}';

const data = {
    info: {
        name: {
            value: '張三'
        }
    },
    day: {
        value: '三'
    }
};

function render(template, data) {
    return template.replace(/{{\s+?([\w.]+)\s+?}}/g, function ($0, $1) {
        return eval(`data.${$1}`)
    })
}

const result = render(template, data); 
// 嗨,張三您好,今天是星期三
console.log(result)      

前端模拟并發請求

已知目前有100個請求,每次隻能同時調用5個,設計一個并發函數。

function send(){
    // 初始化數組
    let list = Array.from({length:100}).map((k,i)=>i)
    // 最大并發次數
    const limit = 5;
    
    //定義異步函數
    const asyncGet =async (item)=>{
       return item;
    }
    // 初始化100個異步請求函數,當閉包被執行的時候會執行一個請求,當請求執行完後,會擷取下一個并執行
    const arr = []
    const handlers = ()=>{
        list = list.map(item=>{
            return ()=>{
                return asyncGet(item).then((res)=>{
                    console.log('res:'+res)
                    let next = list.shift();
                    if(next){
                        next()
                    }else{
                        console.log('全部執行完成')
                    }
                })
            }
        })
    }
    
    handlers();
    // 一次性取出最大并發數并執行
    for(let i=0;i<limit;i++){
        let fn = list.shift();
        arr.push(fn())
    }
    
    Promise.all(arr).then((res)=>{
        
    })
}

send();      

防抖和節流(最經典的閉包案例)

/*
防抖:
定義:規定時間内,隻觸發一次,如果規定時間内再次調用,會清空繼續建立新的任務。
場景:widow.onresize或者onscroll,或者搜尋框
*/
function debounce(fn, wait) {
    var timeout = null;
    return function() {
        if(timeout !== null){
                clearTimeout(timeout);
                timeout = setTimeout(fn, wait);
        }else{
             timeout = setTimeout(fn, wait);
        }
    }
}
window.addEventListener('scroll', debounce(()=>{
// TO-DO
}, 500));
/*
節流:固定時間内,隻觸發一次。
場景:搜尋框
*/
function throttle(fn,delay){
    let valid = true
    return function() {
       if(!valid){
           //休息時間 暫不接客
           return false 
       }
       // 工作時間,執行函數并且在間隔期内把狀态位設為無效
        valid = false
        setTimeout(() {
            fn()
            valid = true;
        }, delay)
    }
}      

以上就是本次給大家整理分享的前端算法面試題,絕不是從網上随意找的,很多都是我經曆過的以及朋友面試的,希望對大家有幫助。