30道高頻JS手撕面試題
前言
最近在準備面試,剛好利用幾天的時間系統(tǒng)的整理了下JS經(jīng)??嫉氖炙侯}類型。
在這里,以腦圖的形式帶大家手撕這篇文章里的所有題(帶注釋)。
想領(lǐng)取腦圖的關(guān)注前端時光屋公眾號,回復(fù)「JS手撕」,即可領(lǐng)取??
腦圖里的所有題型即是本文中的30道常考高頻題
腦圖?

1.手動實現(xiàn)一個淺克隆
淺克?。?/strong> 只拷貝對象或數(shù)組的第一層內(nèi)容
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;
??}
}
2.手動實現(xiàn)一個深克?。ê喴装妫?/span>
深克?。?/strong> 層層拷貝對象或數(shù)組的每一層內(nèi)容
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;
}
3.手動實現(xiàn)一個深克隆(考慮日期/正則等特殊對象 和 解決循環(huán)引用情況)
const?isObject?=?(target)?=>?(typeof?target?===?'object'?||?typeof?target?===?'function')?&&?target?!==?null;
function?deepClone?(target,?map?=?new?Map())?{
??//?先判斷該引用類型是否被?拷貝過
??if?(map.get(target))?{
????return?target;
??}
??//?獲取當前值的構(gòu)造函數(shù):獲取它的類型
??let?constructor?=?target.constructor;
??//?檢測當前對象target是否與?正則、日期格式對象匹配
??if?(/^(RegExp|Date)$/i.test(constructor.name)){
????return?new?constructor(target);?//?創(chuàng)建一個新的特殊對象(正則類/日期類)的實例
??}
??if?(isObject(target))?{
????map.set(target,?true);?//?為循環(huán)引用的對象做標記
????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;
??}
}
4.手動實現(xiàn)instanceOf的機制
思路:
步驟1: 先取得當前類的原型,當前實例對象的原型鏈
步驟2: 一直循環(huán)(執(zhí)行原型鏈的查找機制)
取得當前實例對象原型鏈的原型鏈(
proto = proto.__proto__,沿著原型鏈一直向上查找)如果 當前實例的原型鏈
__proto__上找到了當前類的原型prototype,則返回true如果 一直找到
Object.prototype.__proto__ == null,Object的基類(null)上面都沒找到,則返回 false
function?_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__一層一層向上查找
??}
}
優(yōu)化版 (處理兼容問題)
Object.getPrototypeOf:用來獲取某個實例對象的原型(內(nèi)部[[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__一層一層向上查找
??}
}
5. 手動實現(xiàn)防抖函數(shù)
實現(xiàn)函數(shù)的防抖(目的是頻繁觸發(fā)中只執(zhí)行一次)
以最后一次觸發(fā)為標準
/**
?*?實現(xiàn)函數(shù)的防抖(目的是頻繁觸發(fā)中只執(zhí)行一次)
?*?@param?{*}?func?需要執(zhí)行的函數(shù)
?*?@param?{*}?wait?檢測防抖的間隔頻率
?*?@param?{*}?immediate?是否是立即執(zhí)行??True:第一次,默認False:最后一次
?*?@return?{可被調(diào)用執(zhí)行的函數(shù)}
?*/
function?debounce(func,?wati?=?500,?immediate?=?false)?{
??let?timer?=?null
??return?function?anonymous(...?params)?{
????clearTimeout(timer)
????timer?=?setTimeout(_?=>?{
??????//?在下一個500ms?執(zhí)行func之前,將timer?=?null
??????//(因為clearInterval只能清除定時器,但timer還有值)
??????//?為了確保后續(xù)每一次執(zhí)行都和最初結(jié)果一樣,賦值為null
??????//?也可以通過?timer?是否?為?null?是否有定時器
??????timer?=?null
??????func.call(this,?...params)
????},?wait)
??}
}
以第一次觸發(fā)為標準
/**
?*?實現(xiàn)函數(shù)的防抖(目的是頻繁觸發(fā)中只執(zhí)行一次)
?*?@param?{*}?func?需要執(zhí)行的函數(shù)
?*?@param?{*}?wait?檢測防抖的間隔頻率
?*?@param?{*}?immediate?是否是立即執(zhí)行?True:第一次,默認False:最后一次
?*?@return?{可被調(diào)用執(zhí)行的函數(shù)}
?*/
function?debounce(func,?wait?=?500,?immediate?=?true)?{
??let?timer?=?null
??return?function?anonymous(...?params)?{
????//?第一點擊?沒有設(shè)置過任何定時器?timer就要為?null
????let?now?=?immediate?&&?!timer
????clearTimeout(timer)
????timer?=?setTimeout(_?=>?{
??????//?在下一個500ms?執(zhí)行func之前,將timer?=?null
??????//(因為clearInterval只能在系統(tǒng)內(nèi)清除定時器,但timer還有值)
??????//?為了確保后續(xù)每一次執(zhí)行都和最初結(jié)果一樣,賦值為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)
6.手動實現(xiàn)節(jié)流函數(shù)
實現(xiàn)函數(shù)的節(jié)流 (目的是頻繁觸發(fā)中縮減頻率)
帶注釋說明版
【第一次觸發(fā):reamining是負數(shù),previous被賦值為當前時間】
【第二次觸發(fā):假設(shè)時間間隔是500ms,第一次執(zhí)行完之后,
20ms之后,立即觸發(fā)第二次,則remaining = 500 - ( 新的當前時間 - 上一次觸發(fā)時間 ) = 500 - 20 = 480 】
/**
?*?實現(xiàn)函數(shù)的節(jié)流?(目的是頻繁觸發(fā)中縮減頻率)
?*?@param?{*}?func?需要執(zhí)行的函數(shù)
?*?@param?{*}?wait?檢測節(jié)流的間隔頻率
?*?@param?{*}?immediate?是否是立即執(zhí)行?True:第一次,默認False:最后一次
?*?@return?{可被調(diào)用執(zhí)行的函數(shù)}
?*/
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)?{
??????//?兩次間隔時間超過頻率,把方法執(zhí)行
??????
??????clearTimeout(timer);?//?clearTimeout是從系統(tǒng)中清除定時器,但timer值不會變?yōu)閚ull
??????timer?=?null;?//?后續(xù)可以通過判斷?timer是否為null,而判斷是否有?定時器
??????
??????//?此時已經(jīng)執(zhí)行func?函數(shù),應(yīng)該將上次觸發(fā)函數(shù)的時間點?=?現(xiàn)在觸發(fā)的時間點?new?Date()
??????previous?=?new?Date();?//?把上一次操作時間修改為當前時間
??????func.call(this,?...params);
????}?else?if(!timer){?
??????
??????//?兩次間隔的事件沒有超過頻率,說明還沒有達到觸發(fā)標準,設(shè)置定時器等待即可(還差多久等多久)
??????//?假設(shè)事件間隔為500ms,第一次執(zhí)行完之后,20ms后再次點擊執(zhí)行,則剩余?480ms,就能等待480ms
??????timer?=?setTimeout(?_?=>?{
????????clearTimeout(timer)
????????timer?=?null?//?確保每次執(zhí)行完的時候,timer?都清?0,回到初始狀態(tài)
????????
????????//過了remaining時間后,才去執(zhí)行func,所以previous不能等于初始時的?now
????????previous?=?new?Date();?//?把上一次操作時間修改為當前時間
????????func.call(this,?...params);
??????},?remaining)
????}
??}
}
function?func()?{
??console.?log('ok')
}
btn.?onclick?=?throttle(func,?500)
不帶注釋版
/**
?*?實現(xiàn)函數(shù)的節(jié)流?(目的是頻繁觸發(fā)中縮減頻率)
?*?@param?{*}?func?需要執(zhí)行的函數(shù)
?*?@param?{*}?wait?檢測節(jié)流的間隔頻率
?*?@param?{*}?immediate?是否是立即執(zhí)行?True:第一次,默認False:最后一次
?*?@return?{可被調(diào)用執(zhí)行的函數(shù)}
?*/
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);
7.手動實現(xiàn)Object.create
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;
}
8.手動實現(xiàn)內(nèi)置new的原理
簡化版
步驟1: 創(chuàng)建一個Func的實例對象(實例.proto = 類.prototype)
步驟2: 把
Func當做普通函數(shù)執(zhí)行,并改變this指向步驟3: 分析函數(shù)的返回值
/**
??*?Func:?要操作的類(最后要創(chuàng)建這個類的實例)
??*?args:存儲未來傳遞給Func類的實參
??*/
function?_new(Func,?...args)?{
??
??//?創(chuàng)建一個Func的實例對象(實例.____proto____?=?類.prototype)
??let?obj?=?{};
??obj.__proto__?=?Func.prototype;
??
??//?把Func當做普通函數(shù)執(zhí)行,并改變this指向
??let?result?=?Func.call(obj,?...args);
??
??//?分析函數(shù)的返回值
??if?(result?!==?null?&&?/^(object|function)$/.test(typeof?result))?{
????return?result;
?}
??return?obj;
}
優(yōu)化版
__proto__在IE瀏覽器中不支持
let?x?=?{?name:?"lsh"?};
Object.create(x);
{}.__proto__?=?x;
function?_new(Func,?...args)?{
??
??//?let?obj?=?{};
??//?obj.__proto__?=?Func.prototype;
??//?創(chuàng)建一個Func的實例對象(實例.____proto____?=?類.prototype)
??let?obj?=?Object.create(Func.prototype);
??
??//?把Func當做普通函數(shù)執(zhí)行,并改變this指向
??let?result?=?Func.call(obj,?...args);
??
??//?分析函數(shù)的返回值
??if?(result?!==?null?&&?/^(object|function)$/.test(typeof?result))?{
????return?result;
??}
??return?obj;
}
9.手動實現(xiàn)call方法
簡易版(不考慮context非對象情況,不考慮Symbol\BigInt 不能 new.constructor( context )情況)
/**
?*?context:?要改變的函數(shù)中的this指向,寫誰就是誰
?*?args:傳遞給函數(shù)的實參信息
?*?this:要處理的函數(shù)?fn
?*/
Function.prototype.call?=?function(context,?...args)?{
?//??null,undefined,和不傳時,context為?window
??context?=?context?==?null???window?:?context;
??
??let?result;
??context['fn']?=?this;?//?把函數(shù)作為對象的某個成員值
??result?=?context['fn'](...args);?//?把函數(shù)執(zhí)行,此時函數(shù)中的this就是
?delete?context['fn'];?//?設(shè)置完成員屬性后,刪除
??return?result;
}
完善版(context必須對象類型,兼容Symbol等情況)
/**
?*?context:?要改變的函數(shù)中的this指向,寫誰就是誰
?*?args:傳遞給函數(shù)的實參信息
?*?this:要處理的函數(shù)?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;?//?把函數(shù)作為對象的某個成員值
??result?=?context['fn'](...args);?//?把函數(shù)執(zhí)行,此時函數(shù)中的this就是
?delete?context['fn'];?//?設(shè)置完成員屬性后,刪除
??return?result;
}
10.手動實現(xiàn)apply方法
/**
?*?context:?要改變的函數(shù)中的this指向,寫誰就是誰
?*?args:傳遞給函數(shù)的實參信息
?*?this:要處理的函數(shù)?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;
}
11.手動實現(xiàn)bind方法
/**
?*?this:?要處理的函數(shù)?func
?*?context:?要改變的函數(shù)中的this指向?obj
?*?params:要處理的函數(shù)傳遞的實參?[10,?20]
?*/
Function.prototype._bind?=?function(context,?...params)?{
??
??let?_this?=?this;?//?this:?要處理的函數(shù)
??return?function?anonymous?(...args)?{
????//?args:?可能傳遞的事件對象等信息?[MouseEvent]
????//?this:匿名函數(shù)中的this是由當初綁定的位置?觸發(fā)決定的?(總之不是要處理的函數(shù)func)
????//?所以需要_bind函數(shù)?剛進來時,保存要處理的函數(shù)?_this?=?this
????_this.call(context,?...params.concat(args));
??}
}
12.ES5實現(xiàn)數(shù)組扁平化flat方法
思路:
循環(huán)數(shù)組里的每一個元素 判斷該元素是否為數(shù)組 是數(shù)組的話,繼續(xù)循環(huán)遍歷這個元素——數(shù)組 不是數(shù)組的話,把元素添加到新的數(shù)組中
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?=?[];
??//?循環(huán)arr中的每一項,把不是數(shù)組的元素存儲到?newArr中
??let?cycleArray?=?(arr)?=>?{
????for?(let?i=0;?i??????let?item?=?arr[i];
??????if?(Array.isArray(item))?{?//?元素是數(shù)組的話,繼續(xù)循環(huán)遍歷該數(shù)組
????????cycleArray(item);
????????continue;
??????}?else{
????????newArr.push(item);?//?不是數(shù)組的話,直接添加到新數(shù)組中
??????}
????}
??}
??cycleArray(_this);?//?循環(huán)數(shù)組里的每個元素
??return?newArr;?//?返回新的數(shù)組對象
}
Array.prototype.myFlat?=?myFlat;
arr?=?arr.myFlat();?//?[1,?2,?2,?3,?4,?5,?5,?6,?7,?8,?9,?11,?12,?12,?13,?14,?10]
13.ES6實現(xiàn)數(shù)組扁平化flat方法
const?myFlat?=?(arr)?=>?{
??let?newArr?=?[];
??let?cycleArray?=?(arr)?=>?{
????for(let?i?=?0;?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]
14.使用reduce手動實現(xiàn)數(shù)組扁平化flat方法
根據(jù)Array.isArray逐個判斷數(shù)組里的每一項是否為數(shù)組元素
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]
15.用不同的三種思想實現(xiàn)數(shù)組去重?
思想一:數(shù)組最后一項元素替換掉當前項元素,并刪除最后一項元素
let?arr?=?[12,?23,?12,?15,?25,?23,?16,?25,?16];
for(let?i?=?0;?i?1;?i++)?{
??let?item?=?arr[i];?//?取得當前數(shù)組中的每一項
??let?remainArgs?=?arr.slice(i+1);?//?從?i+1項開始截取數(shù)組中剩余元素,包括i+1位置的元素
??if?(remainArgs.indexOf(item)?>?-1)?{?//?數(shù)組的后面元素?包含當前項
????arr[i]?=?arr[arr.length?-?1];?//?用數(shù)組最后一項替換當前項
????arr.length--;?//?刪除數(shù)組最后一項
????i--;?//?仍從當前項開始比較
??}
}
console.log(arr);?//?[?16,?23,?12,?15,?25?]
思想二:新容器存儲思想——對象鍵值對
思想:
把數(shù)組元素作為對象屬性,通過遍歷數(shù)組,判斷數(shù)組元素是否已經(jīng)是對象的屬性,如果對象屬性定義過,則證明是重復(fù)元素,進而刪除重復(fù)元素
let?obj?=?{};
for?(let?i=0;?i???let?item?=?arr[i];?//?取得當前項
??if?(typeof?obj[item]?!==?'undefined')?{
????//?obj?中存在當前屬性,則證明當前項?之前已經(jīng)是?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?]
16.基于Generator函數(shù)實現(xiàn)async/await原理
核心: 傳遞給我一個
Generator函數(shù),把函數(shù)中的內(nèi)容基于Iterator迭代器的特點一步步的執(zhí)行
function?readFile(file)?{
?return?new?Promise(resolve?=>?{
??setTimeout(()?=>?{
???resolve(file);
????},?1000);
?})
};
function?asyncFunc(generator)?{
?const?iterator?=?generator();?//?接下來要執(zhí)行next
??//?data為第一次執(zhí)行之后的返回結(jié)果,用于傳給第二次執(zhí)行
??const?next?=?(data)?=>?{
??let?{?value,?done?}?=?iterator.next(data);?//?第二次執(zhí)行,并接收第一次的請求結(jié)果?data
????
????if?(done)?return;?//?執(zhí)行完畢(到第三次)直接返回
????//?第一次執(zhí)行next時,yield返回的?promise實例?賦值給了?value
????value.then(data?=>?{
??????next(data);?//?當?shù)谝淮蝪alue?執(zhí)行完畢且成功時,執(zhí)行下一步(并把第一次的結(jié)果傳遞下一步)
????});
??}
??next();
};
asyncFunc(function*?()?{
?//?生成器函數(shù):控制代碼一步步執(zhí)行?
??let?data?=?yield?readFile('a.js');?//?等這一步驟執(zhí)行執(zhí)行成功之后,再往下走,沒執(zhí)行完的時候,直接返回
??data?=?yield?readFile(data?+?'b.js');
??return?data;
})
17.基于Promise封裝Ajax
思路:
返回一個新的 Promise實例創(chuàng)建 HMLHttpRequest異步對象調(diào)用 open方法,打開url,與服務(wù)器建立鏈接(發(fā)送前的一些處理)監(jiān)聽 Ajax狀態(tài)信息xhr.status == 200,返回resolve狀態(tài)xhr.status == 404,返回reject狀態(tài)如果 xhr.readyState == 4(表示服務(wù)器響應(yīng)完成,可以獲取使用服務(wù)器的響應(yīng)了)xhr.readyState !== 4,把請求主體的信息基于send發(fā)送給服務(wù)器
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('請求數(shù)據(jù)失敗')
??????}
????}
????xhr.send(null)
??})
}
18.手動實現(xiàn)JSONP跨域
思路:
創(chuàng)建 script標簽設(shè)置 script標簽的src屬性,以問號傳遞參數(shù),設(shè)置好回調(diào)函數(shù)callback名稱插入到 html文本中調(diào)用回調(diào)函數(shù), res參數(shù)就是獲取的數(shù)據(jù)
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);
}
19.手動實現(xiàn)sleep
某個時間過后,就去執(zhí)行某個函數(shù),基于Promise封裝異步任務(wù)。
await后面的代碼都會放到微任務(wù)隊列中去異步執(zhí)行。
/**
?*?
?*?@param?{*}?fn?要執(zhí)行的函數(shù)
?*?@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('公眾號的朋友們');
};
autoRun();
20.ES5手動實現(xiàn)數(shù)組reduce
特點:
初始值不傳時的特殊處理:會默認使用數(shù)組中的第一個元素 函數(shù)的返回結(jié)果會作為下一次循環(huán)的 prev回調(diào)函數(shù)一共接受四個參數(shù)
(arr.reduce(prev, next, currentIndex, array)))prev:上一次調(diào)用回調(diào)時返回的值正在處理的元素 正在處理的元素的索引 正在遍歷的集合對象
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
21.手動實現(xiàn)通用柯理化函數(shù)
柯理化函數(shù)含義: 是給函數(shù)分步傳遞參數(shù),每次傳遞部分參數(shù),并返回一個更具體的函數(shù)接收剩下的參數(shù),這中間可嵌套多層這樣的接收部分參數(shù)的函數(shù),直至返回最后結(jié)果。
//?add的參數(shù)不固定,看有幾個數(shù)字累計相加
function?add?(a,b,c,d)?{
??return?a+b+c+d
}
function?currying?(fn,?...args)?{
??//?fn.length?回調(diào)函數(shù)的參數(shù)的總和
??//?args.length?currying函數(shù)?后面的參數(shù)總和?
??//?如:add?(a,b,c,d)??currying(add,1,2,3,4)
??if?(fn.length?===?args.length)?{??
????return?fn(...args)
??}?else?{
????//?繼續(xù)分步傳遞參數(shù)?newArgs?新一次傳遞的參數(shù)
????return?function?anonymous(...newArgs)?{
??????//?將先傳遞的參數(shù)和后傳遞的參數(shù)?結(jié)合在一起
??????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
23.ES5實現(xiàn)一個繼承
寄生組合繼承(ES5繼承的最佳方式)
所謂寄生組合式繼承,即通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈的形式來繼承方法。
只調(diào)用了一次父類構(gòu)造函數(shù),效率更高。避免在子類.prototype上面創(chuàng)建不必要的、多余的屬性,與其同時,原型鏈還能保持不變。
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);?//?調(diào)用父類的構(gòu)造函數(shù),將父類構(gòu)造函數(shù)內(nèi)的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();
24.手動實現(xiàn)發(fā)布訂閱
發(fā)布訂閱的核心:: 每次event. emit(發(fā)布),就會觸發(fā)一次event. on(注冊)
class?EventEmitter?{
??constructor()?{
????//?事件對象,存放訂閱的名字和事件
????this.events?=?{};
??}
??//?訂閱事件的方法
??on(eventName,callback)?{
????if?(!this.events[eventName])?{
??????//?注意數(shù)據(jù),一個名字可以訂閱多個事件函數(shù)
??????this.events[eventName]?=?[callback];
????}?else??{
??????//?存在則push到指定數(shù)組的尾部保存
??????this.events[eventName].push(callback)
????}
??}
??//?觸發(fā)事件的方法
??emit(eventName)?{
????//?遍歷執(zhí)行所有訂閱的事件
????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");??
26.手動實現(xiàn)觀察者模式
觀察者模式(基于發(fā)布訂閱模式) 有觀察者,也有被觀察者
觀察者需要放到被觀察者中,被觀察者的狀態(tài)變化需要通知觀察者 我變化了 內(nèi)部也是基于發(fā)布訂閱模式,收集觀察者,狀態(tài)變化后要主動通知觀察者
class?Subject?{?//?被觀察者?學生
??constructor(name)?{
????this.state?=?'開心的'
????this.observers?=?[];?//?存儲所有的觀察者
??}
??//?收集所有的觀察者
??attach(o){?//?Subject.?prototype.?attch
????this.observers.push(o)
??}
??//?更新被觀察者?狀態(tài)的方法
??setState(newState)?{
????this.state?=?newState;?//?更新狀態(tài)
????//?this?指被觀察者?學生
????this.observers.forEach(o?=>?o.update(this))?//?通知觀察者?更新它們的狀態(tài)
??}
}
class?Observer{?//?觀察者?父母和老師
??constructor(name)?{
????this.name?=?name
??}
??update(student)?{
????console.log('當前'?+?this.name?+?'被通知了',?'當前學生的狀態(tài)是'?+?student.state)
??}
}
let?student?=?new?Subject('學生');?
let?parent?=?new?Observer('父母');?
let?teacher?=?new?Observer('老師');?
//?被觀察者存儲觀察者的前提,需要先接納觀察者
student.?attach(parent);?
student.?attach(teacher);?
student.?setState('被欺負了');
27.手動實現(xiàn)Object.freeze
Object.freeze凍結(jié)一個對象,讓其不能再添加/刪除屬性,也不能修改該對象已有屬性的可枚舉性、可配置可寫性,也不能修改已有屬性的值和它的原型屬性,最后返回一個和傳入?yún)?shù)相同的對象。
function?myFreeze(obj){
??//?判斷參數(shù)是否為Object類型,如果是就封閉對象,循環(huán)遍歷對象。去掉原型屬性,將其writable特性設(shè)置為false
??if(obj?instanceof?Object){
????Object.seal(obj);??//?封閉對象
????for(let?key?in?obj){
??????if(obj.hasOwnProperty(key)){
????????Object.defineProperty(obj,key,{
??????????writable:false???//?設(shè)置只讀
????????})
????????//?如果屬性值依然為對象,要通過遞歸來進行進一步的凍結(jié)
????????myFreeze(obj[key]);??
??????}
????}
??}
}
28.手動實現(xiàn)Promise.all
Promise.all:有一個promise任務(wù)失敗就全部失敗
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執(zhí)行后的結(jié)果
????let?index?=?0;?//?計數(shù)器,用來累計promise的已執(zhí)行次數(shù)
????const?processData?=?(key,?data)?=>?{
??????arr[key]?=?data;?//?不能使用數(shù)組的長度來計算
??????/*
????????if?(arr.length?==?promises.length)?{
??????????resolve(arr);??//?[null,?null?,?1,?2]?由于Promise異步比較慢,所以還未返回
????????}
??????*/
?????if?(++index?===?promises.length)?{
??????//?必須保證數(shù)組里的每一個
???????resolve(arr);
?????}
????}
????//?遍歷數(shù)組依次拿到執(zhí)行結(jié)果
????for?(let?i?=?0;?i???????let?result?=?promises[i];
??????if(isPromise(result))?{
????????//?讓里面的promise執(zhí)行,取得成功后的結(jié)果
????????//?data?promise執(zhí)行后的返回結(jié)果
????????result.then((data)?=>?{
??????????//?處理數(shù)據(jù),按照原數(shù)組的順序依次輸出
??????????processData(i?,data)
????????},?reject)??//?reject本事就是個函數(shù)?所以簡寫了
??????}?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?]
})
29.手動實現(xiàn)Promise.allSettled
MDN:
Promise.allSettled()方法返回一個在所有給定的promise都已經(jīng)fulfilled或rejected后的promise,并帶有一個對象數(shù)組,每個對象表示對應(yīng)的promise結(jié)果。
當您有多個彼此不依賴的異步任務(wù)成功完成時,或者您總是想知道每個
promise的結(jié)果時,通常使用它。
【譯】Promise.allSettled跟Promise.all類似, 其參數(shù)接受一個Promise的數(shù)組, 返回一個新的Promise, 唯一的不同在于, 其不會進行短路, 也就是說當Promise全部處理完成后我們可以拿到每個Promise的狀態(tài), 而不管其是否處理成功。
用法 | 測試用例
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);
});
//?輸出結(jié)果
/*
?[
????{?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');
//?輸出結(jié)果
/*
?[
????{?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?}
??]
*/
實現(xiàn)
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???????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?})
??????}
????}
??})
}
30.手動實現(xiàn)Promise.prototype.finally
前面的promise不管成功還是失敗,都會走到finally中,并且finally之后,還可以繼續(xù)then(說明它還是一個then方法是關(guān)鍵),并且會將初始的promise值原封不動的傳遞給后面的then.
Promise.prototype.finally最大的作用
finally里的函數(shù),無論如何都會執(zhí)行,并會把前面的值原封不動傳遞給下一個then方法中(相當于起了一個中間過渡的作用)——對應(yīng)情況1,2,3
如果
finally函數(shù)中有promise等異步任務(wù),會等它們?nèi)繄?zhí)行完畢,再結(jié)合之前的成功與否狀態(tài),返回值
Promise.prototype.finally六大情況用法
//?情況1
Promise.resolve(123).finally((data)?=>?{?//?這里傳入的函數(shù),無論如何都會執(zhí)行
??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執(zhí)行完畢后,再把前面的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函數(shù)中的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執(zhí)行完,才能把一開始的err傳遞到err的回調(diào)中)
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
})
源碼實現(xiàn)
Promise.prototype.finally?=?function?(callback)?{
??return?this.then((data)?=>?{
????//?讓函數(shù)執(zhí)行?內(nèi)部會調(diào)用方法,如果方法是promise,需要等待它完成
????//?如果當前promise執(zhí)行時失敗了,會把err傳遞到,err的回調(diào)函數(shù)中
????return?Promise.resolve(callback()).then(()?=>?data);?//?data?上一個promise的成功態(tài)
??},?err?=>?{
????return?Promise.resolve(callback()).then(()?=>?{
??????throw?err;?//?把之前的失敗的err,拋出去
????});
??})
}