​ 本篇文章用于总结自己在前端面试的过程中所遇到的手撕题和前端常考的算法题,其主要包括题目与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
/*手写flat*/
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
//其实就等价于 obj.fn = function say(){} 当指向 context.fn 时,say里面的this 指向obj
context.fn = this
//obj 此时变成 var obj = {name:'innerName',fn:function say(){console.log(this.name)}}
let args = [...arguments].slice(1) //截取第二个开始的所有参数
let result= context.fn(...args)//把执行的结果赋予result变量
delete context.fn //删除执行上下文上的属性 (还原)由var obj = {name:'innerName',fn:function say(){console.log(this.name)}}删除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方法
返回一个promise, 只有当所有proimse都成功时才成功, 否则只要有一个失败的就失败
*/
Promise.all = function (arr) {
// 用来保存所有成功value的数组
const values = new Array(arr.length)
// 用来保存成功promise的数量
let resolvedCount = 0
// 返回一个新的promise
return new Promise((resolve, reject) => {
// 遍历promises获取每个promise的结果
arr.forEach((p, index) => {
Promise.resolve(p).then(
value => {
resolvedCount++ // 成功的数量加1
// p成功, 将成功的vlaue保存vlaues
// values.push(value)
values[index] = value
// 如果全部成功了, 将return的promise改变成功
if (resolvedCount===promises.length) {
resolve(values)
}
},
reason => { // 只要一个失败了, return的promise就失败
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.race()
/*
Promise函数对象的race方法
返回一个promise对象,状态由第一个完成的promise决定
*/
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 是一个 promise, 取这个 promise 的结果值作为返回的 promise 的结果值
value.then(resolve, reject)
// 如果 value 成功, 调用 resolve(val), 如果 value 失败了, 调用reject(reason)
} 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
/*
实现:lazyman("hack").sleep(10).eat("food");
输出:
Hi,你好Hack
wait(10)...
eat food;
注释:其中eat()和sleep()没有顺序关系,可以且可以多次调用
**/
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
//原理其实很简单,就是利用Promise.race,我们先创建一个Promise,里面用setTimeout进行处理,然后将新创建的Promise与我们之前使用的Promise"比赛"一下。
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)) // 最后一个 done
.catch((err) => console.error(err)); // 前四个 error: 取消重复请求
}
// 设置一个定时器等3s,让前面的请求都处理完再继续测试
setTimeout(() => {
// 手动取消最后一个请求
cancelPromise
.request(createRequest(1000))
.then((res) => console.log(res))
.catch((err) => console.error(err)); // error:手动取消
cancelPromise.cancel("手动取消");
}, 3000);

// 设置一个定时器等4s,让前面的请求都处理完再继续测试
setTimeout(() => {
cancelPromise
.request(createRequest(1000))
.then((res) => console.log(res)) // done
.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
/**
* 并发请求限制并发数
* @param {()=>Promise<any> []} requestFns 并发请求函数数组
* @param {numer} limit 限制最大并发数
*/
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;
}
// 给每个pending态的promise生成一个身份标志
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));
// 每秒轮询loading态,直到loading为false
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
//方法一:左侧float:left;右侧margin-left;
//因为块级元素有流体特性,即默认会填充满外部容器,所以只需要设置margin,不需要设置width就可以让content填满剩余的部分。
<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;
}
//方法二:左侧float:left; 右侧overflow:hidden;
<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:1
flex-grow : 1; // 这意味着div将以与窗口大小相同的比例增长
flex-shrink : 1; // 这意味着div将以与窗口大小相同的比例缩小
flex-basis : 0; // 给上面两个属性分配多余空间之前, 计算项目是否有多余空间, 默认值为 auto, 即项目本身的大小。等于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>
/*给内部div添加margin,把内容放到中间栏,其实整个背景还是100%*/
#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)

不稳定的排序

思想:

  1. 建立大根堆:对于每个小二叉树,通过其子节点的(n-1)/2找到其父节点的位置,当父节点小于子节点时交换位置,依次循环。
  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; //没找到返回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
/*
如:3473243899994324123+142325235
*/
function addString(a,b){
if(a.length<b.length){
a = a.padStart(b.length,"0") //padStart从头开始补全,padEnd从尾部开始补全
}
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
/*
如:
输入:{[]}{
输出:false
如:
输入:{[]}
输出:true
*/
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;
}
}