1. 实现 Promise.all,或者 实现 Promise.allSettled
(1)promise.all:
传入一个promise数组,其中所有promise都变为成功状态,返回一个数组,数组内是各个promise的返回;若任意传入的promise变为拒绝状态,那么返回该拒绝状态的promise的返回。
思路:
输入是一个数组,这个数组中的元素一般都是一个 Promise 实例。
输出是一个新的 Promise 实例, resolve 的内容是一个数组,这个数组就是输入中每个 Promise 的 resolve 的值。
如果有一个 Promise 执行失败,那么返回的新的 Promise 也会 reject。
其次,Promise.all 是 Promise 类上的通用方法,而不是原型上的方法。所以其实现不需要挂在 prototype 上。
<script>
// 1. promise.all
// 输入:[promise实例,promise实例,……]
// 输出:全部成功resolve返回promise结果组成的数组,只要有一个reject返回失败的promise结果
// 是promise类上通用方法,非原型方法,无需挂载在prototype上
Promise.myAll = function (promises) {
if (!Array.isArray(promises)) {
console.error("must be a array");
return;
}
const res = new Promise((resolve, reject) => {
const data = [];
// 成功执行的promise数
let completedCnt = 0;
// 循环执行每个promise
promises.forEach((promise, index) => {
// 确保处理的对象是一个Promise
Promise.resolve(promise)
.then((res) => {
// promise执行后放入data中
data[index] = res;
completedCnt++;
if (completedCnt === promises.length) {
resolve(data);
}
})
.catch((err) => reject(err));
});
});
return res;
};
// 编写测试用例
Promise.myAll([
Promise.resolve(1),
new Promise((resolve) => {
setTimeout(() => {
resolve(2);
}, 1000);
}),
new Promise((resolve) => {
setTimeout(() => {
resolve(3);
}, 2000);
}),
])
.then((res) => {
console.log(res);
})
.catch();
Promise.myAll([Promise.resolve(2), Promise.reject(new Error("Failed"))])
.then((res) => {
console.log(res);
})
.catch(console.err);
</script>
以下图解均来自知乎栗子前端
(2)promise.allSettled :
传入一个promise数组,当所有promise都变成已完成状态时,返回一个数组,包含每个promise返回结果(状态、值或者拒绝原因)
// 2. promise.allSettled
// 输入:[promise实例,promise实例,……]
// 输出:返回所有promise实例的结果
// 是promise类上通用方法,非原型方法,无需挂载在prototype上
Promise.myAllSettled = function (promises) {
if (!Array.isArray(promises)) {
console.error("must be a array");
return;
}
const res = new Promise((resolve) => {
const data = [];
// 存放所有执行的promise结果
let completedCnt = 0;
// 循环执行每个promise
promises.forEach((promise, index) => {
// 确保处理的对象是一个Promise
Promise.resolve(promise)
.then((res) => {
// promise执行后放入data中
data[index] = { status: "fulfilled", value: res };
})
.catch((reason) => {
data[index] = { status: "rejected", value: reason };
})
.finally(() => {
completedCnt++;
if (completedCnt === promises.length) {
resolve(data);
}
});
});
});
return res;
};
// 测试用例
Promise.myAllSettled([
Promise.resolve(1),
new Promise((resolve) => {
setTimeout(() => {
resolve(2);
}, 1000);
}),
new Promise((resolve) => {
setTimeout(() => {
resolve(3);
}, 2000);
}),
Promise.reject(new Error("Failed")),
])
.then((res) => {
console.log(res);
})
.catch(console.err);
(3)promise.race:
promise.race()接收一个包含过个promise实例,返回最先返回结果的promise(不论成功还是失败)
// 3. promise.race
// 输入:[promise实例,promise实例,……]
// 输出:返回最先返回结果的promise
// 是promise类上通用方法,非原型方法,无需挂载在prototype上
Promise.myRace = function (promises) {
if (!Array.isArray(promises)) {
console.error("must be a array");
return;
}
const res = new Promise((resolve, reject) => {
// 循环执行每个promise
promises.forEach((promise) => {
Promise.resolve(promise).then(resolve).catch(reject);
});
});
return res;
};
// 测试用例
Promise.myRace([
new Promise((resolve, reject) => {
setTimeout(() => reject("Promise 2 rejected"), 500);
}),
new Promise((resolve) => {
setTimeout(() => {
resolve(2);
}, 1000);
}), //1s
new Promise((resolve) => {
setTimeout(() => {
resolve(3);
}, 2000);
}), //2s
// Promise.reject(new Error("Failed")), //立即
])
.then((res) => {
console.log(res);
})
.catch((err) => console.error(err));
(4)promise.any:
promise.any()接收一个包含过个promise实例,返回最先成功执行resolve的promise;如果所有promise都都失败(rejected)时被 reject,返回包含所有失败结果的数组
Promise.myAny = function (promises) {
if (!Array.isArray(promises)) {
console.error("must be a array");
return;
}
const res = new Promise((resolve, reject) => {
const errors = [];
let rejectedCnt = 0;
// 循环执行每个promise
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((res) => resolve(res))
.catch((err) => {
errors[index] = err;
rejectedCnt++;
if (rejectedCnt === promises.length) {
reject(
new AggregateError(errors, "All promises were rejected")
);
}
});
});
});
return res;
};
// 测试用例
Promise.myAny([
// 不能不写resolve
new Promise((resolve, reject) => {
setTimeout(() => {
reject(2);
}, 1000);
}), //1s
new Promise((resolve, reject) => {
setTimeout(() => {
reject(3);
}, 2000);
}), //2s
Promise.reject(new Error("Failed")), //立即
Promise.resolve(1),
])
.then((res) => {
console.log(res);
})
.catch((error) => {
if (error instanceof AggregateError) {
console.error("All promises were rejected:");
error.errors.forEach((err, index) => {
console.error(`Error ${index + 1}:`, err);
});
} else {
console.error("An unexpected error occurred:", error);
}
});
2. 实现 对象的深拷贝和浅拷贝
深拷贝:递归赋值所有层,如果是引用类型,那么会新建一个,所以对原值的修改不会影响拷贝值。
浅拷贝:只处理第一层的复制,如果是引用类型,就会复制其引用。所以对于引用属性的值的修改,会相互干扰,因为他们共用一份引用。
(1)浅拷贝:
function shallowCopy(obj) {
// typeof null === 'object' 所以单独处理(typeof对于null、array、object都返回object)
if (typeof obj !== "object" || obj === null) {
return obj;
}
const isArr = Array.isArray(obj);
const res = isArr ? [] : {};
if (isArr) {
for (let i = 0; i < obj.length; i++) {
res[i] = obj[i];
}
} else {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
res[keys[i]] = obj[keys[i]];
}
}
return res;
}
console.log(shallowCopy({ a: 1, b: 2 }));
(2)深拷贝:
function deepCopy(obj) {
// 如果深拷贝map
const map = new Map();
function realCopy(obj) {
if (typeof obj !== "object" || obj === null) {
return obj;
}
// 如果map中有,则使用map中的数据
if (map.has(obj)) {
return map.get(obj);
}
const isArr = Array.isArray(obj);
const res = isArr ? [] : {};
// 将当前obj和res关联起来
map.set(obj, res);
if (isArr) {
for (let i = 0; i < obj.length; i++) {
res[i] = realCopy(obj[i]);
}
} else {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
const value = obj[keys[i]];
res[keys[i]] = realCopy(value);
}
}
return res;
}
return realCopy(obj);
}
const obj = {
a: {
a: 1,
b: 2,
},
b: [1, 2, 3],
};
const res = deepCopy(obj);
console.log("res:", res);
obj.a.a = 3;
obj.b[0] = 99;
console.log("obj:", obj);
console.log("res:", res);
3. 实现 call, apply, bind
三者均是改变this的指向
call()将实参在对象后依次传递 a,b,c,d
apply()需将实参封装到一个数组中统一传递 [a,b,c,d]
bind() 也是依次传递参数,返回改过this指向的新函数,调用才会执行,即bind(a,b,c,d)() a,b,c,d
自己实现call:
// 手写实现call
Function.prototype.myCall = function (context, ...args) {
// 先判断调用对象是不是函数
if (typeof this !== "function") {
console.log("type error");
}
// 判断context是否传入,null则设为window
context = context || window;
// 增加context的临时属性fn,用来存储原来的this指向,也就是函数自己,方便后面调用(为了避免fn与context本身属性重复,使用symbol)
const fn = Symbol();
context[fn] = this;
// 调用方法,此时使用的是context的方法,那么fn 属性所引用的函数(即原始函数)在执行时,this 将指向 context 对象
const result = context[fn](...args);
// 删除临时属性,避免 context 对象被 fn 属性污染
delete context[fn];
// 返回函数本身调用结果
return result;
};
自己实现apply:
自己实现bind:
// 手写实现bind
Function.prototype.myBind = function (context, ...args) {
// 先判断调用对象是不是函数
if (typeof this !== "function") {
console.log("type error");
}
// 判断context是否传入,null则设为window
context = context || window;
// 闭包存一下当前函数,此时this指向要调用的函数
const fn = this;
return function (...innerArgs) {
// 由于bind语法与call语法类似,此处利用call帮助实现
return fn.call(context, ...args, ...innerArgs);
// 也可以使用apply实现
return fn.apply(context, args.concat(innerArgs));
};
};
4. 实现消抖或者节流
(1)消抖的原理是在事件触发后,等待一段时间再执行函数。如果在等待时间内再次触发事件,则重新开始计时。这样可以确保函数在高频率事件触发时,只在最后一次事件触发后才执行一次。常用于 input 事件和 change 事件的处理。
当新函数被多次执行时,每次都会重新计时,只有 delay 长度的时间内,没有触发,才会真正执行原函数。
所以内部设置计时器,该定时器会在 delay 时间之后执行原函数,每次新函数被执行,清除计时器。
手写消抖:
// 手写防抖:连续时间内多次触发只执行最后一次(只要打断就会重新开始)。利用计时器实现,每次在delay事件之后执行fn,每次新函数被执行,清楚计时器。
// 优化:用户每次输入都会等一个delay时间才会响应,我们希望用户在第一次输入后立刻响应
function debounce(fn, delay, immediate) {
// 入参类型判断
if (typeof fn !== "function") {
console.error("First argument must be a function");
}
if (typeof delay !== "number" || delay < 0) {
console.error("Second argument must be a non-negative number");
}
// 闭包存储timer,这样,每次result执行时都会是同一个timer
let timer;
// 闭包存储一下是否是第一次执行
let first = true;
function result(...args) {
// 清除定时器
clearTimeout(timer);
// delay事件后执行fn
// 箭头函数保证this指向result作用域
// 每次新函数执行时,重新计时
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
}
// 如果是第一次执行,那么用户期望立即执行一次
if (first && immediate) {
fn.apply(this.args);
first = false;
}
return result;
}
// 编写测试用例
function test(a, b) {
console.log(a, b);
}
const debounceTest = debounce(test, 1000);
// 1s连续触发3次,只执行最后一次
debounceTest(1, 2);
debounceTest(2, 3);
debounceTest(3, 5);
//3s后执行4,5
setTimeout(() => {
debounceTest(4, 5);
}, 3000);
(2)节流的原理是在规定时间内只执行一次函数,无论这段时间内事件触发了多少次。这可以确保函数在高频率事件触发时,按照固定的时间间隔执行。常用于 scroll 事件的监听
手写节流:
// 手写节流:连续时间内多次触发只执行一次,强调不要打断我。
// 记录上次执行时间,计算当前经过时间是否超过最大限制时间,超过则执行并更新上次执行时间
// 优化:最后一次一定要能执行到
function throttle(fn, limit, trailing = false) {
if (typeof fn !== "function") {
console.error("First argument must be a function.");
}
if (typeof limit !== "number" || limit < 0) {
console.error("Second argument must be a non-negative number.");
}
// 记录上一次执行时间
let lastTime = 0;
let timer = null;
function result(...args) {
const now = Date.now();
// 如果当前经过时间超过了limit,就执行fn
if (now - lastTime >= limit) {
if (timer) {
// 由于定时器执行时间在limit之后,没有必要每次销毁重建,直接用上一次定时器即可
clearTimeout(timer);
timer = null;
}
fn.apply(this, args);
// 更新上次执行时间
lastTime = now;
} else if (trailing && !timer) {
// 如果没有超过limit,设置一个定时器同时清除设次设定的定时器,保障嘴壶一次被执行(这里类似消抖)
clearTimeout(timer);
const remainTime = limit - (now - lastTime);
timer = setTimeout(() => {
fn.apply(this, args);
lastTime = Date.now();
timer = null;
}, remainTime);
}
}
return result;
}
// 编写测试用例
function test(a, b) {
console.log(a, b);
}
const throttleTest = throttle(test, 1000);
// 1s连续触发3次,只执行第一次
throttleTest(1, 2);
throttleTest(2, 3);
throttleTest(3, 8);
//2s后执行4,5
setTimeout(() => {
throttleTest(4, 5);
}, 2000);
setTimeout(() => {
throttleTest(6, 7);
}, 2500);
5. 实现函数的柯里化
函数柯里化(Currying)是一种将一个只能一次性接受多个参数的函数转化为一系列接受一个或多个参数的函数的技术,使得函数可以更灵活地使用和组合。
一个具体函数:
// 函数柯里化(Currying)是一种将一个只能一次性接受多个参数的函数转化为一系列接受一个或多个参数的函数的技术,使得函数可以更灵活地使用和组合。
// 实现函数sum(1)(2)(3)(4)(); //10
// 实现函数sum(1)(2)(3,4)(); //10
// 定义外部变量存储参数
let params = [];
// 立即执行函数
const sum = (function () {
let params = [];
function result(...args) {
if (args.length === 0) {
let temp = 0;
for (let i = 0; i < params.length; i++) {
temp += params[i];
}
params = [];
return temp;
} else {
params.push(...args);
return result;
}
}
return result;
})();
const a = sum(1)(2)(3)(4)();
console.log(a);
对于任意函数:
function curry(fn) {
if (typeof fn !== "function") {
console.error("must be a function");
return;
}
let params = [];
function result(...args) {
params.push(...args);
// 如果参数数量超过fn可以接收的数量就执行fn
// fn.length获取函数参数长度
if (params.length >= fn.length) {
const res = fn.apply(this, params);
params = [];
return res;
}
// 否则返回新函数
return result;
}
return result;
}
// 测试用例
function add(a, b, c, d) {
return a + b + c + d;
}
const curriedAdd = curry(add);
const suma = curriedAdd(1)(2, 3)(4); //10
const sumb = curriedAdd(2)(3, 4, 5); //14
console.log(suma, sumb);