最近在準備面試,恰好利用幾天的時間系統的整理了下JS常常考的手撕題類型。html
在這裏,以腦圖的形式帶你們手撕這篇文章裏的全部題(帶註釋)。前端
想領取腦圖的關注前端時光屋公衆號,回覆「JS手撕」,便可領取❤️面試
腦圖裏的全部題型便是本文中的30道
常考高頻題ajax
腦圖👇 跨域
淺克隆:只拷貝對象或數組的第一層內容數組
const shallClone = (target) => {
if (typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) { // 遍歷對象自身可枚舉屬性(不考慮繼承屬性和原型對象)
cloneTarget[prop] = target[prop];
}
return cloneTarget;
} else {
return target;
}
}
複製代碼
深克隆:層層拷貝對象或數組的每一層內容promise
function deepClone(target) {
if (target === null) return null;
if (typeof target !== 'object') return target;
const cloneTarget = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop]);
}
}
return cloneTarget;
}
複製代碼
const isObject = (target) =>
(typeof target === "object" || typeof target === "function") &&
target !== null;
function deepClone(target, map = new Map()) {
// 先判斷該引用類型是否被 拷貝過
if (map.get(target)) {
return target;
}
// 獲取當前值的構造函數:獲取它的類型
let constructor = target.constructor;
// 檢測當前對象target是否與 正則、日期格式對象匹配
if (/^(RegExp|Date)$/i.test(constructor.name)) {
return new constructor(target); // 建立一個新的特殊對象(正則類/日期類)的實例
}
if (isObject(target)) {
map.set(target, true); // 爲循環引用的對象作標記
const cloneTarget = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop], map);
}
}
return cloneTarget;
} else {
return target;
}
}
複製代碼
思路:瀏覽器
步驟1:先取得當前類的原型,當前實例對象的原型鏈服務器
步驟2:一直循環(執行原型鏈的查找機制)markdown
proto = proto.____proto____
,沿着原型鏈一直向上查找)____proto__
上找到了當前類的原型prototype
,則返回 true
Object.prototype.____proto____ == null
,Object的基類(null)上面都沒找到,則返回 falsefunction _instanceof (instanceObject, classFunc) {
let classFunc = classFunc.prototype; // 取得當前類的原型
let proto = instanceObject.__proto__; // 取得當前實例對象的原型鏈
while (true) {
if (proto === null) { // 找到了 Object的基類 Object.prototype.__proto__
return false;
};
if (proto === classFunc) { // 在當前實例對象的原型鏈上,找到了當前類
return true;
}
proto = proto.__proto__; // 沿着原型鏈__ptoto__一層一層向上查找
}
}
複製代碼
Object.getPrototypeOf ( ):用來獲取某個實例對象的原型(內部[[prototype]]屬性的值,包含proto屬性)
function _instanceof (instanceObject, classFunc) {
let classFunc = classFunc.prototype; // 取得當前類的原型
let proto = Object.getPrototypeOf(instanceObject); // 取得當前實例對象的原型鏈上的屬性
while (true) {
if (proto === null) { // 找到了 Object的基類 Object.prototype.__proto__
return false;
};
if (proto === classFunc) { // 在當前實例對象的原型鏈上,找到了當前類
return true;
}
proto = Object.getPrototypeOf(proto); // 沿着原型鏈__ptoto__一層一層向上查找
}
}
複製代碼
實現函數的防抖(目的是頻繁觸發中只執行一次)
/** * 實現函數的防抖(目的是頻繁觸發中只執行一次) * @param {*} func 須要執行的函數 * @param {*} wait 檢測防抖的間隔頻率 * @param {*} immediate 是不是當即執行 True:第一次,默認False:最後一次 * @return {可被調用執行的函數} */
function debounce(func, wati = 500, immediate = false) {
let timer = null
return function anonymous(... params) {
clearTimeout(timer)
timer = setTimeout(_ => {
// 在下一個500ms 執行func以前,將timer = null
//(由於clearInterval只能清除定時器,但timer還有值)
// 爲了確保後續每一次執行都和最初結果同樣,賦值爲null
// 也能夠經過 timer 是否 爲 null 是否有定時器
timer = null
func.call(this, ...params)
}, wait)
}
}
複製代碼
/** * 實現函數的防抖(目的是頻繁觸發中只執行一次) * @param {*} func 須要執行的函數 * @param {*} wait 檢測防抖的間隔頻率 * @param {*} immediate 是不是當即執行 True:第一次,默認False:最後一次 * @return {可被調用執行的函數} */
function debounce(func, wait = 500, immediate = true) {
let timer = null
return function anonymous(... params) {
// 第一點擊 沒有設置過任何定時器 timer就要爲 null
let now = immediate && !timer
clearTimeout(timer)
timer = setTimeout(_ => {
// 在下一個500ms 執行func以前,將timer = null
//(由於clearInterval只能在系統內清除定時器,但timer還有值)
// 爲了確保後續每一次執行都和最初結果同樣,賦值爲null
// 也能夠經過 timer 是否 爲 null 是否有定時器
timer = null!immediate ? func.call(this, ...params) : null
}, wait)
now ? func.call(this, ...params) : null
}
}
function func() {
console. log('ok')
}
btn. onclick = debounce(func, 500)
複製代碼
實現函數的節流 (目的是頻繁觸發中縮減頻率)
【第一次觸發:reamining是負數,previous被賦值爲當前時間】
【第二次觸發:假設時間間隔是500ms,第一次執行完以後,20ms
以後,當即觸發第二次,則remaining = 500 - ( 新的當前時間 - 上一次觸發時間 ) = 500 - 20 = 480 】
/** * 實現函數的節流 (目的是頻繁觸發中縮減頻率) * @param {*} func 須要執行的函數 * @param {*} wait 檢測節流的間隔頻率 * @param {*} immediate 是不是當即執行 True:第一次,默認False:最後一次 * @return {可被調用執行的函數} */
function throttle(func, wait) {
let timer = null
let previous = 0 // 記錄上一次操做的時間點
return function anonymous(... params) {
let now = new Date() // 當前操做的時間點
remaining = wait - (now - previous) // 剩下的時間
if (remaining <= 0) {
// 兩次間隔時間超過頻率,把方法執行
clearTimeout(timer); // clearTimeout是從系統中清除定時器,但timer值不會變爲null
timer = null; // 後續能夠經過判斷 timer是否爲null,而判斷是否有 定時器
// 此時已經執行func 函數,應該將上次觸發函數的時間點 = 如今觸發的時間點 new Date()
previous = new Date(); // 把上一次操做時間修改成當前時間
func.call(this, ...params);
} else if(!timer){
// 兩次間隔的事件沒有超過頻率,說明尚未達到觸發標準,設置定時器等待便可(還差多久等多久)
// 假設事件間隔爲500ms,第一次執行完以後,20ms後再次點擊執行,則剩餘 480ms,就能等待480ms
timer = setTimeout( _ => {
clearTimeout(timer)
timer = null // 確保每次執行完的時候,timer 都清 0,回到初始狀態
//過了remaining時間後,纔去執行func,因此previous不能等於初始時的 now
previous = new Date(); // 把上一次操做時間修改成當前時間
func.call(this, ...params);
}, remaining)
}
}
}
function func() {
console. log('ok')
}
btn. onclick = throttle(func, 500)
複製代碼
/** * 實現函數的節流 (目的是頻繁觸發中縮減頻率) * @param {*} func 須要執行的函數 * @param {*} wait 檢測節流的間隔頻率 * @param {*} immediate 是不是當即執行 True:第一次,默認False:最後一次 * @return {可被調用執行的函數} */
function throttle(func, wait) {
let timer = null;
let previous = 0;
return function anonymous(... params) {
let now = new Date();
remaining = wait - (now - previous);
if (remaining <= 0) {
clearTimeout(timer);
timer = null;
previous = new Date();
func.call(this, ...params);
} else if(!timer){
timer = setTimeout( _ => {
clearTimeout(timer);
timer = null;
previous = new Date();
func.call(this, ...params);
}, remaining)
}
}
}
function func() {
console. log('ok')
}
btn. onclick = throttle(func, 500);
複製代碼
Object.create() = function create(prototype) {
// 排除傳入的對象是 null 和 非object的狀況
if (prototype === null || typeof prototype !== 'object') {
throw new TypeError(`Object prototype may only be an Object: ${prototype}`);
}
// 讓空對象的 __proto__指向 傳進來的 對象(prototype)
// 目標 {}.__proto__ = prototype
function Temp() {};
Temp.prototype = prototype;
return new Temp;
}
複製代碼
/** * Func: 要操做的類(最後要建立這個類的實例) * args:存儲將來傳遞給Func類的實參 */
function _new(Func, ...args) {
// 建立一個Func的實例對象(實例.____proto____ = 類.prototype)
let obj = {};
obj.__proto__ = Func.prototype;
// 把Func當作普通函數執行,並改變this指向
let result = Func.call(obj, ...args);
// 分析函數的返回值
if (result !== null && /^(object|function)$/.test(typeof result)) {
return result;
}
return obj;
}
複製代碼
proto 在IE瀏覽器中不支持
let x = { name: "lsh" };
Object.create(x);
{}.__proto__ = x;
複製代碼
function _new(Func, ...args) {
// let obj = {};
// obj.__proto__ = Func.prototype;
// 建立一個Func的實例對象(實例.____proto____ = 類.prototype)
let obj = Object.create(Func.prototype);
// 把Func當作普通函數執行,並改變this指向
let result = Func.call(obj, ...args);
// 分析函數的返回值
if (result !== null && /^(object|function)$/.test(typeof result)) {
return result;
}
return obj;
}
複製代碼
/** * context: 要改變的函數中的this指向,寫誰就是誰 * args:傳遞給函數的實參信息 * this:要處理的函數 fn */
Function.prototype.call = function(context, ...args) {
// null,undefined,和不傳時,context爲 window
context = context == null ? window : context;
let result;
context['fn'] = this; // 把函數做爲對象的某個成員值
result = context['fn'](...args); // 把函數執行,此時函數中的this就是
delete context['fn']; // 設置完成員屬性後,刪除
return result;
}
複製代碼
/** * context: 要改變的函數中的this指向,寫誰就是誰 * args:傳遞給函數的實參信息 * this:要處理的函數 fn */
Function.prototype.call = function(context, ...args) {
// null,undefined,和不傳時,context爲 window
context = context == null ? window : context;
// 必須保證 context 是一個對象類型
let contextType = typeof context;
if (!/^(object|function)$/i.test(contextType)) {
// context = new context.constructor(context); // 不適用於 Symbol/BigInt
context = Object(context);
}
let result;
context['fn'] = this; // 把函數做爲對象的某個成員值
result = context['fn'](...args); // 把函數執行,此時函數中的this就是
delete context['fn']; // 設置完成員屬性後,刪除
return result;
}
複製代碼
/** * context: 要改變的函數中的this指向,寫誰就是誰 * args:傳遞給函數的實參信息 * this:要處理的函數 fn */
Function.prototype.apply = function(context, args) {
context = context == null ? window : context;
let contextType = typeof context;
if (!/^(object|function)$/i.test(contextType)) {
context = Object(context);
}
let result;
context['fn'] = this;
result = context['fn'](...args);
delete context['fn'];
return result;
}
複製代碼
/** * this: 要處理的函數 func * context: 要改變的函數中的this指向 obj * params:要處理的函數傳遞的實參 [10, 20] */
Function.prototype._bind = function(context, ...params) {
let _this = this; // this: 要處理的函數
return function anonymous (...args) {
// args: 可能傳遞的事件對象等信息 [MouseEvent]
// this:匿名函數中的this是由當初綁定的位置 觸發決定的 (總之不是要處理的函數func)
// 因此須要_bind函數 剛進來時,保存要處理的函數 _this = this
_this.call(context, ...params.concat(args));
}
}
複製代碼
思路:
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
function myFlat() {
_this = this; // 保存 this:arr
let newArr = [];
// 循環arr中的每一項,把不是數組的元素存儲到 newArr中
let cycleArray = (arr) => {
for (let i=0; i< arr.length; i++) {
let item = arr[i];
if (Array.isArray(item)) { // 元素是數組的話,繼續循環遍歷該數組
cycleArray(item);
continue;
} else{
newArr.push(item); // 不是數組的話,直接添加到新數組中
}
}
}
cycleArray(_this); // 循環數組裏的每一個元素
return newArr; // 返回新的數組對象
}
Array.prototype.myFlat = myFlat;
arr = arr.myFlat(); // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
複製代碼
const myFlat = (arr) => {
let newArr = [];
let cycleArray = (arr) => {
for(let i = 0; i < arr.length; i++) {
let item = arr[i];
if (Array.isArray(item)) {
cycleArray(item);
continue;
} else {
newArr.push(item);
}
}
}
cycleArray(arr);
return newArr;
}
myFlat(arr); // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
複製代碼
根據Array.isArray
逐個判斷數組裏的每一項是否爲數組元素
const myFlat = arr => {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? myFlat(cur) : cur);
}, []);
};
console.log(myFlat(arr));
// [12, 23, 34, 56, 78, 90, 100, 110, 120, 130, 140]
複製代碼
let arr = [12, 23, 12, 15, 25, 23, 16, 25, 16];
for(let i = 0; i < arr.length - 1; i++) {
let item = arr[i]; // 取得當前數組中的每一項
let remainArgs = arr.slice(i+1); // 從 i+1項開始截取數組中剩餘元素,包括i+1位置的元素
if (remainArgs.indexOf(item) > -1) { // 數組的後面元素 包含當前項
arr[i] = arr[arr.length - 1]; // 用數組最後一項替換當前項
arr.length--; // 刪除數組最後一項
i--; // 仍從當前項開始比較
}
}
console.log(arr); // [ 16, 23, 12, 15, 25 ]
複製代碼
思想:
把數組元素做爲
對象屬性
經過遍歷數組,
判斷數組元素是否已是對象的屬性
,若是對象屬性定義過,則證實是重複元素,進而刪除重複元素
let obj = {};
for (let i=0; i < arr.length; i++) {
let item = arr[i]; // 取得當前項
if (typeof obj[item] !== 'undefined') {
// obj 中存在當前屬性,則證實當前項 以前已是 obj屬性了
// 刪除當前項
arr[i] = arr[arr.length-1];
arr.length--;
i--;
}
obj[item] = item; // obj {10: 10, 16: 16, 25: 25 ...}
}
obj = null; // 垃圾回收
console.log(arr); // [ 16, 23, 12, 15, 25 ]
複製代碼
let arr = [12, 23, 12, 15, 25, 23, 16, 25, 16];
arr.sort((a,b) => a-b);
arrStr = arr.join('@') + '@';
let reg = /(\d+@)\1*/g,
newArr = [];
arrStr.replace(reg, (val, group1) => {
// newArr.push(Number(group1.slice(0, group1.length-1)));
newArr.push(parseFloat(group1));
})
console.log(newArr); // [ 12, 15, 16, 23, 25 ]
複製代碼
**核心:**傳遞給我一個
Generator
函數,把函數中的內容基於Iterator
迭代器的特色一步步的執行
function readFile(file) {
return new Promise(resolve => {
setTimeout(() => {
resolve(file);
}, 1000);
})
};
function asyncFunc(generator) {
const iterator = generator(); // 接下來要執行next
// data爲第一次執行以後的返回結果,用於傳給第二次執行
const next = (data) => {
let { value, done } = iterator.next(data); // 第二次執行,並接收第一次的請求結果 data
if (done) return; // 執行完畢(到第三次)直接返回
// 第一次執行next時,yield返回的 promise實例 賦值給了 value
value.then(data => {
next(data); // 當第一次value 執行完畢且成功時,執行下一步(並把第一次的結果傳遞下一步)
});
}
next();
};
asyncFunc(function* () {
// 生成器函數:控制代碼一步步執行
let data = yield readFile('a.js'); // 等這一步驟執行執行成功以後,再往下走,沒執行完的時候,直接返回
data = yield readFile(data + 'b.js');
return data;
})
複製代碼
思路:
返回一個新的Promise
實例
建立HMLHttpRequest
異步對象
調用open
方法,打開url
,與服務器創建連接(發送前的一些處理)
監聽Ajax
狀態信息
若是xhr.readyState == 4
(表示服務器響應完成,能夠獲取使用服務器的響應了)
xhr.status == 200
,返回resolve
狀態xhr.status == 404
,返回reject
狀態xhr.readyState !== 4
,把請求主體的信息基於send
發送給服務器
function ajax(url, method) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open(url, method, true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText)
} else if (xhr.status === 404) {
reject(new Error('404'))
}
} else {
reject('請求數據失敗')
}
}
xhr.send(null)
})
}
複製代碼
思路:
script
標籤script
標籤的src
屬性,以問號傳遞參數
,設置好回調函數callback
名稱html
文本中res
參數就是獲取的數據let script = document.createElement('script');
script.src = 'http://www.baidu.cn/login?username=JasonShu&callback=callback';
document.body.appendChild(script);
function callback (res) {
console.log(res);
}
複製代碼
某個時間事後,就去執行某個函數,基於Promise
封裝異步任務
/** * * @param {*} fn 要執行的函數 * @param {*} wait 等待的時間 */
function sleep(wait) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, wait);
});
}
let sayHello = (name) => console.log(`hello ${name}`);
async function autoRun() {
await sleep(3000);
let demo1 = sayHello("時光屋小豪");
let demo2 = sayHello("掘友們");
let demo3 = sayHello("aaa");
}
autoRun();
複製代碼
特色:
初始值不傳時的特殊處理:會默認使用數組中的第一個元素
prev
arr.reduce(prev, next, currentIndex, array))
)
prev
:上一次調用回調時返回的值Array.prototype.myReduce = function(fn, prev) {
for (let i = 0; i < this.length; i++) {
if (typeof prev === 'undefined') {
prev = fn(this[i], this[i+1], i+1, this);
++i;
} else {
prev = fn(prev, this[i], i, this);
}
}
return prev
}
複製代碼
測試用例
let sum = [1, 2, 3].myReduce((prev, next) => {
return prev + next
});
console.log(sum); // 6
複製代碼
**柯理化函數含義:**是給函數分步傳遞參數,每次傳遞部分參數,並返回一個更具體的函數接收剩下的參數,這中間可嵌套多層這樣的接收部分參數的函數,直至返回最後結果。
// add的參數不固定,看有幾個數字累計相加
function add (a,b,c,d) {
return a+b+c+d
}
function currying (fn, ...args) {
// fn.length 回調函數的參數的總和
// args.length currying函數 後面的參數總和
// 如:add (a,b,c,d) currying(add,1,2,3,4)
if (fn.length === args.length) {
return fn(...args)
} else {
// 繼續分步傳遞參數 newArgs 新一次傳遞的參數
return function anonymous(...newArgs) {
// 將先傳遞的參數和後傳遞的參數 結合在一塊兒
let allArgs = [...args, ...newArgs]
return currying(fn, ...allArgs)
}
}
}
let fn1 = currying(add, 1, 2) // 3
let fn2 = fn1(3) // 6
let fn3 = fn2(4) // 10
複製代碼
寄生組合繼承(ES5繼承的最佳方式)
所謂寄生組合式繼承,即經過借用構造函數來繼承屬性
,經過原型鏈的形式來繼承方法
。
只調用了一次父類
構造函數,效率更高。避免在子類.prototype
上面建立沒必要要的、多餘的屬性,與其同時,原型鏈還能保持不變。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
return this.name;
}
function Child(name, age) {
Parent.call(this, name); // 調用父類的構造函數,將父類構造函數內的this指向子類的實例
this.age = age;
}
//寄生組合式繼承
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.getAge = function () {
return this.age;
}
let girl = new Child('Lisa', 18);
girl.getName();
複製代碼
發佈訂閱的核心:: 每次event. emit(發佈),就會觸發一次event. on(註冊)
class EventEmitter {
constructor() {
// 事件對象,存放訂閱的名字和事件
this.events = {};
}
// 訂閱事件的方法
on(eventName,callback) {
if (!this.events[eventName]) {
// 注意數據,一個名字能夠訂閱多個事件函數
this.events[eventName] = [callback];
} else {
// 存在則push到指定數組的尾部保存
this.events[eventName].push(callback)
}
}
// 觸發事件的方法
emit(eventName) {
// 遍歷執行全部訂閱的事件
this.events[eventName] && this.events[eventName].forEach(cb => cb());
}
}
複製代碼
測試用例
let em = new EventEmitter();
function workDay() {
console.log("天天工做");
}
function makeMoney() {
console.log("賺100萬");
}
function sayLove() {
console.log("向喜歡的人示愛");
}
em.on("money",makeMoney);
em.on("love",sayLove);
em.on("work", workDay);
em.emit("money");
em.emit("love");
em.emit("work");
複製代碼
觀察者模式(基於發佈訂閱模式) 有觀察者,也有被觀察者
觀察者須要放到被觀察者中
,被觀察者的狀態變化須要通知觀察者
我變化了 內部也是基於發佈訂閱模式,收集觀察者,狀態變化後要主動通知觀察者
class Subject { // 被觀察者 學生
constructor(name) {
this.state = '開心的'
this.observers = []; // 存儲全部的觀察者
}
// 收集全部的觀察者
attach(o){ // Subject. prototype. attch
this.observers.push(o)
}
// 更新被觀察者 狀態的方法
setState(newState) {
this.state = newState; // 更新狀態
// this 指被觀察者 學生
this.observers.forEach(o => o.update(this)) // 通知觀察者 更新它們的狀態
}
}
class Observer{ // 觀察者 父母和老師
constructor(name) {
this.name = name
}
update(student) {
console.log('當前' + this.name + '被通知了', '當前學生的狀態是' + student.state)
}
}
let student = new Subject('學生');
let parent = new Observer('父母');
let teacher = new Observer('老師');
// 被觀察者存儲觀察者的前提,須要先接納觀察者
student. attach(parent);
student. attach(teacher);
student. setState('被欺負了');
複製代碼
Object.freeze
凍結一個對象,讓其不能再添加/刪除屬性,也不能修改該對象已有屬性的可枚舉性、可配置可寫性
,也不能修改已有屬性的值和它的原型屬性,最後返回一個和傳入參數相同的對象。
function myFreeze(obj){
// 判斷參數是否爲Object類型,若是是就封閉對象,循環遍歷對象。去掉原型屬性,將其writable特性設置爲false
if(obj instanceof Object){
Object.seal(obj); // 封閉對象
for(let key in obj){
if(obj.hasOwnProperty(key)){
Object.defineProperty(obj,key,{
writable:false // 設置只讀
})
// 若是屬性值依然爲對象,要經過遞歸來進行進一步的凍結
myFreeze(obj[key]);
}
}
}
}
複製代碼
Promise.all
:有一個promise任務失敗就所有失敗
Promise.all
方法返回的是一個promise
function isPromise (val) {
return typeof val.then === 'function'; // (123).then => undefined
}
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
let arr = []; // 存放 promise執行後的結果
let index = 0; // 計數器,用來累計promise的已執行次數
const processData = (key, data) => {
arr[key] = data; // 不能使用數組的長度來計算
/* if (arr.length == promises.length) { resolve(arr); // [null, null , 1, 2] 因爲Promise異步比較慢,因此還未返回 } */
if (++index === promises.length) {
// 必須保證數組裏的每個
resolve(arr);
}
}
// 遍歷數組依次拿到執行結果
for (let i = 0; i < promises.length; i++) {
let result = promises[i];
if(isPromise(result)) {
// 讓裏面的promise執行,取得成功後的結果
// data promise執行後的返回結果
result.then((data) => {
// 處理數據,按照原數組的順序依次輸出
processData(i ,data)
}, reject) // reject本事就是個函數 因此簡寫了
} else {
// 1 , 2
processData(i ,result)
}
}
})
}
複製代碼
測試用例
let fs = require('fs').promises;
let getName = fs.readFile('./name.txt', 'utf8');
let getAge = fs.readFile('./age.txt', 'utf8');
Promise.all([1, getName, getAge, 2]).then(data => {
console.log(data); // [ 1, 'name', '11', 2 ]
})
複製代碼
MDN:
Promise.allSettled()
方法返回一個在全部給定的promise都已經fulfilled
或rejected
後的promise,並帶有一個對象數組,每一個對象表示對應的promise結果。
當您有多個彼此不依賴的異步任務成功完成時,或者您老是想知道每一個
promise
的結果時,一般使用它。
【譯】Promise.allSettled
跟Promise.all
相似, 其參數接受一個Promise
的數組, 返回一個新的Promise
, 惟一的不一樣在於, 其不會進行短路, 也就是說當Promise
所有處理完成後咱們能夠拿到每一個Promise
的狀態, 而無論其是否處理成功。
let fs = require('fs').promises;
let getName = fs.readFile('./name.txt', 'utf8'); // 讀取文件成功
let getAge = fs.readFile('./age.txt', 'utf8');
Promise.allSettled([1, getName, getAge, 2]).then(data => {
console.log(data);
});
// 輸出結果
/* [ { status: 'fulfilled', value: 1 }, { status: 'fulfilled', value: 'zf' }, { status: 'fulfilled', value: '11' }, { status: 'fulfilled', value: 2 } ] */
let getName = fs.readFile('./name123.txt', 'utf8'); // 讀取文件失敗
let getAge = fs.readFile('./age.txt', 'utf8');
// 輸出結果
/* [ { status: 'fulfilled', value: 1 }, { status: 'rejected', value: [Error: ENOENT: no such file or directory, open './name123.txt'] { errno: -2, code: 'ENOENT', syscall: 'open', path: './name123.txt' } }, { status: 'fulfilled', value: '11' }, { status: 'fulfilled', value: 2 } ] */
複製代碼
function isPromise (val) {
return typeof val.then === 'function'; // (123).then => undefined
}
Promise.allSettled = function(promises) {
return new Promise((resolve, reject) => {
let arr = [];
let times = 0;
const setData = (index, data) => {
arr[index] = data;
if (++times === promises.length) {
resolve(arr);
}
console.log('times', times)
}
for (let i = 0; i < promises.length; i++) {
let current = promises[i];
if (isPromise(current)) {
current.then((data) => {
setData(i, { status: 'fulfilled', value: data });
}, err => {
setData(i, { status: 'rejected', value: err })
})
} else {
setData(i, { status: 'fulfilled', value: current })
}
}
})
}
複製代碼
前面的promise
無論成功仍是失敗,都會走到finally
中,而且finally
以後,還能夠繼續then
(說明它仍是一個then
方法是關鍵
),而且會將初始的promise
值原封不動的傳遞給後面的then.
finally
裏的函數,不管如何都會執行,並會把前面的值原封不動傳遞給下一個then
方法中
(至關於起了一箇中間過渡的做用)——對應狀況1,2,3
若是finally
函數中有promise
等異步任務,會等它們所有執行完畢,再結合以前的成功與否狀態,返回值
// 狀況1
Promise.resolve(123).finally((data) => { // 這裏傳入的函數,不管如何都會執行
console.log(data); // undefined
})
// 狀況2 (這裏,finally方法至關於作了中間處理,起一個過渡的做用)
Promise.resolve(123).finally((data) => {
console.log(data); // undefined
}).then(data => {
console.log(data); // 123
})
// 狀況3 (這裏只要reject,都會走到下一個then的err中)
Promise.reject(123).finally((data) => {
console.log(data); // undefined
}).then(data => {
console.log(data);
}, err => {
console.log(err, 'err'); // 123 err
})
// 狀況4 (一開始就成功以後,會等待finally裏的promise執行完畢後,再把前面的data傳遞到下一個then中)
Promise.resolve(123).finally((data) => {
console.log(data); // undefined
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, 3000)
})
}).then(data => {
console.log(data, 'success'); // 123 success
}, err => {
console.log(err, 'err');
})
// 狀況5 (雖然一開始成功,可是隻要finally函數中的promise失敗了,就會把其失敗的值傳遞到下一個then的err中)
Promise.resolve(123).finally((data) => {
console.log(data); // undefined
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('rejected');
}, 3000)
})
}).then(data => {
console.log(data, 'success');
}, err => {
console.log(err, 'err'); // rejected err
})
// 狀況6 (雖然一開始失敗,可是也要等finally中的promise執行完,才能把一開始的err傳遞到err的回調中)
Promise.reject(123).finally((data) => {
console.log(data); // undefined
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolve');
}, 3000)
})
}).then(data => {
console.log(data, 'success');
}, err => {
console.log(err, 'err'); // 123 err
})
複製代碼
Promise.prototype.finally = function (callback) {
return this.then((data) => {
// 讓函數執行 內部會調用方法,若是方法是promise,須要等待它完成
// 若是當前promise執行時失敗了,會把err傳遞到,err的回調函數中
return Promise.resolve(callback()).then(() => data); // data 上一個promise的成功態
}, err => {
return Promise.resolve(callback()).then(() => {
throw err; // 把以前的失敗的err,拋出去
});
})
}
複製代碼
詳見腦圖哦~
若是你以爲這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
點贊,轉發,有大家的 『點贊和評論』,纔是我創造的動力。
關注公衆號 『前端時光屋』,不按期分享原創知識。
同時能夠期待後續文章ing🚀