Skip to content

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

点击上方 前端瓶子君,关注公众号

回复算法,加入前端编程面试算法每日一题群

🎄 前言

本文主要总结了 2021 年前端提前批和秋招所考察的手写题,题目来源于牛客网前端面经区,统计时间自 3 月初至 10 月底,面经来源于阿里、腾讯、百度、字节、美团、京东、快手、拼多多等 15 家公司,并做了简单的频次划分。

  • ⭐⭐⭐⭐⭐: 在 15 家公司面试中出现 10+

  • ⭐⭐⭐⭐:在 15 家公式面试中出现 5-10

  • ⭐⭐⭐:在 15 家公司面试中出现 3-5

  • 无星:出现 1-2

题目解析一部分来源于小包的编写,另一部分如果我感觉题目扩展开来更好的话,我就选取部分大佬的博客链接。

🌟 promise

实现 promise

考察频率: (⭐⭐⭐⭐⭐)

参考代码 [1]

实现 promise.all

考察频率: (⭐⭐⭐⭐⭐)

function PromiseAll(promises){    return new Promise((resolve, reject)=>{        if(!Array.isArray(promises)){            throw new TypeError("promises must be an array")        }        let result = []         let count = 0         promises.forEach((promise, index) => {            promise.then((res)=>{                result[index] = res                count++                count === promises.length && resolve(result)             }, (err)=>{                reject(err)            })        })    })}复制代码

实现 promise.finally

考察频率: (⭐⭐⭐⭐⭐)

Promise.prototype.finally = function (cb) {  return this.then(function (value) {    return Promise.resolve(cb()).then(function () {      return value    })  }, function (err) {    return Promise.resolve(cb()).then(function () {      throw err    })  })}复制代码

实现 promise.allSettled

考察频率: (⭐⭐⭐⭐)

