算法:

原生ajax实现:

  • 原生实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script type="text/javascript">
function getTime(){
//1.创建ajax对象
var xhr= new XMLHttpRequest();
//2.准备请求(请求类型,后端地址):此处请求并没有传数据出去,只是发送了一个请求
xhr.open("get","/gettime");
//3.监听ajax的状态变化
xhr.onreadystatechange= funtion(){
if(xhr.status==200 && xhr.readyState==4){
var result = xhr.responseText;
//放到相应的位置
document.getElementById("time").innerHTML = result;
}
}
//4.发送请求
xhr.send();
}
</script>
  • 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
function ajax({ url, method = 'get', data, params}) {
return new Promise((resolve, reject) => {
// 处理数据的方法
const formatData = (data) => {
let arr = [];
for (let key in data) {
// data[key]
// data.b
arr.push(`${key}=${data[key]}`)
}
return arr.join('&')
}
let newParams = formatData(params);

// 1、创建实例化对象
let xhr = new XMLHttpRequest();

// 2、服务器连接
if (method === 'get') {
/*
open的三个参数
第一个:请求方式
第二个:请求的URL
第三个:是否开启异步
*/
// url?a=1&b=2
xhr.open('get', `${url}?${newParams}`, true);
// 3、发送请求
xhr.send(null);
} else if (method === 'post') {
xhr.open('post', `${url}?${newParams}`, true);
// 设置请求头
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
// 3、发送请求
const newData = formatData(data);
xhr.send(newData);
}

// 4、接收数据
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {// 请求成功、响应成功
// responseText 获取请求数据
resolve(xhr.responseText)
} else {
reject(xhr.responseText)
}
}
})
}

数组扁平化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const arrNum = [1, [2, 3], [4, [5, 6, 7]]]
//第一种递归实现
function flattenLoop(arr, level = 1) {
let result = []
for (var i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i]) && level > 0) {
result = result.concat(flattenLoop(arr[i], level - 1))
} else {
result.push(arr[i])
}
}
return result
}
console.log(flattenLoop(arrNum, 2)) // [1, 2, 3, 4,5, 6, 7]

let arr = [1, [2, [3, 4]]];
function flatten(arr, level) {
return arr.reduce(function(prev, next){
return prev.concat(Array.isArray(next) && level > 0 ? flatten(next, level - 1) : next)
}, [])
}
console.log(flatten(arr, 3));// [1, 2, 3, 4,5]

对象扁平化:

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
const isObject = (obj) => Object.prototype.toString.call(obj) === '[object Object]'
const flatten = obj => {
let res = {}
// 递归结束口
if(!(obj instanceof Object)) return
const dfs = (cur, prefix)=>{
//先判断是否为对象,如果是对象,需要在判断是数组还是对象
if(cur instanceof Object){
if(Array.isArray(cur)){
for(let i in cur){
dfs(cur[i], `${prefix}[${i}]`)
}
}else {
for(let key in cur){
dfs(cur[key], `${prefix}${prefix?'.':''}${key}`)
}
}
// 如果不是对象,数据直接存入就可以了
}else {
res[prefix] = cur
}
}
dfs(obj, '')
return res
}
const obj = {
a: 1,
b: [1, 2, { c: true }],
c: { e: 2, f: 3 },
g: null,
};
console.log(flatten(obj))

