JS今日份练习题2

今日份练习题

image-20250308010244500

1、实现 Promise.all

1
2
3
4
请实现一个函数 promiseAll(promises),它接受一个 Promise 数组作为参数,并返回一个新的 Promise

当所有传入的 Promise 都成功时,新 Promise 应以数组形式返回所有结果,顺序与原数组一致;
如果其中任意一个 Promise 被拒绝,则新 Promise 应立即被拒绝,并返回该错误。

? Promise是什么

是处理异步操作的一个内置对象,被设计的初衷是为异步代码提供更优雅的处理,解决回调地狱的问题。

回调地狱 是由于回调函数的嵌套导致代码难以阅读和维护,Promise提供链式调用和统一处理的错误机制解决。

核心功能:表示异步操作的最终完成(或失败)及其结果值。

三种状态:pending进行中-初始状态、fulfilled已完成-成功完成、rejected已拒绝-操作失败。

不可变:一旦从pending变成fulfilled或者rejected,状态不可再变。

基本用法:通过new Promise构造函数创建,接受一个执行器函数,执行器函数有两个参数resolve和reject。

处理结果:使用.then处理成功的情况,使用.catch处理失败的情况,使用.finally执行无论如何都要的最终操作。每个.then和.catch都返回一个新的Promise,可以继续调用.then或者.catch。.catch会捕获整个链中的任何一步错误。一旦某个.catch捕获处理了任务,会继续执行后续。

静态方法:

Promise.resolve(value) - 创建一个已经解析为value的Promise

Promise.reject(reason) - 创建一个已经拒绝为reson的Promise

Promise.all(promises) - 接受Promise数组,如果都成功返回一个解析为结果数组的Peomise。一个失败则立即拒绝。

Promise.race(promises) - 接受Promise数组,返回第一个完成(成功或者失败都叫做完成)的Promise的结果。

? 思路和要点

1、首先,返回的是Promise类型,先写上return new Promise((resolve,reject)=>{})

2、参数校验:传入的参数是Promise数组,判断到底是不是数组。如果不是,返回失败的Promise;

判断传入的数组是不是长度为0,如果是,返回空数组Promise。

判断数组的方法:

  • Array.isArray()
  • instacnceof
  • Object.prototype.toString.call
  • constructor属性判断

3、需要对数组的元素逐个判断,因此考虑使用forEach循环进行遍历。

4、由于元素可能是普通值(数字、字符串或者对象等),也可能是Promise,所以统一使用Promise.resolve() 静态方法将值转换为Promise对象。如果本身是Promise就返回本身。

5、因为需要按顺序存入Promise,但每个promise完成的顺序不一定,需要按照索引存入完成状态的promise。

6、因为Promise的执行器函数会立即同步执行,不能够在forEach循环后确认结果数组的长度,因为promise可能还在pending,而判断马上就执行了。

7、因为JS的稀疏数组特性,有可能最后一个promise先完成了,而结果数组的长度就直接满足了,实际上前面还是空的情况。所以,要依赖一个计数器去判断。

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const res = [];
let count = 0;
if (!Array.isArray(promises))
return reject(new TypeError("请传入数组类型"));
if (promises.length === 0) return resolve([]);
const length = promises.length
promises.forEach((item, index) => {
Promise.resolve(item)
.then((result) => {
res[index] = result;
count++;
if (count === length){
resolve(res);
}
})
.catch((err)=>{
reject(err);
});
});
});
}

2、实现自定义的 bind 方法

1
2
3
4
请手写实现 Function.prototype.bind 方法,要求返回一个新函数。
新函数在被调用时,其 this 应指向传入的指定对象。
同时,新函数支持预设参数(柯里化)。
另外,当该函数被用作构造函数时,应能正确处理原型链的关系,使得实例继承原函数的原型。

? bind函数的作用是

创建一个新的函数(绑定函数),该函数在调用时的this为指定的对象,同时该新函数还可以预设部分参数。

? 实现思路

1、保存原函数的引用。进入bind方法里,此时this指向调用bind的那个原函数。

2、开始写新函数。整合参数。

3、判断调用新函数是普通调用还是构造函数调用,判断this是不是新函数的实例,使用instanceof,如果是的话,原函数调用绑定的this就要保持构造的新对象,否则才指向指定对象。原函数使用apply传入最终确定的this和整合后的参数。

4、原型链补充,使用Object.create,将新函数的原型设置为原函数的原型对象。

5、返回新函数。

解答

1
2
3
4
5
6
7
8
9
10
Function.prototype.myBInd = function(con,...args1){
const originalFn= this
function boundFn(...args2){
const finalArags = args1.concat(args2)
const isNew = this instanceof boundFn
return originalFn.apply(isNew ? this :con,finalArags)
}
boundFn.prototype = Object.create(originalFn.prototype)
return boundFn
}

3、实现柯里化函数

1
2
3
4
5
6
7
8
9
请编写一个函数 curry(fn),它接收一个多参数函数作为参数,并返回一个柯里化后的函数。所谓柯里化,就是将接受多个参数的函数转化为一系列只接受单个参数的函数调用,直到所有参数都传入时,执行原函数并返回结果。
function add(a, b, c) {
return a + b + c;
}

