本文由 简悦 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 篇
数组去重
考察频率: (⭐⭐⭐⭐⭐)
使用双重 for 和 splice
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;}复制代码使用 indexOf 或 includes 加新数组
//使用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
最后
欢迎关注【前端瓶子君】✿✿ヽ (°▽°) ノ✿
回复「算法」,加入前端编程源码算法群,每日一道面试题(工作日),第二天瓶子君都会很认真的解答哟!
回复「交流」,吹吹水、聊聊技术、吐吐槽!
回复「阅读」,每日刷刷高质量好文!
如果这篇文章对你有帮助,「在看」是最大的支持
》》面试官也在看的算法资料《《
“在看和转发” 就是最大的支持