🎄 前言
本文主要总结了 2021 年前端提前批和秋招所考察的手写题,题目来源于牛客网前端面经区,统计时间自 3 月初至 10 月底,面经来源于阿里、腾讯、百度、字节、美团、京东、快手、拼多多等 15 家公司,并做了简单的频次划分。
- ⭐⭐⭐⭐⭐: 在 15 家公司面试中出现 10+
- ⭐⭐⭐⭐:在 15 家公式面试中出现 5-10
- ⭐⭐⭐:在 15 家公司面试中出现 3-5
- 无星:出现 1-2
题目解析一部分来源于小包的编写,另一部分如果我感觉题目扩展开来更好的话,我就选取部分大佬的博客链接。
🌟 promise
实现promise
考察频率: (⭐⭐⭐⭐⭐)
实现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
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
加新数组
//使用indexof function 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; } // 使用includes function 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 } 复制代码
数组扁平化
考察频率: (⭐⭐⭐)
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
🌊 防抖节流
实现防抖函数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; } 复制代码