您好,登錄后才能下訂單哦!
在面試中,常常會遇到一些手寫XXX之類的面試題,因此好好總結一下,對于鞏固我們的原生js的基礎是非常必要的。
盡管在網上已經有了非常多的總結文章,但在我看來有一個普遍的問題,那就是把原理性的東西過于復雜化了。如果站在面試官的角度,他的目的是在最短的時間內考察出面試者對于JS語言的理解程度,但是在看了網站的諸多總結文章后我發現其中的代碼有很大一部分是做意義不大的操作,比如實現一個簡單的防抖,只要是核心的流程展示即可,至于其他的一些等模式則沒有必要再去深挖,一大堆的if-else讓人看上去也眼花繚亂,甚至誤導別人直接去背代碼,另外,核心的邏輯都能展示出來,再去橫向的實現其他的類似情況恐怕也不是什么問題了。
在以下的整理中,建議大家先照的核心要點自己寫一遍,然后對照下面的代碼,復習效果更好。本文的目的就在于以最簡潔的代碼幫你從第一性原理的角度理解api的內部運作流程,凡是對于我們理解api沒有幫助的的邊界情況都不做處理。
一、用ES5實現數組的map方法
核心要點:
1.回調函數的參數有哪些,返回值如何處理。
2.不修改原來的數組。
Array.prototype.MyMap?=?function(fn,?context){ ?var?arr?=?Array.prototype.slice.call(this);//由于是ES5所以就不用...展開符了 ?var?mappedArr?=?[]; ?for?(var?i?=?0;?i?<?arr.length;?i++?){ ?mappedArr.push(fn.call(context,?arr[i],?i,?this)); ?} ?return?mappedArr; } 復制代碼
二、用ES5實現數組的reduce方法
核心要點:
1、初始值不傳怎么處理
2、回調函數的參數有哪些,返回值如何處理。
Array.prototype.myReduce?=?function(fn,?initialValue)?{ ?var?arr?=?Array.prototype.slice.call(this); ?var?res,?startIndex; ?res?=?initialValue???initialValue?:?arr[0]; ?startIndex?=?initialValue???0?:?1; ?for(var?i?=?startIndex;?i?<?arr.length;?i++)?{ ?res?=?fn.call(null,?res,?arr[i],?i,?this); ?} ?return?res; } 復制代碼
三、實現call/apply
思路: 利用this的上下文特性。
//實現apply只要把下一行中的...args換成args即可? Function.prototype.myCall?=?function(context?=?window,?...args)?{ ?let?func?=?this; ?let?fn?=?Symbol("fn"); ?context[fn]?=?func; ?let?res?=?context[fn](...args);//重點代碼,利用this指向,相當于context.caller(...args) ?delete?context[fn]; ?return?res; } 復制代碼
四、實現Object.create方法(常用)
function?create(proto)?{ ?function?F()?{}; ?F.prototype?=?proto; ?F.prototype.constructor?=?F; ? ?return?new?F(); } 復制代碼
五、實現bind方法
核心要點:
1.對于普通函數,綁定this指向
2.對于構造函數,要保證原函數的原型對象上的屬性不能丟失
Function.prototype.bind?=?function(context,?...args)?{ ?let?self?=?this;//謹記this表示調用bind的函數 ?let?fBound?=?function()?{ ?//this?instanceof?fBound為true表示構造函數的情況。如new?func.bind(obj) ?return?self.apply(this?instanceof?fBound???this?:?context?||?window,?args); ?} ?fBound.prototype?=?Object.create(this.prototype);//保證原函數的原型對象上的屬性不丟失 ?return?fBound; } 復制代碼
大家平時說的手寫bind,其實就這么簡單:)
六、實現new關鍵字
核心要點:
創建一個全新的對象,這個對象的__proto__要指向構造函數的原型對象
執行構造函數
返回值為object類型則作為new方法的返回值返回,否則返回上述全新對象
function?myNew(fn,?...args)?{ ?let?instance?=?Object.create(fn.prototype); ?let?res?=?fn.apply(instance,?args); ?return?typeof?res?===?'object'???res:?instance; } 復制代碼
七、實現instanceof的作用
核心要點:原型鏈的向上查找。
function?myInstanceof(left,?right)?{ ?let?proto?=?Object.getPrototypeOf(left); ?while(true)?{ ?if(proto?==?null)?return?false; ?if(proto?==?right.prototype)?return?true; ?proto?=?Object.getPrototypeof(proto); ?} } 復制代碼
八、實現單例模式
核心要點: 用閉包和Proxy屬性攔截
function?proxy(func)?{ ?let?instance; ?let?handler?=?{ ?constructor(target,?args)?{ ?if(!instance)?{ ?instance?=?Reflect.constructor(fun,?args); ?} ?return?instance; ?} ?} ?return?new?Proxy(func,?handler); } 復制代碼
九、實現數組的flat
方式其實很多,之前我做過系統整理,有六種方法,請參考:
JS數組扁平化(flat)方法總結
十、實現防抖功能
核心要點:
如果在定時器的時間范圍內再次觸發,則重新計時。
const?debounce?=?(fn,?delay)?=>?{ ?let?timer?=?null; ?return?(...args)?=>?{ ?clearTimeout(timer); ?timer?=?setTimeout(()?=>?{ ?fn.apply(this,?args); ?},?delay); ?}; }; 復制代碼
十一、實現節流功能
核心要點:
如果在定時器的時間范圍內再次觸發,則不予理睬,等當前定時器完成,才能啟動下一個定時器。
const?throttle?=?(fn,?delay?=?500)?=>?{ ?let?flag?=?true; ?return?(...args)?=>?{ ?if?(!flag)?return; ?flag?=?false; ?setTimeout(()?=>?{ ?fn.apply(this,?args); ?flag?=?true; ?},?delay); ?}; }; 復制代碼
十二、用發布訂閱模式實現EventEmit
參考我的另一篇文章:
基于"發布-訂閱"的原生JS插件封裝中的手寫發布訂閱部分。
十三、實現深拷貝
以下為簡易版深拷貝,沒有考慮循環引用的情況和Buffer、Promise、Set、Map的處理,如果一一實現,過于復雜,面試短時間寫出來不太現實,如果有興趣可以去這里深入實現:
深拷貝終極探索。
const?clone?=?parent?=>?{ ?//?判斷類型 ?const?isType?=?(target,?type)?=>?`[object?${type}]`?===?Object.prototype.toString.call(target) ?//?處理正則 ?const?getRegExp?=?re?=>?{ ?let?flags?=?""; ?if?(re.global)?flags?+=?"g"; ?if?(re.ignoreCase)?flags?+=?"i"; ?if?(re.multiline)?flags?+=?"m"; ?return?flags; ?}; ?const?_clone?=?parent?=>?{ ?if?(parent?===?null)?return?null; ?if?(typeof?parent?!==?"object")?return?parent; ?let?child,?proto; ?if?(isType(parent,?"Array"))?{ ?//?對數組做特殊處理 ?child?=?[]; ?}?else?if?(isType(parent,?"RegExp"))?{ ?//?對正則對象做特殊處理 ?child?=?new?RegExp(parent.source,?getRegExp(parent)); ?if?(parent.lastIndex)?child.lastIndex?=?parent.lastIndex; ?}?else?if?(isType(parent,?"Date"))?{ ?//?對Date對象做特殊處理 ?child?=?new?Date(parent.getTime()); ?}?else?{ ?//?處理對象原型 ?proto?=?Object.getPrototypeOf(parent); ?//?利用Object.create切斷原型鏈 ?child?=?Object.create(proto); ?} ?for?(let?i?in?parent)?{ ?//?遞歸 ?child[i]?=?_clone(parent[i]); ?} ?return?child; ?}; ?return?_clone(parent); }; 復制代碼
十四、實現Promise
重點難點,比較復雜,請參考我的另一篇步步拆解文章:
我如何實現Promise
十五、使用ES5實現類的繼承效果
也是重點知識,我之前做過詳細拆解,有五個版本,如果每一版本都能說清楚,能夠很好的體現自己對于原型鏈的理解,文章地址:
ES5實現繼承的那些事
?
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。