手撕前端面試代碼題大全
加入我們一起學習,天天進步

轉(zhuǎn)載自:等不到你. https://blog.csdn.net/weixin_43758603/article/details/109895826
寫在前面
最近我終于找到了一份滿意的工作.準備面試的過程中,我整理出了一些有用的筆記,這篇就是其中之一.
既然寫好了,不妨就放在這里分享給大家.
面試通常都有現(xiàn)場寫代碼的題目,我基本每次都或多或少的翻車.有意思的是,每次面試結(jié)束,自己改不到五分鐘就調(diào)試出來了.
所以面試中的寫代碼的過程,一定不能緊張,要沉住氣慢慢來.只要不是系統(tǒng)自動檢查結(jié)果,只要是面試官看著你寫,就有很大的表現(xiàn)的機會,哪怕最后做不出來.
我參加的最煩人的面試,是那種系統(tǒng)判定結(jié)果的面試,只要做不出來,就絕對不可能通過.
仔細想想,在二三十場面試中,很少有我完整寫出毫無瑕疵的答案的題目,但基本也都順利通過了.
對比來看,我的同學中,答案能運行成功但面試沒通過的也大有人在.
可以肯定的是,面試官不會只根據(jù)是否能運行成功來評價應(yīng)聘者.
所以,只需要順著正確思路穩(wěn)穩(wěn)地做就好了,不要怕最后運行不成功.
如果實在做不出,也一定要和面試官說清你當前的進展和思路,而不是一句"我不會"就想結(jié)束問題.
當然,我指的只是前端這個對算法能力要求不強的崗位…
有一場百度的面試我甚至直接和面試官說,我是個偏感性的人,喜歡但是不擅長做算法.都給我通過了.
總的來看,如果能完全掌握這篇文章的內(nèi)容,就足以應(yīng)付所有前端面試中的手撕代碼環(huán)節(jié)了.
來都來了,點個贊唄
導讀
關(guān)于這篇文章,有幾點我想先說清楚,方便讀者更順利的學習.
這篇文章不適合前端小白閱讀,需要對
JS和ES6有一定了解,否則遇到一些寫法可能不太看得懂因為精力有限,我只加了較為粗略但足以幫助讀者理解的注釋,因為多數(shù)題也只有幾行代碼而已.
如果遇到還不懂的地方,我認為讀者完全可以自己去查詢文檔來了解為什么這么做,為什么使用這個函數(shù).
或者,先查詢該問題通常的解決思路,再回來參考我的實現(xiàn)
代碼大量使用了
ES6的語法學習手撕代碼,不只是理解的過程,更是實踐的過程
我在完全掌握(可以默寫出每段代碼,并講清楚每一行的作用)以下代碼的過程中,做了以下幾件事
參考別人的實現(xiàn),結(jié)合自己的思路,寫出一個自己的版本
不斷對代碼進行優(yōu)化
當你嘗試去優(yōu)化一段代碼的時候,對它的理解和記憶會異常深刻
不看之前的實現(xiàn),重新自己實現(xiàn)一次
再和之前的實現(xiàn)做對比,檢查錯誤
反復閱讀和默寫,直到可以完全正確的默寫為之
作為一個專業(yè)的程序員,除了工作中的編碼,額外的無實際產(chǎn)出的練習(反復練習解決一個問題,反復默寫同一段代碼),也是必不可少的.
這就像歌手不可能到了舞臺上才去練習自己的聲音.他一定會在平時大量去練聲.
這就是我強調(diào)要反復敲代碼的原因.別想著平時只要理解,工作中再去熟能生巧.
工作不是給你練習的地方,工作是你的舞臺.
下文中幾乎每一段代碼,都是我反復優(yōu)化后的結(jié)果,希望可以帶給讀者新的啟發(fā).
我把代碼大致分成了幾個專題,一共包含了大致30個問題的解決方案
除了文章中的問題,還有些我沒有提到的,都是頻率較低的問題
關(guān)于算法題,除了排序和查找我也基本沒有寫.因為算法問題千變?nèi)f化,需要的是解決問題的思維,而不是固定的實現(xiàn)
重要性與順序無關(guān)
有問題可以問我,我都會回復
目錄
DOM
事件代理
數(shù)組 對象
扁平化 去重 - unique()拷貝 淺拷貝 深拷貝( copy()函數(shù)實現(xiàn)、JSON.stringify)
字符串
去除空格 - trim()字符串全排列 廣度優(yōu)先實現(xiàn) 深度優(yōu)先實現(xiàn)
排序和查找
插入排序 歸并排序 快速排序 二分查找 找出出現(xiàn)次數(shù)最多的元素 - getMostItem()
功能函數(shù)實現(xiàn)
setTimeout實現(xiàn)setInterval函數(shù)柯里化 防抖 節(jié)流
數(shù)據(jù)結(jié)構(gòu)
單鏈表
設(shè)計模式
發(fā)布訂閱模式
JS原生API實現(xiàn)
bind()call()apply()InstanceOfnewreduce()forEach()Promise
HTTP請求
AJAX封裝 JSONP
DOM
事件代理
document.getElementById("father-id").onclick=function(event){
????event=event||window.event
????let?target=event.target||event.srcElement
????//可以自己打印一下event.target.nodeName,看看是什么
????if?(target.nodeName.toLowerCase()==='xxx'){
????????//事件內(nèi)容
????}
}
數(shù)組 對象
扁平化
function?flatten(arr)?{
?let?result=[]
?for?(let?i=0,len=arr.length;i??if?(Array.isArray(arr[i]))?{
???result=result.concat(flatten(arr[i]))
??}?else?{
???result.push(arr[i])
??}
?}
?return?result
}
去重 - unique()
function?unique(arr)?{
????let?appeard=new?Set()
????return?arr.filter(item=>{
????????//創(chuàng)建一個可以唯一標識對象的字符串id
????????let?id=item+JSON.stringify(item)
????????if?(appeard.has(id))?{
????????????return?false
????????}?else?{
????????????appeard.add(id)
????????????return?true
????????}
????})
}
拷貝
淺拷貝
function?copy(obj)?{
?let?result=Array.isArray(obj)?[]:{}
?Object.keys(obj).forEach(key=>result[key]=obj[key])
?return?result
}
otherStar={...star}
Object.assign({},star)
深拷貝
copy()函數(shù)實現(xiàn)
處理了循環(huán)引用和key為symbol類型的情況
function?copy(obj,appeard=new?Map())?{
?if?(!(obj?instanceof?Object))?return?obj//如果是原始數(shù)據(jù)類型
????if?(appeard.has(obj))?return?appeard.get(obj)//如果已經(jīng)出現(xiàn)過
????let?result=Array.isArray(obj)?[]:{}
????appeard.set(obj,result)//將新對象放入map
????//遍歷所有屬性進行遞歸拷貝
????;[...Object.keys(obj),...Object.getOwnPropertySymbols(obj)]
?????.forEach(key=>result[key]=copy(obj[key],appeard))
????return?result
}
JSON.stringify
只能處理純JSON數(shù)據(jù) 有幾種情況會發(fā)生錯誤 包含不能轉(zhuǎn)成 JSON 格式的數(shù)據(jù) 循環(huán)引用 undefined,NaN, -Infinity, Infinity 都會被轉(zhuǎn)化成null RegExp/函數(shù)不會拷貝 new Date()會被轉(zhuǎn)成字符串
new=JSON.parse(JSON.stringify(old))
字符串
去除空格 - trim()
function?myTrim(str)?{
?return?str.replace(/(^\s+)|(\s+$)/g,'')//將前空格和后空格替換為空
}
function?myTrim(str)?{//記錄前后空格的個數(shù),最后對字符串進行截取
?let?first=0,last=str.length
?for?(let?i?in?str)?{
??if?(str[i]==='?')?{
???first++
??}?else?{
???break
??}
?}
?for?(let?i=last;i>first;i--)?{
??if?(str[i]==='?')?{
???last--
??}?else?{
???break
??}
?}
?return?str.substr(first,last-first)
}
字符串全排列
廣度優(yōu)先實現(xiàn)
function?combine(str)?{//抽出一個字符s,對其余的進行排列,將s放在每種排列開頭
?if?(str.length===1)?return?[str]
?let?results=[]
?for?(let?i?in?str)?{
??for?(let?s?of?combine(str.slice(0,i)+str.slice(1+(+i))))?{
???results.push(str[i]+s)
??}
?}
????//可能會出現(xiàn)類似"aa"=>[aa,aa,aa,aa]的情況,需要去重
?return?[...new?Set(results)]
}
深度優(yōu)先實現(xiàn)
function?combine(str)?{//記錄已經(jīng)使用過的字符,深度優(yōu)先訪問所有方案
?let?result=[]
?;(function?_combine(str,path=''){
??if?(str.length===0)?return?result.push(path)
??for?(let?i?in?str)?{
???_combine(str.slice(0,i)+str.slice((+i)+1,str.length),path+str[i])
??}
?})(str)
????//可能會出現(xiàn)類似"aa"=>[aa,aa,aa,aa]的情況,需要去重
?return?[...new?Set(result)]
}
排序和查找
插入排序
function?sort(arr)?{//原地
?for?(let?i?in?arr)?{//選一個元素
??while?(i>0&&arr[i]-1])?{//向前移動到合適的位置
???[arr[i],arr[i-1]]=[arr[i-1],arr[i]]
???i--
??}
?}
}
歸并排序
function?sort(arr)?{
?if?(arr.length===1)?return?arr
?//分成兩部分
?let?mid=Math.floor(arr.length/2)
?let?[part1,part2]=[sort(arr.slice(0,mid)),sort(arr.slice(mid))]
?//對比+合并
?let?result=[]
?while?(part1.length>0&&part2.length>0)
??result.push((part1[0]0]?part1:part2).shift())
?return?[...result,...part1,...part2]
}
快速排序
function?sort(arr)?{
?if?(arr.length<=1)?return?arr
????//選基準值
?let?mid_pos=arr.length>>1
?let?mid=arr.splice(mid_pos,1)[0]
?let?left=[],right=[]
????//和基準值比較,分別插入left,right數(shù)組
?arr.forEach(item=>(item<=mid?left:right).push(item))
?return?[...sort(left),mid,...sort(right)]//遞歸調(diào)用排序
}
二分查找
function?search(arr,target)?{//循環(huán)寫法,不斷移動左右指針,縮小范圍
?let?left=0,right=arr.length-1
?while?(left<=right)?{
??const?mid_pos=Math.floor((left+right)/2)
??const?mid_val=arr[mid_pos]
??if?(target===mid_val)?{
???return?mid_pos
??}?else?if?(target>mid_val)?{
???left=mid_pos+1
??}?else?{
???right=mid_pos-1
??}
?}
?return?-1
}
找出出現(xiàn)次數(shù)最多的元素 - getMostItem()
function?getMost(arr)?{
?//計數(shù)
?let?map=new?Map()
?arr.forEach(item=>{
??if?(map.has(item))?{
???map.set(item,map.get(item)+1)
??}?else?{
???map.set(item,1)
??}
?})
?//找出出現(xiàn)最多
?let?[max_vals,max_num]=[[arr[0]],map.get(arr[0])]
?map.forEach((count,item)=>{
??if?(count>max_num){
???max_vals=[item]
???max_num=count
??}?else?{
???max_vals.push(item)
??}?
?})
?return?max_vals
}
console.log(getMost(['1',?'2',?'3',?'3',?'55',?'3',?'55',?'55']))
功能函數(shù)實現(xiàn)
setTimeout實現(xiàn)setInterval
function?myInterval(fn,interval,...args)?{
?let?context=this
?setTimeout(()=>{
??fn.apply(context,args)
??myInterval(fn,interval,...args)//別忘了為它傳入?yún)?shù)
?},interval)
}
myInterval((num)=>console.log(num),500,10)
函數(shù)柯里化
function?sum(...args1){
????return?function?(...args2)?{
????????return?[...args1,...args2].reduce((p,n)=>p+n)
????}
}
console.log(sum(1,?2,?2)(7))
防抖 節(jié)流
實現(xiàn)了兩個加工方法,返回一個加工后的防抖/節(jié)流函數(shù)
防抖
function?debounce(fn,delay)?{
?let?timer=null
?return?function?(){
??if?(timer)?clearTimeout(timer)
??timer=setTimeout(()=>fn.call(...arguments),delay)//別忘了為它傳入?yún)?shù)
?}
}
節(jié)流
function?throttle(fn,delay)?{
?let?flag=true
?return?function()?{
??if?(!flag)?return
??flag=false
??setTimeout(()=>{
???fn(...arguments)//別忘了為它傳入?yún)?shù)
???flag=true
??},delay)
?}
}
數(shù)據(jù)結(jié)構(gòu)
單鏈表
function?Node(element)?{//結(jié)點類
?[this.element,this.next]=[element,null]
}
class?LinkList?{//鏈表類
?constructor()?{
??this.length=0
??this.head=new?Node()
??this.tail=new?Node()
??this.head.next=this.tail
?}
?get_all()?{
??let?result=[]
??let?now=this.head
??while?(now.next!==this.tail)?{
???now=now.next
???result.push(now.element)
??}
??return?result
?}
?unshift(element)?{//開頭添加
??let?node=new?Node(element)
??node.next=this.head.next
??this.head.next=node
?}
?shift(){//開頭刪除
??let?node=this.head.next
??this.head.next=this.head.next.next
??return?node.element
?}
}
let?list=new?LinkList()
list.unshift(15)
list.unshift(16)
list.unshift(17)
console.log(list.shift())//17
console.log(list.get_all())//[?16,?15?]
設(shè)計模式
發(fā)布訂閱模式
class?Observer?{
?constructor()?{
??this.events={}//事件中心
?}
?publish(eventName,...args)?{//發(fā)布=>調(diào)用事件中心中對應(yīng)的函數(shù)
??if?(this.events[eventName])
???this.events[eventName].forEach(cb=>cb.apply(this,args))
?}
?subscribe(eventName,callback)?{//訂閱=>向事件中心中添加事件
??if?(this.events[eventName])?{
???this.events[eventName].push(callback)
??}?else?{
???this.events[eventName]=[callback]
??}
?}
?unSubscribe(eventName,callback)?{//取消訂閱
??if?(events[eventName])
???events[eventName]=events[eventName].filter(cb=>cb!==callback)
?}
}
JS原生API實現(xiàn)
bind() call() apply()
apply()
Function.prototype.myApply=function(context,args)?{
?context.fn=this//為context設(shè)置函數(shù)屬性
?let?result=context.fn(...args)//調(diào)用函數(shù)
?delete?context.fn//刪除context的函數(shù)屬性
?return?result
}
call()
//除了...args
//和apply都一樣
Function.prototype.myCall=function(context,...args)?{
?context.fn=this
?let?result=context.fn(...args)
?delete?context.fn
?return?result
}
bind()
Function.prototype.myBind=function(context,args1)?{//使用[閉包+apply]實現(xiàn)
?return?(...args2)=>this.apply(context,[...args1,...args2]);
}
InstanceOf
function?myInstanceOf(son,father)?{//沿著父親的原型鏈向上查找是否有兒子的原型
?while?(true)?{
??son=son.__proto__
??if?(!son)?return?false
??if?(son===father.prototype)?return?true
?}
}
myInstanceOf([],?Array)??//?true
new
function?myNew(constructor_fn,...args)?{
?//構(gòu)造新的空對象
?let?new_obj={}
?new_obj.__proto__=constructor_fn.prototype
?let?result=constructor_fn.apply(new_obj,args)
?//如果構(gòu)造函數(shù)沒有返回一個對象,則返回新創(chuàng)建的對象
?//如果構(gòu)造函數(shù)返回了一個對象,則返回那個對象
?//如果構(gòu)造函數(shù)返回原始值,則當作沒有返回對象
?return?result?instanceof?Object?result:new_obj
}
function?Animal(name)?{
??this.name?=?name;
}
let?animal?=?myNew(Animal,?'dog');
console.log(animal.name)??//?dog
reduce() forEach()
reduce()
api用法:
arr.reduce(function(prev,?cur,?index,?arr){},?initialValue)
實現(xiàn):
Array.prototype.myReduce=function(fn,init_val){
?let?[val,idx]=init_val?[init_val,0]:[this[0],1]//設(shè)置初始值
?for?(let?i=idx,len=this.length;i??val=fn(val,this[i],i,this)//循環(huán)并迭代結(jié)果
?}
?return?val
}
console.log([1,2,3,4,5].reduce((pre,item)=>pre+item,0))?//?15
forEach()
api用法:
[1,3,5,7,9].myForEach(function(item,index,arr)?{
????console.log(this)
},15)
實現(xiàn):
Array.prototype.myForEach=function(fn,temp_this)?{
????for?(let?i=0,len=this.length;i????????fn.call(temp_this,this[i],i,this)//循環(huán)數(shù)組元素,為回調(diào)函數(shù)傳入?yún)?shù)
????}
}
Promise
Promise.all()
Promise.prototype.all=function(promiseList)?{
????return?new?Promise((resolve,reject)=>{
????????if?(promiseList.length===0)?return?resolve([])
????????let?result=[],count=0
????????promiseList.forEach((promise,index)=>{
????????????Promise.resolve(promise).then(value=>{
????????????????result[index]=value
????????????????if?(++count===promiseList.length)?resolve(result)
????????????},reason=>reject(reason))
????????})
????})
}
ES6所有API完整實現(xiàn)
通過Promise/A+ test測試
實現(xiàn)細節(jié)過多,還請參照Promise/A+規(guī)范閱讀
也可以直接參考我關(guān)于promise的筆記
深入理解promise
https://blog.csdn.net/weixin_43758603/article/details/109641486
class?Promise?{
?constructor(task)?{
??this.status="pending"
??this.value=undefined
??this.reason=undefined
??this.fulfilled_callbacks=[]
??this.rejected_callbacks=[]
??try?{
???task(this._resolve,this._reject)
??}?catch?(error)?{
???this._reject(error)
??}
?}
?then(onFulfilled,onRejected){
??if?(this.status==='fulfilled')?{
???let?promise2=new?Promise((resolve,reject)=>{
????setTimeout(()=>{
?????try?{
??????if?(!this._isFunction(onFulfilled))?{
???????resolve(this.value)
??????}?else?{
???????this._resolvePromise(promise2,onFulfilled(this.value))
??????}
?????}?catch?(error)?{
??????reject(error)
?????}
????},0)
???})
???return?promise2
??}?else?if?(this.status==='rejected')?{
???let?promise2=new?Promise((resolve,reject)=>{
????setTimeout(()=>{
?????try?{
??????if?(!this._isFunction(onRejected))?{
???????reject(this.reason)
??????}?else?{
???????this._resolvePromise(promise2,onRejected(this.reason))
??????}
?????}?catch?(error)?{
??????reject(error)
?????}
????},0)
???})
???return?promise2
??}?else?if?(this.status==='pending')??{
???let?promise2=new?Promise((resolve,reject)=>{
????this.fulfilled_callbacks.push(()=>{
?????try?{
??????if?(!this._isFunction(onFulfilled))?{
???????resolve(this.value)
??????}?else?{
???????this._resolvePromise(promise2,onFulfilled(this.value))
??????}
?????}?catch?(error)?{
??????reject(error)
?????}
????})
????this.rejected_callbacks.push(()=>{
?????try?{
??????if?(!this._isFunction(onRejected))?{
???????reject(this.reason)
??????}?else?{
???????this._resolvePromise(promise2,onRejected(this.reason))
??????}
?????}?catch?(error)?{
??????reject(error)
?????}
????})
???})
???return?promise2
??}
?}
?catch=onRejected=>this.then(null,onRejected)
?finally=onFinished=>this.then(onFinished,onFinished)
?static?deferred(){
??let?deferred={}
??deferred.promise=new?Promise((resolve,reject)=>{
???deferred.resolve=resolve
???deferred.reject=reject
??})
??return?deferred
?}
?static?resolve(value)?{
??if?(value?instanceof?Promise)?return?value
??return?new?Promise(resolve=>resolve(value))
?}
?static?reject=reason=>{return?new?Promise((resolve,?reject)=>reject(reason))}
?static?all(promiseList)?{
??return?new?Promise((resolve,reject)=>{
???if?(promiseList.length===0)?return?resolve([])
???let?result=[],count=0
???promiseList.forEach((promise,index)=>{
????Promise.resolve(promise).then(value=>{
?????result[index]=value
?????if?(++count===promiseList.length)?resolve(result)
????},reason=>reject(reason))
???})
??})
?}
?static?race(promiseList)?{
??return?new?Promise((resolve,reject)=>{
???if?(promiseList.length===0)?return?resolve()
???promiseList.forEach(promise=>{
????Promise.resolve(promise)
?????.then(value=>resolve(value),reason=>reject(reason))
???})
??})
?}
?static?allSettled(promiseList)?{
??return?new?Promise(resolve=>{
???let?result=[],count=0
???if?(len===0)?return?resolve(result)
???promiseList.forEach((promise,i)=>{
????Promise.resolve(promise).then(value=>{
?????result[i]={
??????status:'fulfilled',
??????value:value
?????}
?????if?(++count===promiseList.length)?resolve(result)
????},reason=>{
?????result[i]={
??????status:'rejected',
??????reason:reason
?????}
?????if?(++count===promiseList.length)?resolve(result)
????})
???})
??})
?}
?_resolve=value=>{
??if?(this.status!=='pending')?return
??setTimeout(()=>{
???this.status?='fulfilled'
???this.value?=?value
???this.fulfilled_callbacks.forEach(cb=>cb(this.value))
??},0)
?}
?_reject=reason=>{
??if?(this.status!=='pending')?return
??setTimeout(()=>{
???this.reason?=?reason
???this.status?='rejected'
???this.rejected_callbacks.forEach(cb=>cb(this.reason))
??},0)
?}
?_isFunction=f=>Object.prototype.toString.call(f).toLocaleLowerCase()==='[object?function]'
?
?_isObject=o=>Object.prototype.toString.call(o).toLocaleLowerCase()==='[object?object]'
?_resolvePromise(promise,x){
??if?(promise===x)?{
??????promise._reject(new?TypeError('cant?be?the?same'))
??????return
??}
??if?(x?instanceof?Promise)?{
???if?(x.status==='fulfilled')?{
????promise._resolve(x.value)
???}?else?if?(x.status==='rejected')?{
????promise._reject(x.reason)
???}?else?if?(x.status==='pending')?{
????x.then(value=>{
?????this._resolvePromise(promise,value)
????},reason=>{
?????promise._reject(reason)
????})
???}
???return
??}
??if?(this._isObject(x)||this._isFunction(x))?{
???let?then
???try?{
????then=x.then
???}?catch?(error)?{
????promise._reject(error)
????return
???}
???if?(this._isFunction(then))?{
????let?called=false
????try?{
?????then.call(x,value=>{
??????if?(called)?return
??????called=true
??????this._resolvePromise(promise,value)
?????},reason=>{
??????if?(called)?return
??????called=true
??????promise._reject(reason)
?????})
????}?catch?(error)?{
?????if?(called)?return
?????promise._reject(error)
????}
???}?else?{
????promise._resolve(x)
???}
??}?else?{
???promise._resolve(x)
??}
?}
}
module.exports?=?Promise
HTTP請求
AJAX封裝
function?ajax(method,url,params,callback)?{
?//對參數(shù)進行處理
?method=method.toUpperCase()
?let?post_params=null
?let?get_params=''
?
?if?(method==='GET')?{
??if?(typeof?params==='object')?{
???let?tempArr=[]
???for?(let?key?in?params)?{
????tempArr.push(`${key}=${params[key]}`)
???}
???params=tempArr.join('&')
??}
??get_params=`?${params}`
?}?else?{
??post_params=params
?}
?//發(fā)請求
?let?xhr=new?XMLHttpRequest()
?xhr.onreadystatechange=function(){
??if?(xhr.readyState!==4)?return
??callback(xhr.responseText)
?}
?xhr.open(method,url+get_params,false)
?if?(method==='POST')
??xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
?xhr.send(post_params)?
}
ajax('get','https://www.baidu.com',{id:15},data=>console.log(data))
JSONP
function?jsonp(url,?params_obj,?callback)?{
?//創(chuàng)建一個供后端返回數(shù)據(jù)調(diào)用的函數(shù)名
?let?funcName?=?'jsonp_'?+?Data.now()?+?Math.random().toString().substr(2,?5)
?//將參數(shù)拼接成字符串
?if?(typeof?params==='object')?{
??let?temp=[]
??for?(let?key?in?params)?{
???temp.push(`${key}=${params[key]}`)
??}
??params=temp.join('&')
?}
?//在html中插入
影音先锋男人的资源
|
日逼黄片视频
|
俺去听听婷婷
|
91偷拍网|
成人国产精品秘 欧美高清
|
