本篇文章用于总结自己在前端面试的过程中所遇到的手撕题和前端常考的算法题,其主要包括题目与Javascript实现的解法。一部分题目的代码由自己编写实现,还有一部分题目的代码来源互联网,所有代码都经过自己的运行验证,请放心食用。后续还会持续更新。。。
场景手写题 手写debounce 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function debounce (fn,delay=100 ) { let timer = null ; return function ( ) { if (timer){ clearTimeout (timer); } timer = setTimeout (()=> { fn.apply(this ,arguments ); timer = null ; },delay) } } input.addEventListener('keyup' ,debounce(function ( ) { console .log(input.value); },100 ))
手写throttle 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function throttle (fn,delay = 100 ) { let timer = null ; return function ( ) { if (timer){ return ; } timer = setTimeout (()=> { fn.apply(this ,arguments ); timer = null ; },delay) } } div.addEventListener('drag' ,throttle(function (e ) { console .log(e.offsetX,e.offsetY); },100 ))
手写flat 1 2 3 4 5 6 7 8 9 10 11 12 13 function flatDeep (arr,deep = 1 ) { if (arr.length == 0 ) return arr; let result = []; for (let i=0 ;i<arr.length;i++){ if (Array .isArray(arr[i])){ deep>0 ?(result = result.concat(flatDeep(arr[i],deep-1 ))):(result.push(arr[i])) }else { result.push(arr[i]); } } return result; }
手写深拷贝 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 function deepClone (obj = {} ) { if (typeof obj != 'object' || obj == null ){ return obj; } let result; if (obj instanceof Array ){ result = []; }else { result = {}; } for (let key in obj){ if (obj.hasOwnProperty(key)){ result[key] = deepClone(obj[key]); } } return result; } function deepClone (obj = {} ) { if (typeof obj != 'object' || obj == null ){ return obj; } let result; if (obj instanceof Array ){ result = []; }else { result = {}; } for (let key in obj){ if (obj.hasOwnProperty(key)&&obj[key]!=obj){ result[key] = deepClone(obj[key]); }else if (obj[key]===obj){ result[key] = result; } } return result; }
手写call 1 2 3 4 5 6 7 8 9 10 Function .prototype.myCall = function (context ) { context =(context === null || context === undefined ) ? window : context context.fn = this let args = [...arguments].slice(1 ) let result= context.fn(...args) delete context.fn return result }
手写apply 1 2 3 4 5 6 7 Function .prototype.myApply = function (context ) { context =(context === null || context === undefined ) ? window : context; context.fn = this ; let result = arguments [1 ] ? context.fn(...arguments[1 ]) : context.fn() delete context.fn; return result; }
手写bind 1 2 3 4 5 6 7 8 9 10 Function .prototype.myBind = function (context ) { context =(context === null || context === undefined ) ? window : context let o = Object .create(context) o.fn = this let args = [...arguments].slice(1 ) let result= function ( ) { o.fn(...args) } return result }
手写promise.all 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Promise .all = function (arr ) { const values = new Array (arr.length) let resolvedCount = 0 return new Promise ((resolve, reject ) => { arr.forEach((p, index ) => { Promise .resolve(p).then( value => { resolvedCount++ values[index] = value if (resolvedCount===promises.length) { resolve(values) } }, reason => { reject(reason) } ) }) }) }
手写promise.race 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Promise .myRace = function (arr ) { return new Promise ((resolve,reject )=> { arr.forEach((p,index )=> { Promise .resolve(p).then( value=>{ resolve(value) }, reason=>{ reject(reason); } ) }) }) } let p1 = new Promise ((resolve, reject ) => { resolve('ok1' ) }) let p2 = new Promise ((resolve, reject ) => { setTimeout (() => {resolve('ok2' )}, 1000 ) }) let p3 = new Promise ((resolve, reject ) => { resolve('ok3' ) }) Promise .myRace([p2,p1,p3]).then(result => { console .log(result); })
手写promise.resolveDelay 1 2 3 4 5 6 7 8 9 10 11 12 13 Promise .resolveDelay = function (value, time ) { return new Promise ((resolve, reject ) => { setTimeout (() => { if (value instanceof Promise ) { value.then(resolve, reject) } else { resolve(value) } }, time); }) }
手写Promise.rejectDelay 1 2 3 4 5 6 7 Promise .rejectDelay = function (reason, time ) { return new Promise ((resolve, reject ) => { setTimeout (() => { reject(reason); }, time) }) }
手写LazyMan函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const lazyman = function (name ) { console .log(`Hi,你好${name} ` ); return { executeChain: Promise .resolve(), eat, sleep } }; const eat = function (food ) { this .executeChain = this .executeChain.then(() => new Promise (resolve => { console .log(`eat ${food} ` ); resolve(); })) return this ; } const sleep = function (time ) { this .executeChain = this .executeChain.then(() => new Promise (resolve => { setTimeout (() => { resolve(); }, time) })) return this ; }
Promise超时控制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 let rest=function (_data=1000 ) { return Promise .race([ upload(), uploadTimeout(_data) ]).then((value )=> { console .log(value) }) } function upload ( ) { console .log('请求进行中...' ); return new Promise ((resolve,reject )=> { let xhr=new XMLHttpRequest(); xhr.open('GET' ,"https://www.baidu.com" ); xhr.onload=function ( ) { if (xhr.readyState==4 && (xhr.status>=200 && xhr.status<300 )){ setTimeout (()=> { resolve("请求成功!" ) },2000 ) }else { reject(xhr.status) } } xhr.onerror=function ( ) { reject('请求失败了...' ) } xhr.send(null ); }) }; function uploadTimeout (times ) { return new Promise ((resolve,reject )=> { setTimeout (()=> { reject('请求超时,请重试' ); },times) }) }
Promise取消重复请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 function CancelablePromise ( ) { this .pendingPromise = null ; } CancelablePromise.prototype.request = function (requestFn ) { if (this .pendingPromise) { this .cancel("取消重复请求" ); } const _promise = new Promise ((resolve, reject ) => (this .reject = reject)); this .pendingPromise = Promise .race([requestFn(), _promise]); return this .pendingPromise; }; CancelablePromise.prototype.cancel = function (reason ) { this .reject(new Error (reason)); this .pendingPromise = null ; }; function createRequest (delay ) { return () => new Promise ((resolve ) => { setTimeout (() => { resolve("done" ); }, delay); }); } const cancelPromise = new CancelablePromise();for (let i = 0 ; i < 5 ; i++) { cancelPromise .request(createRequest(1000 )) .then((res ) => console .log(res)) .catch((err ) => console .error(err)); } setTimeout (() => { cancelPromise .request(createRequest(1000 )) .then((res ) => console .log(res)) .catch((err ) => console .error(err)); cancelPromise.cancel("手动取消" ); }, 3000 ); setTimeout (() => { cancelPromise .request(createRequest(1000 )) .then((res ) => console .log(res)) .catch((err ) => console .error(err)); }, 4000 );
Promise并发控制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function concurrentRequest (requestFns, limit ) { function recursion (requestFn ) { requestFn().finally(() => { if (_requestFns.length > 0 ) { recursion(_requestFns.pop()); } }); } const _requestFns = [...requestFns]; for (let i = 0 ; i < limit && _requestFns.length > 0 ; i++) { recursion(_requestFns.pop()); } } function createRequest (delay ) { return () => new Promise ((resolve ) => { setTimeout (() => { resolve("done" ); }, delay); }).then((res ) => { console .log(res); }); } const requestFns = [];for (let i = 0 ; i < 10 ; i++) { requestFns.push(createRequest(1000 )); } concurrentRequest(requestFns, 3 );
Promise全局loading态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function PromiseManager ( ) { this .pendingPromise = new Set (); this .loading = false ; } PromiseManager.prototype.generateKey = function ( ) { return `${new Date ().getTime()} -${parseInt (Math .random() * 1000 )} ` ; }; PromiseManager.prototype.push = function (...requestFns ) { for (const requestFn of requestFns) { const key = this .generateKey(); this .pendingPromise.add(key); requestFn().finally(() => { this .pendingPromise.delete(key); this .loading = this .pendingPromise.size !== 0 ; }); } }; function createRequest (delay ) { return () => new Promise ((resolve ) => { setTimeout (() => { resolve("done" ); }, delay); }).then((res ) => console .log(res)); } const manager = new PromiseManager();manager.push(createRequest(1000 )); manager.push(createRequest(4500 )); const id = setInterval (() => { console .log(manager.loading); if (!manager.loading) clearInterval (id); }, 1000 );
手写柯里化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function Curry (fn ) { var arr = []; return function ( ) { if (arguments .length>0 ){ arr = arr.concat(Array .from(arguments )); return arguments .callee; }else { return fn.apply(null ,arr); } } } let fn = Curry(function ( ) { let arr = Array .from(arguments ); let sum = arr.reduce(function (total,item ) { return total+item; }) return sum; }) var s= fn(1 )(2 )()console .log(s)
两栏布局 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 <div class ="left" >left</div> <div class ="right" >right</div> body,div{ padding: 0 ; margin:0 ; } .left,.right{ height: 200px; } .left{ float: left; width: 200px; background-color:skyblue; } .right{ margin-left: 200px; background-color: greenyellow; } <div class ="left" >left</div> <div class ="right" >right</div> body,div{ padding: 0 ; margin:0 ; } .left,.right{ height:200px; } .left{ float:left; width: 200px; background-color:skyblue; } .right{ overflow:hidden; background-color: greenyellow; } <div class ="wrap" > <div class ="left" >left</div> <div class ="right" >right</div> </div> body,div{ padding: 0 ; margin:0 ; } .wrap{ display: flex; } .left,.right{ height: 200px; } .left{ width: 200px; background-color:skyblue; } .right{ flex: 1 ; background-color: greenyellow; } flex-grow : 1 ; flex-shrink : 1 ; flex-basis : 0 ;
双飞翼布局(三栏布局) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <style> #inside { margin: 0 200px 0 180px; height: 100px; } #center { float: left; width: 100 %; height: 100px; background: blue; } #left { float: left; width: 180px; height: 100px; margin-left: -100 %; background: #0c9; } #right { float: left; width: 200px; height: 100px; margin-left: -200px; background: #0c9; } </style> <body> <div id="center" > <div id="inside" >middle</div> </div> <div id="left" >left</div> <div id="right" >right</div> </body>
圣杯布局(三栏布局) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <style> #bd{ padding: 0 200px 0 180px; height: 100px; } #middle{ float: left; width: 100 %; height: 500px; background:blue; } #left{ float:left; width:180px; height:500px; margin-left:-100 %; background: #0c9; position: relative; left: -180px; } #right{ float: left; width: 200px; height: 500px; margin-left: -200px; background: #0c9; position: relative; right: -200px; } </style> <body> <div id="bd" > <div id="middle" >middle</div> <div id="left" >left</div> <div id="right" >right</div> </div> </body>
排序及查找算法 快速排序O(nlogn) 不稳定的排序
思想:划定两个区域,-1位置表示小于num的区域,数组长度+1的位置表示大于num的区域,然后cur指针从0位置开始遍历。如果小于num,把小于区域的下一个位置 和cur位置交换,交换完之后小于等于区域扩大一个位置cur也++。如果等于num,cur直接+1其他不变。如果大于num,把大于区域的前一个位置和cur交换,more前移动一个位置,cur不变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function quickSort (arr,L=0 ,R=arr.length-1 ) { if (L<R){ let p = partition(arr,L,R); quickSort(arr,L,p[0 ]); quickSort(arr,p[1 ],R); } function partition (arr,L,R ) { let less = L-1 ,more = R+1 ,cur = L; let num = arr[R]; while (cur<more){ if (arr[cur]<num){ swap(arr,++less,cur++); }else if (arr[cur]>num){ swap(arr,cur,--more); }else { cur++; } } return [less,more]; } function swap (arr,i,j ) { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } return arr; }
归并排序O(nlogn) 可以实现稳定的排序
思想:使用递归先对数组进行无限分割,然后再对分割出来的两块进行合并;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function mergeSort (arr,l,r ) { if (l>=r) return 0 ; let mid = Math .floor((l+r)/2 ); mergeSort(arr,l,mid); mergeSort(arr,mid+1 ,r); let temp=[]; let i = l; let j = mid+1 ; while (i<=mid && j<=r){ if (arr[i]<=arr[j]){ temp.push(arr[i]); i++; }else { temp.push(arr[j]); j++; } } while (i<=mid){ temp.push(arr[i]); i++; } while (j<=r){ temp.push(arr[j]); j++; } for (let i=l,j=0 ;i<=r;i++,j++){ arr[i] = temp[j]; } }
堆排序O(nlogn) 不稳定的排序
思想:
建立大根堆:对于每个小二叉树,通过其子节点的(n-1)/2找到其父节点的位置,当父节点小于子节点时交换位置,依次循环。
然后将堆顶元素和树的最后一个叶子节点进行交换,此时最后一个叶子节点上的值为当前树中的最大值,然后将除开此叶子节点的树重复1-2的过程。
冒泡排序O(n^2) 可以实现稳定的排序
1 2 3 4 5 6 7 8 9 10 11 12 13 function bubble (arr ) { if (arr.length === 0 ) return arr; for (let i=arr.length-1 ;i>=0 ;i--){ for (let j=0 ;j<i;j++){ if (arr[j]>arr[j+1 ]){ let temp = arr[j]; arr[j] = arr[j+1 ]; arr[j+1 ] = temp; } } } return arr; }
插入排序O(n^2) 可以实现稳定的排序
1 2 3 4 5 6 7 8 9 10 11 12 13 function insertSort (arr ) { if (arr.length===0 ) return arr; for (let i=1 ;i<arr.length;i++){ for (let j=i;j>0 ;j--){ if (arr[j]<arr[j-1 ]){ let temp = arr[j]; arr[j] = arr[j-1 ]; arr[j-1 ] = temp; } } } return arr; }
选择排序O(n^2) 不稳定的排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function selectSort (arr ) { if (arr.length===0 ) return arr; let min,temp; for (let i=0 ;i<arr.length;i++){ min=i; for (let j=i;j<arr.length;j++){ if (arr[min]>arr[j]){ min =j; } } temp = arr[i]; arr[i] = arr[min]; arr[min] = temp; } return arr; }
二分查找 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function search (arr,data ) { var max = arr.length-1 , min = 0 ; while (min<=max){ var mid = Math .floor((max+min)/2 ); if (arr[mid]<data){ min = mid+1 ; }else if (arr[mid]>data){ max = mid-1 ; }else { return mid; } } return false ; }
树(Tree) 先序遍历(递归) 1 2 3 4 5 6 7 8 9 10 11 12 var preorderTraversal = function (root ) { if (root===null ) return []; let res=[]; function dfs (root ) { if (!root) return null ; res.push(root.val); dfs(root.left); dfs(root.right); } dfs(root); return res; };
先序遍历(非递归) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var preorderTraversal = function (root ) { let res = []; let stack = []; while (root || stack.length!=0 ){ while (root){ res.push(root.val); stack.push(root); root = root.left; } root = stack.pop(); root = root.right; } return res; };
中序遍历(递归) 按先序类推
中序遍历(非递归) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var inorderTraversal = function (root ) { let res = []; let stack = []; while (root || stack.length){ while (root){ stack.push(root); root = root.left; } root = stack.pop(); res.push(root.val); root = root.right; } return res; };
后续遍历(递归) 按先序类推
后续遍历(非递归) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var postorderTraversal = function (root ) { let res = []; let stack = []; while (root || stack.length){ while (root){ stack.push(root); res.unshift(root.val); root = root.right; } root = stack.pop(); root = root.left; } return res; };
二叉树的层序遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var levelOrder = function (root ) { if (!root) return []; let res = [],stack=[root]; let i = 0 while (stack.length){ let len = stack.length; res[i] = []; while (len){ let cur = stack.shift(); res[i].push(cur.val) if (cur.left) stack.push(cur.left); if (cur.right) stack.push(cur.right); len--; } i++; } return res; };
其他 js实现大数相加 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function addString (a,b ) { if (a.length<b.length){ a = a.padStart(b.length,"0" ) } if (a.length>b.length){ b = b.padStart(a.length,"0" ); } let addOne = 0 ; let res = []; for (let i=a.length-1 ;i>=0 ;i--){ let sum = Number (a[i])+Number (b[i])+addOne; if (sum>9 ){ res.unshift(sum-10 ); addOne = 1 ; }else { res.unshift(sum); addOne = 0 ; } } if (addOne){ res.unshift(addOne); } return res.join("" ) }
判断表达式是否闭合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function fn (arr ) { if (arr.length === 0 ) return false ; let stack = []; while (arr.length>0 ){ let c = arr.shift(); if (c === "(" || c==="[" || c=== "{" ){ stack.push(c); }else if (c===")" || c==="]" || c=== "}" ){ if (test(stack.pop()) != c){ return false ; } } } function test (str ) { if (str === "(" ) return ")" ; if (str === "[" ) return "]" ; if (str === "{" ) return "}" ; } if (stack.length ===0 ){ return true ; }else { return false ; } }