<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          30道高頻JS手撕面試題

          共 21013字,需瀏覽 43分鐘

           ·

          2020-09-27 18:31

          前言

          最近在準備面試,剛好利用幾天的時間系統(tǒng)的整理了下JS經(jīng)??嫉氖炙侯}類型。

          在這里,以腦圖的形式帶大家手撕這篇文章里的所有題(帶注釋)。

          想領(lǐng)取腦圖的關(guān)注前端時光屋公眾號,回復(fù)「JS手撕」,即可領(lǐng)取??

          腦圖里的所有題型即是本文中的30道常考高頻題

          腦圖?

          30道JS高頻手撕題

          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)fulfilledrejected后的promise,并帶有一個對象數(shù)組,每個對象表示對應(yīng)的promise結(jié)果。

          當您有多個彼此不依賴的異步任務(wù)成功完成時,或者您總是想知道每個promise的結(jié)果時,通常使用它。

          【譯】Promise.allSettledPromise.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最大的作用

          1. finally里的函數(shù),無論如何都會執(zhí)行,并會把前面的值原封不動傳遞給下一個then方法中

            (相當于起了一個中間過渡的作用)——對應(yīng)情況1,2,3

          2. 如果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,拋出去
          ????});
          ??})
          }
          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  AV性都花花世界 | 日韩国产高清无码 | 欧美三级片一区二区 | 天天干免费视频 | 青青青视频分类 |