括号匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function validBraces(braces) {
const stack = []
try {
braces.split("").forEach(e => {
if (e === "(" || e === "{" || e === "[") {
stack.push(e)
} else {
if (e === ")" && stack.pop() !== "(") throw ""
if (e === "]" && stack.pop() !== "[") throw ""
if (e === "}" && stack.pop() !== "{") throw ""
}
})
} catch {
return false
}

return stack.length === 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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// 异步请求防抖,不能通过防抖实现,因为如果对异步进行防抖,请求和结果可能会错乱,导致显示有问题,所以async,await方式进行同步操作,可以使用防抖
function debounce(fn, delay) {
// timer 存储定时器
let timer = null
// 返回一个闭包函数,用闭包保存timer确保其不会销毁,重复点击会清理上一次的定时器
return function() {
let arg = arguments
// 在抖动的时间内,清除定时器
if(timer) {
clearTimeout(timer)
}
// 开启这一次的定时器
timer = setTimeout(() => {
fn.apply(this, arg);
}, delay)
}
}


/**
* 节流函数(首节流),函数只在指定的时间执行一次,首次会立即执行,之后在规定的时间间隔内执行
* @param {需要节流的函数} fn
* @param {节流中的时间间隔} delay
* @returns
*/
function throttle_first(fn, delay) {
let pre = null
return function() {
let args = arguments
let now = Date.now()
if(now - pre >= delay){
fn.apply(this, args)
pre = now
}
}
}
/**
* 节流函数(尾节流),函数只在指定的时间执行一次,首次不会执行,只会在之后执行
* @param {需要节流的函数} fn
* @param {节流中的时间间隔} delay
*/
function throttle_end(fn, delay) {
let timer = null
return function () {
let args = arguments
if(timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
}

/**
* 时间戳 & 定时器
* @param {需要节流的函数} fn
* @param {节流中的时间间隔} delay
*/

function throttle(fn, delay) {
// 初始化定时器
let timer = null;
// 上一次调用时间
let prev = null;
// 返回闭包函数
return function () {
// 现在触发事件时间
let now = Date.now();
// 触发间隔是否大于delay
let remaining = delay - (now - prev);
// 保存事件参数
const args = arguments;
// 清除定时器
clearTimeout(timer);
// 如果间隔时间满足delay
if (remaining <= 0) {
// 调用fn,并且将现在的时间设置为上一次执行时间
fn.apply(this, args);
prev = Date.now();
} else {
// 否则,过了剩余时间执行最后一次fn
timer = setTimeout(() => {
fn.apply(this, args)
}, delay);
}
}
}

数组去重:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const quchong = (arr) => {
return [...new Set(arr)]
}

const quchong2 = (arr) => {
return arr.reduce((pre, cur) => {
return pre.includes(cur) ? pre : pre.concat(cur)
// return pre.includes(cur) ? pre : [...pre, cur]
}, [])

}


const quchong3 = (arr) => {
return arr.filter((item, index, arr) => {
return arr.indexOf(item) === index
})
}

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
//all 方法表示等待所有的 Promise 全部成功后才会执行回调,如果有一个 Promise 失败则 Promise 就失败了
Promise.all = promises => {
return new Promise((resolve, reject) => {
//存放结果
const res = [];
//计数,当count 等于 length的时候就resolve
let count = 0;
const resolveRes = (index, data) => {
//将执行结果缓存在res中
res[index] = data;
//所有子项执行完毕之后,执行resolve 抛出所有的执行结果
if (++count === promises.length) {
resolve(res);
}
};
//循环遍历每一个参数的每一项
for(let i = 0; i < promises.length; i++) {
const current = promises[i];
//如果当前项是Promise,则返回 then 的结果
if (isPromise(current)) {
current.then((data) => {
resolveRes(i, data);
}, (err) => {
reject(err);
});
} else {
resolveRes(i, current);
}
}
});
}
//race谁是第一个完成的,就用他的结果,如果是失败这个 Promise 就失败,如果第一个是成功就是成功
Promise.race = (promises) => {
return new Promise((resolve, reject) => {
for(let i = 0; i < promises.length; i++) {
let current = promises[i];
//如果是一个 promise 继续执行 then
if (isPromise(current)) {
current.then(resolve, reject);
} else {
//是普通值则直接 resolve 返回,并终止循环
resolve(current);
break;
}
}
});
}

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
// 并发请求函数
const concurrencyRequest = (urls, maxNum) => {
// 因为测试代码调用concurrencyRequest后需要返回一个Promise
return new Promise((resolve) => {
// urls的长度为0时,results就没有值,此时应该返回空数组
if (urls.length === 0) {
resolve([]);
return;
}
const results = [];
let index = 0; // 下一个请求的下标
let count = 0; // 当前请求完成的数量

// 发送请求
async function request() {
if (index === urls.length) return;
const i = index; // 保存序号,使result和urls相对应
const url = urls[index];
index++;
console.log(url);
try {// 请求成功
const res = await fetch(url);
// resp 加入到results
results[i] = res;
} catch (err) {// 请求失败
// err 加入到results
results[i] = err;
} finally {// 请求失败或成功都补一个新的请求进来
// 请求完成计数器
count++;
// 判断是否所有的请求都已完成
if (count === urls.length) {
console.log('完成了');
resolve(results);
}
//
request();
}
}

// maxNum大于urls的长度时,应该取的是urls的长度,否则则是取maxNum
const times = Math.min(maxNum, urls.length);
// 开启第一次批量调用
for(let i = 0; i < times; i++) {
request();
}
})
}

http://example.com/2023/11/30/前端面试常考算法/
作者
Deng ErPu
发布于
2023年11月30日
许可协议