const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 输出 6
console.log(curriedAdd(1, 2)(3)); // 输出 6
console.log(curriedAdd(1)(2, 3)); // 输出 6

?实现思路

1、因为对curriedAdd传递了参数,所以curry函数内部会返回新函数。

2、新函数需要接收参数,使用…args接收

3、接收的参数数量不固定,如果大于等于原函数的定义参数个数length直接调用,否则返回新的函数用于进一步接收参数,递归调用,拼接参数。

解答

1
2
3
4
5
6
7
8
9
10
11
function curry(fn){
return function curried(...args){
if(args.length >= fn.length){
return fn(...args)
}else{
return function(...args2){
return curried(...args.concat(args2))
}
}
}
}

4、实现节流函数

1
2
3
4
请编写一个 throttle 函数,要求满足以下条件:

接受两个参数:func(需要节流的函数)和 wait(时间间隔,单位毫秒)。
返回一个新函数,该新函数在连续调用时,每隔 wait 毫秒只会执行一次 func

? 实现思路

节流:指定时间内只执行一次函数。

1、节流的目的是在指定的时间内,目标函数只会执行一次。所以再次调用函数时,如果存在定时器,就等待目标函数执行。如果不存在,就要开启定时器。

2、执行结束后,需要清理定时器。注意。清理不能放在setTimeout下面,因为会被同步执行,可能定时任务还没结束,就被清理了。所以要放在定时器里面,执行完函数后清理!

3、为了清理,所以一开始的时候,要声明变量,并且在开启定时器的时候,用该变量接收定时器。最后,清理定时器的时候就吧这个变量赋值为null。

解答

1
2
3
4
5
6
7
8
9
10
11
12
function throttle(fn,delay){
let timer = null;
return function(...args){
if (!timer){
timer = setTimeout(()=>{
fn.apply(this,args)
timer = null
},delay)
}

}
}

5、实现 Promise.race

1
2
3
请实现一个函数 promiseRace(promises),它接受一个 Promise 数组作为参数,并返回一个新的 Promise

当数组中的任意一个 Promise 解决或拒绝时,新 Promise 立即以相同的状态解决或拒绝。

? 实现思路

1、返回新的Promise,所以先写上retuen new Promise((resolve,reject)=>{})

2、参数验证,需要是数组。

3、将每个元素转换为promise对象

4、为每个promise对象绑定then和catch方法

5、第一个改变状态的 promise 会触发外层 promise 的 resolvereject,其他 promise 依然会在它们各自状态改变时触发回调,不过它们的结果不会影响已经决议的外层 promise。

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function promiseRace(promises){
return new Promise((resolve,reject)=>{
if (!Array.isArray(promises)){
return reject(new TypeError("请确保输入的是数组类型"))
}
promises.forEach(element => {
element = Promise.resolve(element);
element
.then((res)=>{
resolve(res)
})
.catch((err)=>{
reject(err)
})
});

})
}

6、数组传入指定深度展平

1
2
3
编写一个函数 `flatten(arr, depth)`,该函数接收一个嵌套数组和一个指定的深度 `depth`,返回一个新的数组,其中嵌套的数组只展平到指定深度。
flatten([1, [2, [3, [4]], 5]], 1); // 输出: [1, 2, [3, [4]], 5]
flatten([1, [2, [3, [4]], 5]], 2); // 输出: [1, 2, 3, [4], 5]

? 实现思路

1、先只考虑展平1层

2、参数验证

3、遍历数组,如果是数组,使用扩展运算符,展开1层添加到结果数组。如果是普通值,直接添加到结果数组。

4、根据depth设置终止条件,即深度为0的时候。在对数组展开添加的操作中递归处理数组。

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function flatten(arr, depth){
if (!Array.isArray(arr)){
throw new TypeError('请确保输入是数组类型')
}
if (depth === 0){
return arr.slice()
}
let finalRes = []
arr.forEach(item=>{
if (Array.isArray(item)){
finalRes.push(...flatten(item,depth-1))
}else{
finalRes.push(item)
}
})
return finalRes
}

7、匹配括号问题

1
2
3
4
5
编写一个函数 isValid(s),该函数接受一个只包含括号字符("()""{}""[]")的字符串,并判断字符串中的括号是否匹配有效。
匹配规则:

每个左括号必须有相应的右括号。
左括号必须以正确的顺序闭合。

?实现思路

1、因为从左到右匹配,利用栈的结构解决

2、分为左括号和右括号,遇到左括号就入栈,遇到右括号就做匹配。

3、匹配的时候,如果栈空或者与左括号不匹配,就返回false

4、最终如果栈空,则匹配成功。但是如果栈不空,证明左括号多了,也失败。

? 关键

1、栈结构用数组表示,pop是末端删除,返回被删除元素/undefined,push是末端增加,返回新数组长度

2、使用对象键值对定义括号的匹配规则,右括号为键,因为需要从右括号出发去匹配左括号

3、右括号匹配错误的情况(栈空或者左括号不匹配)

4、右括号都被匹配了,需要检查栈是否为空(如果剩余左括号也算失败)

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function isValid(s){
let stack = []
const map = {
']':'[',
'}':'{',
')':'('
}
for (let char of s){
if (char in map){
if(stack.length === 0 || stack.pop() !== map[char]){
return false
}
}else{
stack.push(char)
}
}
return stack.length === 0
}