function allSettled(promises) {  if (promises.length === 0) return Promise.resolve([])    const _promises = promises.map(    item => item instanceof Promise ? item : Promise.resolve(item)    )    return new Promise((resolve, reject) => {    const result = []    let unSettledPromiseCount = _promises.length        _promises.forEach((promise, index) => {      promise.then((value) => {        result[index] = {          status: 'fulfilled',          value        }                unSettledPromiseCount -= 1        // resolve after all are settled        if (unSettledPromiseCount === 0) {          resolve(result)        }      }, (reason) => {        result[index] = {          status: 'rejected',          reason        }                unSettledPromiseCount -= 1        // resolve after all are settled        if (unSettledPromiseCount === 0) {          resolve(result)        }      })    })  })}复制代码

实现 promise.race

考察频率: (⭐⭐⭐)

Promise.race = function(promiseArr) {    return new Promise((resolve, reject) => {        promiseArr.forEach(p => {            Promise.resolve(p).then(val => {                resolve(val)            }, err => {                rejecte(err)            })        })    })}复制代码

来说一下如何串行执行多个 Promise

参考代码 [2]

promise.any

Promise.any = function(promiseArr) {    let index = 0    return new Promise((resolve, reject) => {        if (promiseArr.length === 0) return         promiseArr.forEach((p, i) => {            Promise.resolve(p).then(val => {                resolve(val)                            }, err => {                index++                if (index === promiseArr.length) {                  reject(new AggregateError('All promises were rejected'))                }            })        })    })}复制代码

resolve

Promise.resolve = function(value) {    if(value instanceof Promise){        return value    }    return new Promise(resolve => resolve(value))}复制代码

reject

Promise.reject = function(reason) {    return new Promise((resolve, reject) => reject(reason))}复制代码

🐳 Array 篇

数组去重

考察频率: (⭐⭐⭐⭐⭐)

使用双重 forsplice

function unique(arr){                for(var i=0; i<arr.length; i++){        for(var j=i+1; j<arr.length; j++){            if(arr[i]==arr[j]){                     //第一个等同于第二个,splice方法删除第二个                arr.splice(j,1);                // 删除后注意回调j                j--;            }        }    }return arr;}复制代码

使用 indexOfincludes 加新数组

//使用indexoffunction unique(arr) {    var uniqueArr = []; // 新数组    for (let i = 0; i < arr.length; i++) {        if (uniqueArr.indexOf(arr[i]) === -1) {            //indexof返回-1表示在新数组中不存在该元素            uniqueArr.push(arr[i])//是新数组里没有的元素就push入        }    }    return uniqueArr;}// 使用includesfunction unique(arr) {    var uniqueArr = [];     for (let i = 0; i < arr.length; i++) {        //includes 检测数组是否有某个值        if (!uniqueArr.includes(arr[i])) {            uniqueArr.push(arr[i])//        }    }    return uniqueArr;}复制代码

sort 排序后,使用快慢指针的思想

function unique(arr) {    arr.sort((a, b) => a - b);    var slow = 1,        fast = 1;    while (fast < arr.length) {        if (arr[fast] != arr[fast - 1]) {            arr[slow ++] = arr[fast];        }        ++ fast;    }    arr.length = slow;    return arr;}复制代码

sort 方法用于从小到大排序 (返回一个新数组),其参数中不带以上回调函数就会在两位数及以上时出现排序错误 (如果省略,元素按照转换为的字符串的各个字符的 Unicode 位点进行排序。两位数会变为长度为二的字符串来计算)。

ES6 提供的 Set 去重

function unique(arr) {    const result = new Set(arr);    return [...result];    //使用扩展运算符将Set数据结构转为数组}复制代码

Set 中的元素只会出现一次,即 Set 中的元素是唯一的。

使用哈希表存储元素是否出现 (ES6 提供的 map)

function unique(arr) {    let map = new Map();    let uniqueArr = new Array();  // 数组用于返回结果    for (let i = 0; i < arr.length; i++) {      if(map.has(arr[i])) {  // 如果有该key值        map.set(arr[i], true);       } else {         map.set(arr[i], false);   // 如果没有该key值        uniqueArr.push(arr[i]);      }    }     return uniqueArr ;}复制代码

map 对象保存键值对,与对象类似。但 map 的键可以是任意类型,对象的键只能是字符串类型。

如果数组中只有数字也可以使用普通对象作为哈希表。

filter 配合 indexOf

function unique(arr) {    return arr.filter(function (item, index, arr) {        //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素        //不是那么就证明是重复项,就舍弃        return arr.indexOf(item) === index;    })}复制代码

这里有可能存在疑问,我来举个例子:

const arr = [1,1,2,1,3]arr.indexOf(arr[0]) === 0 // 1 的第一次出现arr.indexOf(arr[1]) !== 1 // 说明前面曾经出现过1复制代码

reduce 配合 includes

function unique(arr){    let uniqueArr = arr.reduce((acc,cur)=>{        if(!acc.includes(cur)){            acc.push(cur);        }        return acc;    },[]) // []作为回调函数的第一个参数的初始值    return uniqueArr}复制代码

数组扁平化

考察频率: (⭐⭐⭐)

参考代码 [3]

forEach

考察频率: (⭐⭐⭐)

Array.prototype.myForEach = function (callbackFn) {    // 判断this是否合法    if (this === null || this === undefined) {        throw new TypeError("Cannot read property 'myForEach' of null");    }    // 判断callbackFn是否合法    if (Object.prototype.toString.call(callbackFn) !== "[object Function]") {        throw new TypeError(callbackFn + ' is not a function')    }    // 取到执行方法的数组对象和传入的this对象    var _arr = this, thisArg = arguments[1] || window;    for (var i = 0; i < _arr.length; i++) {        // 执行回调函数        callbackFn.call(thisArg, _arr[i], i, _arr);    }}复制代码

reduce

考察频率: (⭐⭐⭐)

Array.prototype.myReduce = function(callbackFn) {    var _arr = this, accumulator = arguments[1];    var i = 0;    // 判断是否传入初始值    if (accumulator === undefined) {        // 没有初始值的空数组调用reduce会报错        if (_arr.length === 0) {            throw new Error('initVal and Array.length>0 need one')        }        // 初始值赋值为数组第一个元素        accumulator = _arr[i];        i++;    }    for (; i<_arr.length; i++) {        // 计算结果赋值给初始值        accumulator = callbackFn(accumulator,  _arr[i], i, _arr)    }    return accumulator;}复制代码

map

Array.prototype.myMap = function(callbackFn) {    var _arr = this, thisArg = arguments[1] || window, res = [];    for (var i = 0; i<_arr.length; i++) {        // 存储运算结果        res.push(callbackFn.call(thisArg, _arr[i], i, _arr));    }    return res;}复制代码

filter

Array.prototype.myFilter = function(callbackFn) {    var _arr = this, thisArg = arguments[1] || window, res = [];    for (var i = 0; i<_arr.length; i++) {        // 回调函数执行为true        if (callbackFn.call(thisArg, _arr[i], i, _arr)) {            res.push(_arr[i]);        }    }    return res;}复制代码

every

Array.prototype.myEvery = function(callbackFn) {    var _arr = this, thisArg = arguments[1] || window;    // 开始标识值为true    // 遇到回调返回false,直接返回false    // 如果循环执行完毕,意味着所有回调返回值为true,最终结果为true    var flag = true;    for (var i = 0; i<_arr.length; i++) {        // 回调函数执行为false,函数中断        if (!callbackFn.call(thisArg, _arr[i], i, _arr)) {            return false;        }    }    return flag;}复制代码

some

Array.prototype.mySome = function(callbackFn) {    var _arr = this, thisArg = arguments[1] || window;    // 开始标识值为false    // 遇到回调返回true,直接返回true    // 如果循环执行完毕,意味着所有回调返回值为false,最终结果为false    var flag = false;    for (var i = 0; i<_arr.length; i++) {        // 回调函数执行为false,函数中断        if (callbackFn.call(thisArg, _arr[i], i, _arr)) {            return true;        }    }    return flag;}复制代码

find/findIndex

Array.prototype.myFind = function(callbackFn) {    var _arr = this, thisArg = arguments[1] || window;    // 遇到回调返回true,直接返回该数组元素    // 如果循环执行完毕,意味着所有回调返回值为false,最终结果为undefined    for (var i = 0; i<_arr.length; i++) {        // 回调函数执行为false,函数中断        if (callbackFn.call(thisArg, _arr[i], i, _arr)) {            return _arr[i];        }    }    return undefined;}复制代码

indexOf

function indexOf(findVal, beginIndex = 0) {    if (this.length < 1 || beginIndex > findVal.length) {        return -1;    }    if (!findVal) {        return 0;    }    beginIndex = beginIndex <= 0 ? 0 : beginIndex;    for (let i = beginIndex; i < this.length; i++) {        if (this[i] == findVal) return i;    }    return -1;}      复制代码

实现 sort

参考代码 [4]

🌊 防抖节流

实现防抖函数 debounce

考察频率: (⭐⭐⭐⭐⭐)

function debounce(func, wait, immediate) {    var timeout, result;    var debounced = function () {        var context = this;        var args = arguments;        if (timeout) clearTimeout(timeout);        if (immediate) {            // 如果已经执行过,不再执行            var callNow = !timeout;            timeout = setTimeout(function(){                timeout = null;            }, wait)            if (callNow) result = func.apply(context, args)        }        else {            timeout = setTimeout(function(){                result = func.apply(context, args)            }, wait);        }        return result;    };    debounced.cancel = function() {        clearTimeout(timeout);        timeout = null;    };    return debounced;}复制代码

实现节流函数 throttle

考察频率: (⭐⭐⭐⭐⭐)

// 第四版function throttle(func, wait, options) {    var timeout, context, args, result;    var previous = 0;    if (!options) options = {};    var later = function() {        previous = options.leading === false ? 0 : new Date().getTime();        timeout = null;        func.apply(context, args);        if (!timeout) context = args = null;    };    var throttled = function() {        var now = new Date().getTime();        if (!previous && options.leading === false) previous = now;        var remaining = wait - (now - previous);        context = this;        args = arguments;        if (remaining <= 0 || remaining > wait) {            if (timeout) {                clearTimeout(timeout);                timeout = null;            }            previous = now;            func.apply(context, args);            if (!timeout) context = args = null;        } else if (!timeout && options.trailing !== false) {            timeout = setTimeout(later, remaining);        }    };    return throttled;}复制代码

⛲ Object 篇

能不能写一个完整的深拷贝

考察频率: (⭐⭐⭐⭐⭐)

const getType = obj => Object.prototype.toString.call(obj);const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;const canTraverse = {  '[object Map]': true,  '[object Set]': true,  '[object Array]': true,  '[object Object]': true,  '[object Arguments]': true,};const mapTag = '[object Map]';const setTag = '[object Set]';const boolTag = '[object Boolean]';const numberTag = '[object Number]';const stringTag = '[object String]';const symbolTag = '[object Symbol]';const dateTag = '[object Date]';const errorTag = '[object Error]';const regexpTag = '[object RegExp]';const funcTag = '[object Function]';const handleRegExp = (target) => {  const { source, flags } = target;  return new target.constructor(source, flags);}const handleFunc = (func) => {  // 箭头函数直接返回自身  if(!func.prototype) return func;  const bodyReg = /(?<={)(.|\n)+(?=})/m;  const paramReg = /(?<=\().+(?=\)\s+{)/;  const funcString = func.toString();  // 分别匹配 函数参数 和 函数体  const param = paramReg.exec(funcString);  const body = bodyReg.exec(funcString);  if(!body) return null;  if (param) {    const paramArr = param[0].split(',');    return new Function(...paramArr, body[0]);  } else {    return new Function(body[0]);  }}const handleNotTraverse = (target, tag) => {  const Ctor = target.constructor;  switch(tag) {    case boolTag:      return new Object(Boolean.prototype.valueOf.call(target));    case numberTag:      return new Object(Number.prototype.valueOf.call(target));    case stringTag:      return new Object(String.prototype.valueOf.call(target));    case symbolTag:      return new Object(Symbol.prototype.valueOf.call(target));    case errorTag:     case dateTag:      return new Ctor(target);    case regexpTag:      return handleRegExp(target);    case funcTag:      return handleFunc(target);    default:      return new Ctor(target);  }}const deepClone = (target, map = new WeakMap()) => {  if(!isObject(target))     return target;  let type = getType(target);  let cloneTarget;  if(!canTraverse[type]) {    // 处理不能遍历的对象    return handleNotTraverse(target, type);  }else {    // 这波操作相当关键,可以保证对象的原型不丢失!    let ctor = target.constructor;    cloneTarget = new ctor();  }  if(map.get(target))     return target;  map.set(target, true);  if(type === mapTag) {    //处理Map    target.forEach((item, key) => {      cloneTarget.set(deepClone(key, map), deepClone(item, map));    })  }    if(type === setTag) {    //处理Set    target.forEach(item => {      cloneTarget.add(deepClone(item, map));    })  }  // 处理数组和对象  for (let prop in target) {    if (target.hasOwnProperty(prop)) {        cloneTarget[prop] = deepClone(target[prop], map);    }  }  return cloneTarget;}复制代码

参考博客 [5]

实现 new

考察频率: (⭐⭐⭐⭐)

function createObject(Con) {    // 创建新对象obj    // var obj = {};也可以    var obj = Object.create(null);    // 将obj.__proto__ -> 构造函数原型    // (不推荐)obj.__proto__ = Con.prototype    Object.setPrototypeOf(obj, Con.prototype);    // 执行构造函数,并接受构造函数返回值    const ret = Con.apply(obj, [].slice.call(arguments, 1));    // 若构造函数返回值为对象,直接返回该对象    // 否则返回obj    return typeof(ret) === 'object' ? ret: obj;}复制代码

继承

考察频率: (⭐⭐⭐⭐)

原型链继承

借用构造函数 (经典继承)

组合继承

原型式继承

寄生式继承

寄生组合式继承

Class 实现继承 (补充一下)

class Animal {    constructor(name) {        this.name = name    }     getName() {        return this.name    }}class Dog extends Animal {    constructor(name, age) {        super(name)        this.age = age    }}复制代码

参考代码 [6]

实现 object.create

function newCreate(proto, propertiesObject) {    if (typeof proto !== 'object' && typeof proto !== 'function') {        throw TypeError('Object prototype may only be an Object: ' + proto)    }    function F() { }    F.prototype = proto    const o = new F()    if (propertiesObject !== undefined) {        Object.keys(propertiesObject).forEach(prop => {            let desc = propertiesObject[prop]            if (typeof desc !== 'object' || desc === null) {                throw TypeError('Object prorotype may only be an Object: ' + desc)            } else {                Object.defineProperty(o, prop, desc)            }        })    }    return o}复制代码

🚂 Function 篇

call

考察频率: (⭐⭐⭐⭐)

Function.prototype.myCall = function (thisArg) {    thisArg = thisArg || window;    thisArg.func = this;    const args = []    for (let i = 1; i<arguments.length; i++) {        args.push('arguments['+ i + ']')    }    const result = eval('thisArg.func(' + args +')')    delete thisArg.func;    return result;}复制代码

bind

考察频率: (⭐⭐⭐⭐)

Function.prototype.sx_bind = function (obj, ...args) {    obj = obj || window    const fn = Symbol()    obj[fn] = this    const _this = this    const res = function (...innerArgs) {        console.log(this, _this)        if (this instanceof _this) {            this[fn] = _this            this[fn](...[...args, ...innerArgs])            delete this[fn]        } else {            obj[fn](...[...args, ...innerArgs])            delete obj[fn]        }    }    res.prototype = Object.create(this.prototype)    return res}复制代码

apply

考察频率: (⭐⭐⭐⭐)

Function.prototype.myApply = function (thisArg, arr) {    thisArg = thisArg || window;    thisArg.func = this;    const args = []    for (let i = 0; i<arr.length; i++) {        args.push('arr['+ i + ']')    }    const result = eval('thisArg.func(' + args +')')    delete thisArg.func;    return result;}复制代码

实现柯里化

考察频率: (⭐⭐⭐)

参考代码 [7]

实现链式调用

参考代码 [8]

偏函数

参考代码 [9]

🌍 ajax 与 jsonp

考察频率: (⭐⭐⭐)

实现 ajax

function ajax({    url= null, method = 'GET', dataType = 'JSON', async = true}){ return new Promise((resolve, reject) => {  let xhr = new XMLHttpRequest()  xhr.open(method, url, async)  xhr.responseType = dataType  xhr.onreadystatechange = () => {   if(!/^[23]\d{2}$/.test(xhr.status)) return;   if(xhr.readyState === 4) {    let result = xhr.responseText    resolve(result)   }  }  xhr.onerror = (err) => {   reject(err)  }  xhr.send() })}复制代码

实现 jsonp

const jsonp = ({ url, params, callbackName }) => {    const generateUrl = () => {        let dataSrc = ''        for (let key in params) {            if (params.hasOwnProperty(key)) {                dataSrc += `${key}=${params[key]}&`            }        }        dataSrc += `callback=${callbackName}`        return `${url}?${dataSrc}`    }    return new Promise((resolve, reject) => {        const scriptEle = document.createElement('script')        scriptEle.src = generateUrl()        document.body.appendChild(scriptEle)        window[callbackName] = data => {            resolve(data)            document.removeChild(scriptEle)        }    })}复制代码

🛫 ES6 篇

实现 set

class Set {  constructor() {    this.items = {};    this.size = 0;  }  has(element) {    return element in this.items;  }  add(element) {    if(! this.has(element)) {      this.items[element] = element;      this.size++;    }    return this;  }  delete(element) {    if (this.has(element)) {      delete this.items[element];      this.size--;    }    return this;  }  clear() {    this.items = {}    this.size = 0;  }  values() {    let values = [];    for(let key in this.items) {      if(this.items.hasOwnProperty(key)) {        values.push(key);      }    }    return values;  }}复制代码

实现 map

function defaultToString(key) {  if(key === null) {    return 'NULL';  } else if (key === undefined) {    return 'UNDEFINED'  } else if (Object.prototype.toString.call(key) === '[object Object]' || Object.prototype.toString.call(key) === '[object Array]') {    return JSON.stringify(key);  }  return key.toString();}class Map {  constructor() {    this.items = {};    this.size = 0;  }  set(key, value) {    if(!this.has(key)) {      this.items[defaultToString(key)] = value;      this.size++;    }    return this;  }  get(key) {    return this.items[defaultToString(key)];  }  has(key) {    return this.items[defaultToString(key)] !== undefined;  }  delete(key) {    if (this.has(key)) {      delete this.items[key];      this.size--;    }    return this;  }  clear() {    this.items = {}    this.size = 0;  }  keys() {    let keys = [];    for(let key in this.items) {      if(this.has(key)) {        keys.push(key)      }    }    return keys;  }  values() {    let values = [];    for(let key in this.items) {      if(this.has(key)) {        values.push(this.items[key]);      }    }    return values;  }}复制代码

实现 es6 的 class

参考代码 [10]

🦉 其他

instanceof

考察频率: (⭐⭐⭐⭐)

function instance_of(Case, Constructor) {    // 基本数据类型返回false    // 兼容一下函数对象    if ((typeof(Case) != 'object' && typeof(Case) != 'function') || Case == 'null') return false;    let CaseProto = Object.getPrototypeOf(Case);    while (true) {        // 查到原型链顶端,仍未查到,返回false        if (CaseProto == null) return false;        // 找到相同的原型        if (CaseProto === Constructor.prototype) return true;        CaseProto = Object.getPrototypeOf(CaseProto);    }}复制代码

实现千分位分隔符

考察频率: (⭐⭐⭐)

var str = "100000000000",    reg = /(?=(\B\d{3})+$)/g;str.replace(reg, ",")复制代码

把一个 JSON 对象的 key 从下划线形式(Pascal)转换到小驼峰形式(Camel)

考察频率: (⭐⭐⭐)

参考代码 [11]

实现数据类型判断函数

function myTypeof(obj) {   return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase() }复制代码

实现数组转树

参考代码 [12]

实现 sleep 函数

// promiseconst sleep = time => {  return new Promise(resolve => setTimeout(resolve,time))}sleep(1000).then(()=>{  console.log(1)})// ES5function sleep(callback,time) {  if(typeof callback === 'function')    setTimeout(callback,time)}function output(){  console.log(1);}sleep(output,1000);复制代码

实现发布订阅模式

class EventEmitter {    constructor() {        this.cache = {}    }    on(name, fn) {        if (this.cache[name]) {            this.cache[name].push(fn)        } else {            this.cache[name] = [fn]        }    }    off(name, fn) {        let tasks = this.cache[name]        if (tasks) {            const index = tasks.findIndex(f => f === fn || f.callback === fn)            if (index >= 0) {                tasks.splice(index, 1)            }        }    }    emit(name, once = false, ...args) {        if (this.cache[name]) {            // 创建副本,如果回调函数内继续注册相同事件,会造成死循环            let tasks = this.cache[name].slice()            for (let fn of tasks) {                fn(...args)            }            if (once) {                delete this.cache[name]            }        }    }}复制代码

🛕 更多题目

传送门: 前端题目 [13]

如果感觉有帮助的话,别忘了给小包点个 ⭐ 。

💘 往期精彩文章

  • 牛客最新前端 JS 笔试百题 [14]

  • 牛客最新前端面经面试题汇总 (含解析 )[15]

  • 抓取牛客最新前端面试题五百道 数据分析 JS 面试热点 [16]

  • 给 VSCode 和网站领养喵咪 一起快乐撸猫 [17]

  • 原生 JavaScript 灵魂拷问 (一 ),你能答上多少?[18]

  • JavaScript 之彻底理解原型与原型链 [19]

  • JavaScript 之彻底理解 EventLoop[20]

  • 《2w 字大章 38 道面试题》彻底理清 JS 中 this 指向问题 [21]

💥 后语

伙伴们,如果大家感觉本文对你有一些帮助,给阿包点一个赞👍或者关注➕都是对我最大的支持。

另外如果本文章有问题,或者对文章其中一部分不理解,都可以评论区回复我,我们来一起讨论,共同学习,一起进步!

关于本文

来源:战场小包

https://juejin.cn/post/7033275515880341512

最后

欢迎关注【前端瓶子君】✿✿ヽ (°▽°) ノ✿

回复「算法」,加入前端编程源码算法群,每日一道面试题(工作日),第二天瓶子君都会很认真的解答哟!

回复「交流」,吹吹水、聊聊技术、吐吐槽!

回复「阅读」,每日刷刷高质量好文!

如果这篇文章对你有帮助,「在看」是最大的支持

》》面试官也在看的算法资料《《

“在看和转发” 就是最大的支持