# Promise API
# Promise.all
- 接受一个promise数组,并返回一个新的promise
- 当所有的promise都成功resolve,新的promise才resolve
- 如果任意一个promise被reject,由 Promise.all 返回的 promise 就会立即 reject,并且带有的就是这个 error。
Promise.myAll = (promises)=>{
let count = 0
let arr = []
let length = promises.length
return new Promise((resolve,reject)=>{
promises.forEach((item,index) => {
Promise.resolve(item).then(res=>{
arr[index]=res
count++
if(count===length){
resolve(arr)
}
}).catch(err=>{
reject(err)
})
});
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Promise.race
- 等待第一个settled的promise并resolve其结果或者reject其error
Promise.myRace = (promises) => {
return new Promise((resolve, reject) => {
promises.forEach((item) => {
Promise.resolve(item).then(resolve).catch(reject);
});
});
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# Promise.any
- 等待第一个fulfilled的promise,并将其返回
- 如果promise都rejected,那么reject个error对象
Promise.myAny = (promises) => {
let count = 0;
let length = promises.length;
return new Promise((resolve, reject) => {
promises.forEach((item) => {
Promise.resolve(item)
.then(resolve)
.catch(() => {
count++;
if (count === length) {
reject({ error: '没有promise成功' });
}
});
});
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Promise.allSettled
Promise.allSettled 等待所有的 promise 都被 settle,无论结果如何。结果数组具有
- {status:"fulfilled", value:result} 对于成功的响应,
- {status:"rejected", reason:error} 对于 error。
Promise.myAllSettled = function (promises) {
let arr = [];
let count = 0;
let length = promises.length;
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
Promise.resolve(item)
.then((res) => {
arr[i] = { status: 'fulfilled', value: res };
count++;
if (count === length) {
resolve(arr);
}
})
.catch((error) => {
arr[i] = { status: 'rejected', reason: error };
count++;
if (count === length) {
resolve(arr);
}
});
});
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# call,apply,bind
# call
call(obj,...args)
- 改变this指向,指向函数第一个参数
- 函数执行
Function.prototype.myCall = function (context, ...args) {
if (typeof this !== 'function') {
return new Error('调用对象必须是函数');
}
let ctx = context || windows;
let fn = Symbol();
ctx[fn] = this;
args = args ? args : [];
const res = args.length > 0 ? ctx[fn](...args) : ctx[fn]();
delete ctx[fn];
return res;
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# apply
apply(obj,args=[])
- 和call唯一的区别是,参数是类数组对象
Function.prototype.myCall = function (context, args=[]) {
if (typeof this !== 'function') {
return new Error('调用对象必须是函数');
}
let ctx = context || windows;
let fn = Symbol();
ctx[fn] = this;
const res = args.length > 0 ? ctx[fn](...args) : ctx[fn]();
delete ctx[fn];
return res;
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# bind
- 改变this指向
- 返回一个函数
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new Error('调用对象必须是函数');
}
let self = this;
const args = [...arguments].slice(1);
return function bingFn() {
return self.apply(this instanceof bingFn ? this : context, args.concat(...arguments));
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# instanceof
myInstanceof(target,origin)
- 在target原型链上是否能找到origin
function myInstanceof(target, origin) {
if (typeof target !== 'object' || target === null) {
return false;
}
if (typeof origin !== 'function') {
throw new Error('origin must be function');
}
let proto = Object.getPrototypeOf(target);
while (proto) {
if (proto === origin.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# new
- 创建一个新对象,并让this指向这个对象
- 新对象的__proto__指向函数的prototype
- 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用
function myNew(fn) {
let obj = {};
if (fn.prototype !== null) {
obj.__proto__ = fn.prototype;
}
let args = [...arguments].slice(1);
let res = fn.apply(obj, args);
if ((typeof res === 'object' || typeof res === 'function') && res !== null) {
return res;
}
return obj;
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 柯里化
- 柯里化是一种函数的转换,将一个函数从可调用的f(a,b,c)转换为可调用的f(a)(b)(c)
- 柯里化不会调用函数,只是对函数转换
function curry(fn) {
let length = fn.length;
return function _curry(...args) {
if (args.length >= length) {
return fn.apply(this, args);
} else {
return function (...args2) {
return _curry.apply(this, args2.concat(args));
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 防抖与节流
# 防抖
- 事件f被触发后n毫秒后回调,如果n毫秒内又被触发,则使用最新的参数等待n毫秒后调用f
function debounce(fn, ms) {
let timer;
return function () {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, arguments), ms);
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 节流
- 在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
function throttle(func, ms) {
let isThrottled = false,
savedArgs,
savedThis;
function wrapper() {
if (isThrottled) {
savedArgs = arguments;
savedThis = this;
return;
}
isThrottled = true;
func.apply(this, arguments);
setTimeout(function() {
isThrottled = false;
if (savedArgs) {
wrapper.apply(savedThis, savedArgs);
savedArgs = savedThis = null;
}
}, ms);
}
return wrapper;
}
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
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
# flat
- flat(depth) 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
- depth默认为1
# ❤️ reduce + concat + isArray + recursivity
function flatDeep(arr, depth = 1) {
return depth > 0
? arr.reduce((cur, val) => cur.concat(Array.isArray(val) ? flatDeep(val, depth - 1) : val), [])
: arr.slice();
}
1
2
3
4
5
2
3
4
5
# ❤️ forEach+isArray+push+recursivity
function eachFlat(arr = [], depth = 1) {
const result = []; // 缓存递归结果
// 开始递归
(function flat(arr, depth) {
// forEach 会自动去除数组空位
arr.forEach((item) => {
// 控制递归深度
if (Array.isArray(item) && depth > 0) {
// 递归数组
flat(item, depth - 1);
} else {
// 缓存元素
result.push(item);
}
});
})(arr, depth);
// 返回递归结果
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 深浅拷贝
# 浅拷贝
function shallowClone(target) {
if (!(typeof target === 'object' && target !== null)) {
return target;
}
let copyObj = {};
for (let key in target) {
if (target.hasOwnProperty(key)) {
copyObj[key] = target[key];
}
}
return copyObj;
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 深拷贝
# ❤️ 乞丐版
let copyObj = JSON.parse(JSON.stringify(obj))
1
这样写会有以下问题
- 一些特定属性会被 JSON.stringify 跳过
- 函数属性
- 值为undefined的属性
- Symbol类型的键和值
- Date会变字符串,RegExp,Error对象会变空值
- NaN,+Infinity,-Infinity变成null
- 无法解决循环引用
- 无法拷贝不可枚举的属性
let obj = {
fn() {
console.log('1234');
},
[Symbol('123')]: 123,
name: undefined,
time: new Date(),
city: NaN,
age: Infinity,
error: new Error('1212'),
reg: new RegExp('[^0-9]'),
};
console.log(JSON.parse(JSON.stringify(obj)));
//输出
// {
// "time": "2022-08-26T08:20:05.206Z",
// "city": null,
// "age": null,
// "error": {},
// "reg": {}
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 一步步实现深拷贝 参考文章 (opens new window)
- 首先在浅拷贝基础上,加入递归,完成一个简单的基础版
function deepClone(target){
if(typeof target === 'object'){
let copyObj = {}
for(let key in target){
copyObj[key] = deepClone(target[key])
}
return copyObj
}else{
return target
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 考虑数组
function deepClone(target) {
if (typeof target === 'object') {
let copyObj = Array.isArray(target) ? [] : {};
for (let key in target) {
copyObj[key] = deepClone(target[key]);
}
return copyObj;
} else {
return target;
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 解决循环引用
TIP
解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。
function deepClone(target, map = new Map()) {
if (typeof target === 'object') {
let copyObj = Array.isArray(target) ? [] : {};
if (map.get(target)) {
return map.get(target);
}
map.set(target, copyObj);
for (const key in target) {
copyObj[key] = deepClone(target[key], map);
}
return copyObj;
} else {
return target;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
修改下引用类型的判断
function isObject(target) {
const type = typeof target;
return type !== null && (type === 'object' || type === 'function');
}
1
2
3
4
2
3
4