1. JavaScrpit API
1 Promise
Promise A+:https://promisesaplus.com
1.1 Promise 实现
- 搭建架子:
- MyPromise 对象:
- constrictor 三个函数 resolve、reject、executor;
- promise 实例对象
- 传入 executor 的入参(回调函数);
- 尝试在 executor 的回调函数中,调用 resolve 和 reject。
- MyPromise 对象:
- 定义状态:pending、fulfilled、rejected;
- 定义 resolve 和 reject 内的状态执行逻辑。
// ES6 ES2015
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
class MyPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
// 如果状态已经决议,则不执行resolve
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
console.log("resolve被调用");
};
const reject = (reason) => {
// 如果状态已经决议,则不执行reject
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
console.log("reject被调用");
};
executor(resolve, reject);
}
}
const promise = new MyPromise((resolve, reject) => {
console.log("pending 状态");
resolve(1111);
reject(2222);
});
1.2 then 实现
- 先写出
promise.then(回调1, 回调2);
通过函数调用构思 then 的内容; - Promise 实现了回调函数的延时绑定技术,所以只有通过
.then(回调1,回调2)
调用时,才会动态绑定通过 then 传递进去的回调函数,绑定完毕后立即根据 promise 的结果(成功/失败)来执行对应的回调函数。- 延迟绑定的思路:让
.then()
这行代码的执行顺序提前,先绑定好 then 中的两个回调函数,再执行回调函数。 - resolve 和 reject 在执行结束前,察看一下当前 callbacks 队列中是否有需要执行的回调,如果有依次执行
- then 在绑定 onFulfilled 和 onRejected 回调时,判断一下当前 promise 的状态:
- 如果还在 pending,说明 executor 内部存在异步,把回调加入 callbacks 回调队列。
- 如果已经 fulfilled 或 rejected,executor 函数已经执行完毕,直接执行回调。
- 延迟绑定的思路:让
- 对同一个 promise,可以调用多次 then,获得各自的结果:
- 这里在 Promise 对象中定义两个数组,分别来存放多个
.then()
调用时,添加的onFulfilled
和onRejected
函数。在 promise 状态敲定后,把数组中的全部回调都执行一下即可。
- 这里在 Promise 对象中定义两个数组,分别来存放多个
// @ts-nocheck
class MyPromise {
static STATE = {
PENDING: "pending",
FULFILLED: "fulfilled",
REJECTED: "rejected",
};
constructor(executor) {
this.state = MyPromise.STATE.PENDING;
this.result = undefined;
this.fulfilledCallbacks = [];
this.rejectedCallbacks = [];
this.resolve = (value) => {
if (this.state !== MyPromise.STATE.PENDING) return;
queueMicrotask(() => {
this.state = MyPromise.STATE.FULFILLED;
this.result = value;
this.fulfilledCallbacks.forEach( cb => {
cb(this.result);
})
})
};
this.reject = (reason) => {
if (this.state !== MyPromise.STATE.PENDING) return;
queueMicrotask(() => {
this.state = MyPromise.STATE.REJECTED;
this.result = reason;
this.rejectedCallbacks.forEach( cb => {
cb(this.result);
})
})
};
try {
executor(this.resolve, this.reject);
} catch (err) {
this.reject(err);
}
}
then = (onFulfilled, onRejected) => {
// 1 判断:如果不是函数
// onFulfilled将 value 原封不动的返回,
//onRejected 返回 reason, 通过 throw Error 来返回
onFulfilled =
onFulfilled instanceof Function ? onFulfilled : (value) => {return value};
onRejected =
onRejected instanceof Function ? onRejected : (value) => {throw value};
// 2 如果promise状态还在pending,则加入执行队列
if (this.state === MyPromise.STATE.PENDING) {
this.fulfilledCallbacks.push(onFulfilled);
this.rejectedCallbacks.push(onRejected);
}
// 3 如果promise状态已经确认,则异步执行回调
if (this.state === MyPromise.STATE.FULFILLED) {
queueMicrotask(() => {
onFulfilled(this.result);
})
}
if (this.state === MyPromise.STATE.REJECTED) {
queueMicrotask(() => {
onRejected(this.result);
})
}
};
}
const promise = new MyPromise((resolve, reject) => {
console.log("pending 状态");
// 异步执行
setTimeout(()=> {
resolve("resolve-1");
reject("reject-1");
}, 1000)
});
promise.then(
(res) => {
console.log("成功", res);
},
(err) => {
console.log("失败", err);
}
);
// 多个then调用
promise.then(
(res) => {
console.log("成功2", res);
},
(err) => {
console.log("失败2", err);
}
);
三处异步处理的地方:
- then
- then 的调用时机是异步的,所以添加异步回调。
- resolve、reject
- executor 内,当出现
resolve()
执行时,promise 状态改变,但此时 resolve 内不能立即执行保存的 onFulfilled 回调,因为按照 A+ 规定,此时还要把 executor 剩余的代码执行完闭,下一个 异步时钟才执行 onFulfilled 回调。所以 resolve 和 reject 也添加异步。
- executor 内,当出现
执行顺序测试:
console.log(1);
let promise1 = new MyPromise((resolve, reject) => {
console.log(2);
setTimeout(() => {
console.log('A', promise1.state);
resolve('这次一定');
console.log('B', promise1.state);
console.log(4);
});
})
promise1.then(
result => {
console.log('C', promise1.state);
console.log('fulfilled:', result);
},
reason => {
console.log('rejected:', reason)
}
)
console.log(3);
- then 可以形成调用链
- 让 then 方法再返回一个新的 promise 对象。
promise.then().then().then()
注意以下几点:
- then 返回一个新的 promise,所以在 then 方法中,返回一个新的 MyPromise 实例。
onFulfilled
和onRejected
有返回结果,所以用 value 拿到这个返回结果,做为新的 promise 决议信息,通过resolve(value)
决议这个新的 promise。- 在执行
onFulfilled
和onRejected
如果出现错误,新的 promise 的状态就立即修改为rejected
,所以用 try catch 包裹这两个回调函数的执行。如果 catch 到错误,就用reject(value)
来决议这个新的 promise。 - 所以在情况2 “如果 promise 状态还在 pending,则加入执行队列” 时,加入队列的回调函数也要添加 try catch,同时给他们添加异步回调,确保执行顺序在 executor 执行完毕之后。
- 在
resolve
和reject
函数中,当执行异步后,再次判断一下 promise 是否决议,如果已经决议,则不再执行后面的代码。
最终代码如下:
class MyPromise {
static STATE = {
PENDING: "pending",
FULFILLED: "fulfilled",
REJECTED: "rejected",
};
constructor(executor) {
this.state = MyPromise.STATE.PENDING;
this.result = undefined;
this.fulfilledCallbacks = [];
this.rejectedCallbacks = [];
this.resolve = (value) => {
if (this.state !== MyPromise.STATE.PENDING) return;
queueMicrotask(() => {
// 第二次判断是否已经决议
if (this.state !== MyPromise.STATE.PENDING) return;
this.state = MyPromise.STATE.FULFILLED;
this.result = value;
this.fulfilledCallbacks.forEach((cb) => {
cb(this.result);
});
});
};
this.reject = (reason) => {
if (this.state !== MyPromise.STATE.PENDING) return;
queueMicrotask(() => {
// 第二次判断是否已经决议
if (this.state !== MyPromise.STATE.PENDING) return;
this.state = MyPromise.STATE.REJECTED;
this.result = reason;
this.rejectedCallbacks.forEach((cb) => {
cb(this.result);
});
});
};
try {
executor(this.resolve, this.reject);
} catch (err) {
this.reject(err);
}
}
then = (onFulfilled, onRejected) => {
// 1 判断:如果不是函数
// onFulfilled将 value 原封不动的返回,
//onRejected 返回 reason, 通过 throw Error 来返回
onFulfilled =
onFulfilled instanceof Function ? onFulfilled : (value) => {return value};
onRejected =
onRejected instanceof Function ? onRejected : (value) => {throw value};
return new MyPromise((resolve, reject) => {
// 2 如果promise状态还在pending,则加入执行队列
if (this.state === MyPromise.STATE.PENDING) {
this.fulfilledCallbacks.push(() => {
queueMicrotask(() => {
try {
const value = onFulfilled(this.result);
resolve(value);
} catch (e) {
reject(e);
}
});
});
this.rejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const value = onRejected(this.result);
resolve(value);
} catch (e) {
reject(e);
}
});
});
}
// 3 如果promise状态已经确认,则异步执行回调
if (this.state === MyPromise.STATE.FULFILLED) {
queueMicrotask(() => {
try {
const value = onFulfilled(this.result);
resolve(value);
} catch (e) {
reject(e);
}
});
}
if (this.state === MyPromise.STATE.REJECTED) {
queueMicrotask(() => {
try {
const value = onRejected(this.result);
resolve(value);
} catch (e) {
reject(e);
}
});
}
});
};
}
测试 then 调用链:
const promise = new MyPromise((resolve, reject) => {
console.log("pending 状态");
setTimeout(() => {
resolve("resolve-1");
reject("reject-1");
}, 1000);
// resolve("resolve-1");
// reject("reject-1");
});
promise.then(
(res) => {
console.log("成功1", res);
return "resolve-2";
},
(err) => {
console.log("失败1", err);
}
).then(
(res) => {
console.log("成功2", res);
throw "失败了 抛出错误";
return "resolve-3";
},
(err) => {
console.log("失败2", err);
}
).then(
(res) => {
console.log(res);
},
(err) => {
console.log('失败3', err);
}
);
注意执行顺序:
-
new MyPromise(executor)
- 实例化一个 promise 对象,给 promise 对象上绑定了 status、value、reason 属性,以及 resolve、reject、exectuor 方法。
- 执行
executor(resolve, reject)
方法。- 执行 executor 逻辑 ...
- 执行 resolve 或 reject。
- 此时,遇到微任务 queueMicrotask,后续执行放入队列中。
-
.then(成功回调, 失败回调)
- 执行
promise.then()
方法,把成功回调和失败回调两个参数传递到then
中,添加到内部的onfulfilledFn
和onRejectedFn
数组上。
- 执行
-
当前队列代码执行完毕,进行微任务队列执行
-
继续执行刚才 resolve 或 reject 剩余的代码,此时的 then 已经完成了回调函数绑定,可以执行
onfulfilledFn
或onRejectedFn
数组上的回调函数了。- 改变 promise 的状态(fulfilled or rejected),表明 executor 逻辑执行完毕。
- 执行
onfulfilledFn
或onRejectedFn
数组内的全部回调。
1.3 catch 实现
直接添加该函数:
- 注意 catch 也会返回一个新的 promise,借用
return this.then()
来调用它。
catch(onRejected) {
return this.then(undefined, onRejected);
}
测试:
可以看到,第一个 then 中没有定义错误处理,所以异常继续抛出,在 catch 上成功捕获。
promise.then(
(res) => {
console.log("成功1", res);
return "resolve-2";
},
).catch(
(err) => {
console.log('catch 到错误:', err);
}
)
// pending 状态
// catch 到错误: reject-1
1.4 finally 实现
直接添加函数:
finally(onFinally) {
this.then(() => {
onFinally()
}, () => {
onFinally()
})
}
测试:
const promise = new MyPromise((resolve, reject) => {
console.log("pending 状态");
setTimeout(() => {
reject("reject-1");
resolve("resolve-1");
}, 1000);
});
promise.then(
(res) => {
console.log("成功1", res);
return "resolve-2";
},
).catch(
(err) => {
console.log('catch 到错误:', err);
}
).finally(
()=> {
console.log('执行完毕!')
})
// pending 状态
// catch 到错误: reject-1
// 执行完毕
1.5 类:resolve reject
在 MyPromise 类中添加:
static resolve(value) {
return new MyPromise((resolve) => resolve(value));
}
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason));
}
测试:
MyPromise.resolve('对了').then((res) => {console.log('resolve', res)});
// resolve 对了
MyPromise.reject('错了').then((res) => {}, (err) =>{console.log('reject', err)});
// or
MyPromise.reject('错了').catch((err) =>{console.log('reject', err)});
// reject 错了
1.6 .all .allSettled
all:全部 成功 后返回保存为数组返回,期间一旦有一个失败,直接返回失败结果。
allSettled:全部 决议 后保存为 object 放入数组返回,保存成员的决议状态(fulfilled / rejected)
确保结果返回的数组中,所有成员顺序和输入时不变:
- 遍历
promises
时不能用 forEach,而是用 for 循环,通过下标方式添加到 values 数组中。这样保证顺序不会发生改变
Promise.all([p1, p2, p3])
.then(res => {
// 返回的结果 res 顺序也应当是:[p1, p2, p3]
console.log(res)
})
最终代码如下:
class MyPromise extends Promise {
constructor(values) {
super(values);
}
static all(promises) {
// 1. 传入的参数不一定是数组对象,可以是 iterator,Array.from 转化为 array
// 2. 每个成员必须是 promise,通过回调函数包装
promises = Array.from(promises, (promise) => MyPromise.resolve(promise));
const values = [];
let count = promises.length;
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promise.then(
(res) => {
values.push(res);
// count === 0 时,返回 fulfilled 状态
if (!--count) resolve(values);
}
,(err) => {
reject(err);
}
)
};
})
}
// allSettled 最终状态一定是fulfilled
// 返回成员的不再是值,而是一个有status和value/reason属性的对象
// 最终在finally,把对象push到数组中
static allSettled(promises) {
promises = Array.from(promises, (promise) => MyPromise.resolve(promise));
const values = [];
let count = promises.length;
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
const result = {}
promises[i].then((res) => {
result.status = 'fulfilled';
result.value = res;
}, (err) => {
result.status = 'rejected';
result.reason = err;
}).finally(()=> {
values[i] = result;
// count === 0 时,返回 fulfilled 状态
if (!--count) resolve(values);
})
}
})
}
}
代码测试:
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => { resolve('p1 resolve') }, 0);
})
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => { resolve('p2 resolve') }, 100);
})
const p3= new MyPromise((resolve, reject) => {
setTimeout(() => { resolve('p3 resolve') }, 1000);
})
const p4 = new MyPromise((resolve, reject) => {
setTimeout(() => { reject('p4 reject') }, 101);
})
MyPromise.all([p1, p2, p3])
.then((res) => {
console.log('all resolved', res);
}).catch((err) => {
console.log('someone err', err);
})
// a few moments later ...
// all resolved (3) ['p1 resolve', 'p3 resolve', 'p2 resolve']
MyPromise.all([p1, p2, p3, p4])
.then((res) => {
console.log('all resolved', res);
}).catch((err) => {
console.log('someone err', err);
})
// someone err p4 reject
// 3测试入参成员是非Promise时处理
MyPromise.all([p1, p3, "heihei~"])
.then((res) => {
console.log('all resolved', res);
}).catch((err) => {
console.log('someone err', err);
})
// all resolved (3) ['heihei~', 'p1 resolve', 'p3 resolve']4
// 4 allSettled 测试
MyPromise.allSettled([p1, p2, p3, p4, 'heihei~'])
.then((res) => {
console.log('all settled:', res);
}).catch(err => console.log(err));
// 输出如下:
1.7 .race .any
race:返回第一个跨过终点线的 Promise 对象,而抛弃其他 Promise。
any:返回第一个决议为 成功 的 Promise 对象,不关心 promise 的错误结果。
- 如果全部失败,则返回特定的 reject 信息。
- 同时,
err.errors
保存了一个数组,内容是所有promise成员(都失败了)按序的失败信息。
class MyPromise extends Promise {
constructor(value) {
super(value);
}
static race(promises) {
// 解决两个问题:迭代器转数组、普通值转promise对象
promises = Array.from(promises, (promises) => MyPromise.resolve(promises));
return new MyPromise((resolve, reject) => {
promises.forEach( promise => {
promise.then((res) => {
resolve(res);
}, (err) => {
reject(err);
})
});
})
}
// 如果有一个成功,就直接返回成功
// 如果全部失败,则返回一个AggregateError,同时用 .errors 按序保存了所有错误信息
// 固只能用 for 循环来遍历 promises,以确保按序登记错误信息
static any(promises) {
promises = Array.from(promises, (promise) => MyPromise.resolve(promise));
return new MyPromise((resolve, reject) => {
const errValues = [];
let count = promises.length;
for (let i = 0; i < promises.length; i++) {
promises[i].then((res) => {
resolve(res);
}, (err) => {
errValues[i] = err;
if (!--count) { // count === 0 时,全部promise决议完毕
const message = new AggregateError('All promises were rejected');
message.errors = errValues;
reject(message);
}
})
}
})
}
}
测试:
// p1 p2 p3 p4 和 2.2 的测试举例相同,不再赘述
//* race测试
// 谁先完成就返回谁(成功/失败都算)
MyPromise.race([p2, p3, p4])
.then((res) => {
console.log('res:', res);
}).catch((err)=> {
console.log('err:', err);
})
// res: p2 resolve
// 如果传入立即值,则直接返回
MyPromise.race([p2, p3, p4, 'heihei~'])
.then((res) => {
console.log('res:', res);
}).catch((err)=> {
console.log('err:', err);
})
// res: heihei~
//* any测试
const p5 = new MyPromise((resolve, reject) => {
setTimeout(() => { reject('p5 reject') }, 3001);
})
MyPromise.any([p3, p4])
.then((res) => {
console.log('res:', res);
}).catch((err)=> {
console.log('err:', err);
})
// res: p3 resolves
// 顺序相反
MyPromise.any([p5, p4])
.then((res) => {
console.log('res:', res);
}).catch((err)=> {
console.log('err:', err);
console.log(err.errors);
})
//err: AggregateError: All promises were rejected
// (2) ['p5 reject', 'p4 reject']
1.8 如何串行执行多个 Promise
需求:希望可以让 Promise 串行调用。如下代码:传入数组 times,可以在 1s, 2s, 3s 后执行 delay 函数
function delay(time) {
return new Promise((resovle) => {
console.log(`wait ${time}s...`);
setTimeout(() => {
console.log("execute");
// @ts-ignore
resovle();
}, time * 1000);
});
}
const times = [1, 2, 3];
方法一:手动输入 then 回调
// 回调地狱
delay(1).then(() => {
delay(2).then(() => {
delay(3);
});
});
// then调用链
// then 中把delay()的结果return出去,所以可以用调用链
Promise.resolve()
.then(() => delay(1))
.then(() => delay(2))
.then(() => delay(3));
方法二:for 循环 + 外部变量
- 把 then 调用链改为循环,实现自动调用
- 从打印结果可以看到,for 循环是在第一个宏任务内就同步执行完毕了。
其实质上,这里是利用 res 做为外部指针,在每一轮 for 循环中,让 res 指向 res.then() 返回的新 prmise。在循环结束时,res 让最初的 promise 形成了方法一中的 then 调用链。
let res = Promise.resolve();
for (const time of times) {
res = res.then(() => delay(time));
console.log(res);
}
// .then(() => delay(1))
// .then(() => delay(2))
// .then(() => delay(3));
// Promise { <pending> }
// Promise { <pending> }
// Promise { <pending> }
// wait 1s...
// execute
// wait 2s...
// execute
// wait 3s...
// execute
方法三:forEach
- forEach 可以代替方法二的 for 循环 + 外部变量
// prev:promise对象,curv:delay 时间
times.reduce((prev, curv) => {
return prev.then(() => delay(curv));
}, Promise.resolve());
方法四:递归
function dispatch(index, p = Promise.resolve()) {
// 递归结束
if (!times[index]) return Promise.resolve();
// 第一轮:dispatch(1, delay(times[0]))
return p.then(() => dispatch(index + 1, delay(times[index])));
}
dispatch(0);
方法五:promise + generator
// 生成器
function* gen() {
for (const time of times) {
yield delay(time);
}
}
// 实现自动迭代
function run(gen) {
const g = gen();
function next(res) {
const result = g.next(res);
// 迭代结束
if (result.done) return result.value;
// 把delay的结果放入下一次next()迭代中
result.value.then((res) => {
next(res);
});
}
next();
}
run(gen);
方法六:async/await
- 规范化了 promise + generator
(async function () {
for (const time of times) {
await delay(time);
}
})();
方法七:for await of
for await of
和for of
规则类似,只需要实现一个内部[Symbol.asyncIterator]
方法即可
function createAsyncIterable(times) {
return {
[Symbol.asyncIterator]() {
return {
index: 0,
next() {
if (this.index < times.length) {
return delay(times[this.index]).then(() => ({ value: this.index++, done: false }));
}
return Promise.resolve({ done: true });
},
};
},
};
}
(async function () {
for await (index of createAsyncIterable(times)) {}
})();
2 Array
2.1 数组去重
方法一:双重 for + splice
function arrUnique(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--; // 删去后,j 下标不变 (j--, j++)
}
}
}
return arr;
}
const arr = [ 1, 6, 3, 7, 8, 3, 5, 8, 234, 5, 9, 34, 23, 2, 23, 3, 5, 89, 3, 4, 8, 87, 9, 23, 2,];
arrUnique(arr); // [1, 6, 3, 7, 8, 5, 234, 9, 34, 23, 2, 89, 4, 87]
[].splice(index, 删除个数, [添加成员])
对数组删除 / 替换 / 添加,改变原数组。[].slice(start, end)
,浅拷贝并返回数组,从 start 到 end(不含)。
方法二:filter + indexOf
- filter:挑选不重复的成员
- indexOf:通过下标相等判断是否重复
function arrUnique(arr) {
return arr2 = arr.filter((value, index) => {
return arr.indexOf(value) === index;
});
}
方法三:indexOf / include
- 创建一个新数组,不断往里添加不 重复的成员
function arrUnique(arr) {
const newArr = [];
for (const c of arr) {
// if (newArr.includes(c)) continue;
if (newArr.indexOf(c) !== -1) continue;
newArr.push(c);
}
return newArr;
}
方法四:reduce + includes
- reduce:相当于方法三中的创建新数组 + for 循环遍历的作用
- includes:判断 acc 中是否已经存在当前 cur
function arrUnique(arr) {
return arr.reduce((acc, cur) => {
if (!acc.includes(cur)) acc.push(cur);
return acc;
}, []);
}
方法五:sort + 快慢指针
- 原地修改
- 最终把长度截断到慢指针指向的地方
function arrUnique(arr) {
arr.sort((x, y) => x - y);
console.log(arr);
let slow = 0, fast = 1;
while (fast < arr.length) {
while (arr[fast - 1] === arr[fast]) fast++;
arr[slow] = arr[fast - 1];
slow++, fast++;
}
// 如果最后一位不重复,则slow额外赋值一次,同时 slow+1
if (arr[arr.length - 1] !== arr[arr.length - 2]) arr[slow++] = arr[arr.length - 1];
arr.length = slow;
}
换一种思路:
function arrUnique(arr) {
arr.sort((x, y) => x - y);
console.log(arr);
let slow = 1, fast = 1; // fast必须从1开始
while (fast < arr.length) {
if (arr[fast -1] !== arr[fast]) {
arr[slow] = arr[fast];
slow++;
}
fast++;
}
arr.length = slow;
}
方法六:Set
function arrUnique(arr) {
// const set = new Set(arr);
// return Array.from(set);
return Array.from(new Set(arr));
}
// shorter
const arrUnique = (arr) => [...new Set(arr)];
方法七:Map
- 思路和 Set 一样,只是多加了一层重复判断,map 中只保存不重复的值,最后遍历出来。
function arrUnique(arr) {
const map = new Map();
for (const c of arr) {
if (map.has(c)) continue;
map.set(c, true);
}
return [...map.keys()];
}
另:数组成员是对象去重
根据 name 重复,名称相同的去重。
- reduce:相当于 for 循环 + 新建一个 array
- temp:如果 已经添加过该对象,就登记并设置为 ture。
const resources = [
{ name: "张三", age: "18" },
{ name: "张三", age: "19" },
{ name: "张三", age: "20" },
{ name: "李四", age: "19" },
{ name: "王五", age: "20" },
{ name: "赵六", age: "21" },
];
const distinct = (arr) => {
const record = [];
return arr.reduce((acc, curv) => {
if (!record.includes(curv.name)) {
acc.push(curv);
record.push(curv.name);
}
return acc;
}, [])
}
console.log(distinct(resources));
// 0: {name: '张三', age: '18'}
// 1: {name: '李四', age: '19'}
// 2: {name: '王五', age: '20'}
// 3: {name: '赵六', age: '21'}
另:浅拷贝去重
function func(array) {
// 边界:不是数组
if (!Array.isArray(array)) throw new Error("data must be an array");
if (array.length <= 1) return array;
const ans = [];
const map = new Map();
array.forEach((item) => {
const str = JSON.stringify(item);
console.log(map.get(item), str);
if (map.has(str)) {
return;
} else map.set(str, item);
});
const arr = [...map];
arr.forEach((item) => ans.push(item[1]));
return ans;
}
const arr = [123, "webank", [1, 2, 3], "123", { a: 1 }, "tencent", 123, [1, 2, 3], { a: 1 }];
func(arr);
// [123, 'webank', [1, 2, 3], '123', { a: 1 }, 'tencent']
2.2 数组扁平化
实现 Array.prototype.flat()
Array.prototype.flat()
特性总结
Array.prototype.flat()
用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。- 不传参数时,默认“拉平”一层,可以传入一个整数,表示想要“拉平”的层数。
- 传入
<=0
的整数将返回原数组,不“拉平” Infinity
关键字作为参数时,无论多少层嵌套,都会转为一维数组- 如果原数组有空位,
Array.prototype.flat()
会跳过空位。
解决:判断元素是数组的方案,按照可靠性:
- 官方 API:
Array.isArray()
- 调用对象的 toString 方法:
Object.prototype.toString
- 原型链上查找原型对象:
instanceof
- 原型链上查找原型对象的构造函数属性:
constructor
另外:typeof
操作符对数组取类型将返回 object
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }];
arr instanceof Array // true
arr.constructor === Array // true
Object.prototype.toString.call(arr) === '[object Array]' // true
Array.isArray(arr) // true
解决:将元素展开一层的方案
- 展开数组:扩展运算符、
.apply()
apply(第二个参数传入数组,自动展开) - 合并多个数组:
[].concat()
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }];
// 扩展运算符 + concat
[].concat(...arr)
// [1, 2, 3, 4, 1, 2, 3, Array(4), 5, 'string', {…}]
// apply + concat
[].concat.apply([], arr);
// [1, 2, 3, 4, 1, 2, 3, Array(4), 5, 'string', {…}]
方法一:forEach + 递归
function flat(arr) {
let res = [];
deep(arr);
return res;
function deep(arr) {
arr.forEach((item) => {
if (Array.isArray(item)) deep(item); // 如果是数组,进一步展开
else res.push(item); // 如果不是数组,就加入到res中
});
}
}
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }];
flat(arr);
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, 'string', {…}]