前端面試js高頻手寫大全
本文涵蓋了前端面試常考的各種重點手寫。
建議優(yōu)先掌握:
instanceof (考察對原型鏈的理解) new (對創(chuàng)建對象實例過程的理解) call&apply&bind (對this指向的理解) 手寫promise (對異步的理解) 手寫原生ajax (對http請求方式的理解,重點是get和post請求)
1. 手寫instanceof
let?myInstanceof?=?(target,origin)?=>?{
?????while(target)?{
?????????if(target.__proto__===origin.prototype)?{
????????????return?true
?????????}
?????????target?=?target.__proto__
?????}
?????return?false
?}
?let?a?=?[1,2,3]
?console.log(myInstanceof(a,Array));??//?true
?console.log(myInstanceof(a,Object));??//?true
2. 實現數組的map方法
Array.prototype.myMap?=?function(fn,?thisValue)?{
?????let?res?=?[]
?????thisValue?=?thisValue||[]
?????let?arr?=?this
?????for(let?i?in?arr)?{
????????res.push(fn(arr[i]))
?????}
?????return?res
?}
3. reduce實現數組的map方法
Array.prototype.myMap?=?function(fn,thisValue){
?????var?res?=?[];
?????thisValue?=?thisValue||[];
?????this.reduce(function(pre,cur,index,arr){
?????????return?res.push(fn.call(thisValue,cur,index,arr));
?????},[]);
?????return?res;
}
var?arr?=?[2,3,1,5];
arr.myMap(function(item,index,arr){
?console.log(item,index,arr);
})
4. 手寫數組的reduce方法
callback(一個在數組中每一項上調用的函數,接受四個函數:) previousValue(上一次調用回調函數時的返回值,或者初始值) currentValue(當前正在處理的數組元素) currentIndex(當前正在處理的數組元素下標) array(調用reduce()方法的數組) initialValue(可選的初始值。作為第一次調用回調函數時傳給previousValue的值)
function?reduce(arr,?cb,?initialValue){
?????var?num?=?initValue?==?undefined??num?=?arr[0]:?initValue;
?????var?i?=?initValue?==?undefined??1:?0
?????for?(i;?i????????num?=?cb(num,arr[i],i)
?????}
?????return?num
?}
?
?function?fn(result,?currentValue,?index){
?????return?result?+?currentValue
?}
?
?var?arr?=?[2,3,4,5]
?var?b?=?reduce(arr,?fn,10)?
?var?c?=?reduce(arr,?fn)
?console.log(b)???//?24
5. 數組扁平化
let?a?=?[1,[2,3]];?
a.flat();?//?[1,2,3]?
a.flat(1);?//[1,2,3]
let?a?=?[1,[2,3,[4,[5]]]];?
a.flat(Infinity);?//?[1,2,3,4,5]??a是4維數組
var?arr1?=?[1,?2,?3,?[1,?2,?3,?4,?[2,?3,?4]]];
?function?flatten(arr)?{
?????var?res?=?[];
?????for?(let?i?=?0,?length?=?arr.length;?i??????if?(Array.isArray(arr[i]))?{
?????res?=?res.concat(flatten(arr[i]));?//concat?并不會改變原數組
?????//res.push(...flatten(arr[i]));?//擴展運算符?
?????}?else?{
?????????res.push(arr[i]);
???????}
?????}
?????return?res;
?}
?flatten(arr1);?//[1,?2,?3,?1,?2,?3,?4,?2,?3,?4]
6. 函數柯里化
通過函數的 length 屬性,獲取函數的形參個數,形參的個數就是所需的參數個數 在調用柯里化工具函數時,手動指定所需的參數個數
/**
?*?將函數柯里化
?*?@param?fn????待柯里化的原函數
?*?@param?len???所需的參數個數,默認為原函數的形參個數
?*/
function?curry(fn,len?=?fn.length)?{
?return?_curry.call(this,fn,len)
}
/**
?*?中轉函數
?*?@param?fn????待柯里化的原函數
?*?@param?len???所需的參數個數
?*?@param?args??已接收的參數列表
?*/
function?_curry(fn,len,...args)?{
????return?function?(...params)?{
?????????let?_args?=?[...args,...params];
?????????if(_args.length?>=?len){
?????????????return?fn.apply(this,_args);
?????????}else{
??????????return?_curry.call(this,fn,len,..._args)
?????????}
????}
}
let?_fn?=?curry(function(a,b,c,d,e){
?console.log(a,b,c,d,e)
});
_fn(1,2,3,4,5);?????//?print:?1,2,3,4,5
_fn(1)(2)(3,4,5);???//?print:?1,2,3,4,5
_fn(1,2)(3,4)(5);???//?print:?1,2,3,4,5
_fn(1)(2)(3)(4)(5);?//?print:?1,2,3,4,5

/**
?*?@param??fn???????????待柯里化的函數
?*?@param??length???????需要的參數個數,默認為函數的形參個數
?*?@param??holder???????占位符,默認當前柯里化函數
?*?@return?{Function}???柯里化后的函數
?*/
function?curry(fn,length?=?fn.length,holder?=?curry){
?return?_curry.call(this,fn,length,holder,[],[])
}
/**
?*?中轉函數
?*?@param?fn????????????柯里化的原函數
?*?@param?length????????原函數需要的參數個數
?*?@param?holder????????接收的占位符
?*?@param?args??????????已接收的參數列表
?*?@param?holders???????已接收的占位符位置列表
?*?@return?{Function}???繼續(xù)柯里化的函數?或?最終結果
?*/
function?_curry(fn,length,holder,args,holders){
?return?function(..._args){
?//將參數復制一份,避免多次操作同一函數導致參數混亂
?let?params?=?args.slice();
?//將占位符位置列表復制一份,新增加的占位符增加至此
?let?_holders?=?holders.slice();
?//循環(huán)入參,追加參數?或?替換占位符
?_args.forEach((arg,i)=>{
?//真實參數?之前存在占位符?將占位符替換為真實參數
?if?(arg?!==?holder?&&?holders.length)?{
?????let?index?=?holders.shift();
?????_holders.splice(_holders.indexOf(index),1);
?????params[index]?=?arg;
?}
?//真實參數?之前不存在占位符?將參數追加到參數列表中
?else?if(arg?!==?holder?&&?!holders.length){
?????params.push(arg);
?}
?//傳入的是占位符,之前不存在占位符?記錄占位符的位置
?else?if(arg?===?holder?&&?!holders.length){
?????params.push(arg);
?????_holders.push(params.length?-?1);
?}
?//傳入的是占位符,之前存在占位符?刪除原占位符位置
?else?if(arg?===?holder?&&?holders.length){
????holders.shift();
?}
?});
?//?params?中前?length?條記錄中不包含占位符,執(zhí)行函數
?if(params.length?>=?length?&&?params.slice(0,length).every(i=>i!==holder)){
?return?fn.apply(this,params);
?}else{
?return?_curry.call(this,fn,length,holder,params,_holders)
?}
?}
}
let?fn?=?function(a,?b,?c,?d,?e)?{
?console.log([a,?b,?c,?d,?e]);
}
let?_?=?{};?//?定義占位符
let?_fn?=?curry(fn,5,_);??//?將函數柯里化,指定所需的參數個數,指定所需的占位符
_fn(1,?2,?3,?4,?5);?????????????????//?print:?1,2,3,4,5
_fn(_,?2,?3,?4,?5)(1);??????????????//?print:?1,2,3,4,5
_fn(1,?_,?3,?4,?5)(2);??????????????//?print:?1,2,3,4,5
_fn(1,?_,?3)(_,?4,_)(2)(5);?????????//?print:?1,2,3,4,5
_fn(1,?_,?_,?4)(_,?3)(2)(5);????????//?print:?1,2,3,4,5
_fn(_,?2)(_,?_,?4)(1)(3)(5);????????//?print:?1,2,3,4,5
7. 實現深拷貝
let?obj={
?id:1,
?name:'Tom',
?msg:{
?age:18
?}
}
let?o={}
//實現深拷貝??遞歸????可以用于生命游戲那個題對二維數組的拷貝,
//但比較麻煩,因為已知元素都是值,直接復制就行,無需判斷
function?deepCopy(newObj,oldObj){
?????for(var?k?in?oldObj){
?????????let?item=oldObj[k]
?????????//判斷是數組?對象?簡單類型?
?????????if(item?instanceof?Array){
?????????????newObj[k]=[]
?????????????deepCopy(newObj[k],item)
?????????}else?if(item?instanceof?Object){
?????????????newObj[k]={}
?????????????deepCopy(newObj[k],item)
?????????}else{??//簡單數據類型,直接賦值
?????????????newObj[k]=item
?????????}
?????}
}
8. 手寫call, apply, bind
Function.prototype.myCall=function(context=window){??//?函數的方法,所以寫在Fuction原型對象上
?if(typeof?this?!=="function"){???//?這里if其實沒必要,會自動拋出錯誤
????throw?new?Error("不是函數")
?}
?const?obj=context||window???//這里可用ES6方法,為參數添加默認值,js嚴格模式全局作用域this為undefined
?obj.fn=this??????//this為調用的上下文,this此處為函數,將這個函數作為obj的方法
?const?arg=[...arguments].slice(1)???//第一個為obj所以刪除,偽數組轉為數組
?res=obj.fn(...arg)
?delete?obj.fn???//?不刪除會導致context屬性越來越多
?return?res
}
//用法:f.call(obj,arg1)
function?f(a,b){
?console.log(a+b)
?console.log(this.name)
}
let?obj={
?name:1
}
f.myCall(obj,1,2)?//否則this指向window
obj.greet.call({name:?'Spike'})?//打出來的是?Spike
Function.prototype.myApply=function(context){??//?箭頭函數從不具有參數對象!!!!!這里不能寫成箭頭函數
?let?obj=context||window
?obj.fn=this
?const?arg=arguments[1]||[]????//若有參數,得到的是數組
?let?res=obj.fn(...arg)
?delete?obj.fn
?return?res
}?
function?f(a,b){
?console.log(a,b)
?console.log(this.name)
}
let?obj={
?name:'張三'
}
f.myApply(obj,[1,2])??//arguments[1]
this.value?=?2
var?foo?=?{
?value:?1
};
var?bar?=?function(name,?age,?school){
?console.log(name)?//?'An'
?console.log(age)?//?22
?console.log(school)?//?'家里蹲大學'
}
var?result?=?bar.bind(foo,?'An')?//預置了部分參數'An'
result(22,?'家里蹲大學')?//這個參數會和預置的參數合并到一起放入bar中
Function.prototype.bind?=?function(context,?...outerArgs)?{
?var?fn?=?this;
?return?function(...innerArgs)?{???//返回了一個函數,...rest為實際調用時傳入的參數
?return?fn.apply(context,[...outerArgs,?...innerArgs]);??//返回改變了this的函數,
?//參數合并
?}
}
//?聲明一個上下文
let?thovino?=?{
?name:?'thovino'
}
//?聲明一個構造函數
let?eat?=?function?(food)?{
?this.food?=?food
?console.log(`${this.name}?eat?${this.food}`)
}
eat.prototype.sayFuncName?=?function?()?{
?console.log('func?name?:?eat')
}
//?bind一下
let?thovinoEat?=?eat.bind(thovino)
let?instance?=?new?thovinoEat('orange')??//實際上orange放到了thovino里面
console.log('instance:',?instance)?//?{}
function?thovinoEat?(...innerArgs)?{
?eat.call(thovino,?...outerArgs,?...innerArgs)
}
Function.prototype.bind?=?function?(context,?...outerArgs)?{
?let?that?=?this;
function?res?(...innerArgs)?{
?????if?(this?instanceof?res)?{
?????????//?new操作符執(zhí)行時
?????????//?這里的this在new操作符第三步操作時,會指向new自身創(chuàng)建的那個簡單空對象{}
?????????that.call(this,?...outerArgs,?...innerArgs)
?????}?else?{
?????????//?普通bind
?????????that.call(context,?...outerArgs,?...innerArgs)
?????}
?????}
?????res.prototype?=?this.prototype?//!!!
?????return?res
}
9. 手動實現new
創(chuàng)建一個空對象 obj; 將空對象的隱式原型(proto)指向構造函數的prototype。 使用 call 改變 this 的指向 如果無返回值或者返回一個非對象值,則將 obj 返回作為新對象;如果返回值是一個新對象的話那么直接直接返回該對象。
function?Person(name,age){
?this.name=name
?this.age=age
}
Person.prototype.sayHi=function(){
?console.log('Hi!我是'+this.name)
}
let?p1=new?Person('張三',18)
////手動實現new
function?create(){
?let?obj={}
?//獲取構造函數
?let?fn=[].shift.call(arguments)??//將arguments對象提出來轉化為數組,arguments并不是數組而是對象????!!!這種方法刪除了arguments數組的第一個元素,!!這里的空數組里面填不填元素都沒關系,不影響arguments的結果??????或者let?arg?=?[].slice.call(arguments,1)
?obj.__proto__=fn.prototype
?let?res=fn.apply(obj,arguments)????//改變this指向,為實例添加方法和屬性
?//確保返回的是一個對象(萬一fn不是構造函數)
?return?typeof?res==='object'?res:obj
}
let?p2=create(Person,'李四',19)
p2.sayHi()
[].shift.call(arguments)??也可寫成:
?let?arg=[...arguments]
?let?fn=arg.shift()??//使得arguments能調用數組方法,第一個參數為構造函數
?obj.__proto__=fn.prototype
?//改變this指向,為實例添加方法和屬性
?let?res=fn.apply(obj,arg)
10. 手寫promise(常見promise.all, promise.race)
//?Promise/A+?規(guī)范規(guī)定的三種狀態(tài)
const?STATUS?=?{
?PENDING:?'pending',
?FULFILLED:?'fulfilled',
?REJECTED:?'rejected'
}
class?MyPromise?{
?//?構造函數接收一個執(zhí)行回調
?constructor(executor)?{
?????this._status?=?STATUS.PENDING?//?Promise初始狀態(tài)
?????this._value?=?undefined?//?then回調的值
?????this._resolveQueue?=?[]?//?resolve時觸發(fā)的成功隊列
?????this._rejectQueue?=?[]?//?reject時觸發(fā)的失敗隊列
????
?//?使用箭頭函數固定this(resolve函數在executor中觸發(fā),不然找不到this)
?const?resolve?=?value?=>?{
?????const?run?=?()?=>?{
?????????//?Promise/A+?規(guī)范規(guī)定的Promise狀態(tài)只能從pending觸發(fā),變成fulfilled
?????????if?(this._status?===?STATUS.PENDING)?{
?????????????this._status?=?STATUS.FULFILLED?//?更改狀態(tài)
?????????????this._value?=?value?//?儲存當前值,用于then回調
????????????
?????????????//?執(zhí)行resolve回調
?????????????while?(this._resolveQueue.length)?{
?????????????????const?callback?=?this._resolveQueue.shift()
?????????????????callback(value)
?????????????}
?????????}
?????}
?????//把resolve執(zhí)行回調的操作封裝成一個函數,放進setTimeout里,以實現promise異步調用的特性(規(guī)范上是微任務,這里是宏任務)
?????setTimeout(run)
?}
?//?同?resolve
?const?reject?=?value?=>?{
?????const?run?=?()?=>?{
?????????if?(this._status?===?STATUS.PENDING)?{
?????????this._status?=?STATUS.REJECTED
?????????this._value?=?value
????????
?????????while?(this._rejectQueue.length)?{
?????????????const?callback?=?this._rejectQueue.shift()
?????????????callback(value)
?????????}
?????}
?}
?????setTimeout(run)
?}
?????//?new?Promise()時立即執(zhí)行executor,并傳入resolve和reject
?????executor(resolve,?reject)
?}
?//?then方法,接收一個成功的回調和一個失敗的回調
?function?then(onFulfilled,?onRejected)?{
??//?根據規(guī)范,如果then的參數不是function,則忽略它,?讓值繼續(xù)往下傳遞,鏈式調用繼續(xù)往下執(zhí)行
??typeof?onFulfilled?!==?'function'???onFulfilled?=?value?=>?value?:?null
??typeof?onRejected?!==?'function'???onRejected?=?error?=>?error?:?null
??//?then?返回一個新的promise
??return?new?MyPromise((resolve,?reject)?=>?{
????const?resolveFn?=?value?=>?{
??????try?{
????????const?x?=?onFulfilled(value)
????????//?分類討論返回值,如果是Promise,那么等待Promise狀態(tài)變更,否則直接resolve
????????x?instanceof?MyPromise???x.then(resolve,?reject)?:?resolve(x)
??????}?catch?(error)?{
????????reject(error)
??????}
????}
??}
}
??const?rejectFn?=?error?=>?{
??????try?{
????????const?x?=?onRejected(error)
????????x?instanceof?MyPromise???x.then(resolve,?reject)?:?resolve(x)
??????}?catch?(error)?{
????????reject(error)
??????}
????}
????switch?(this._status)?{
??????case?STATUS.PENDING:
????????this._resolveQueue.push(resolveFn)
????????this._rejectQueue.push(rejectFn)
????????break;
??????case?STATUS.FULFILLED:
????????resolveFn(this._value)
????????break;
??????case?STATUS.REJECTED:
????????rejectFn(this._value)
????????break;
????}
?})
?}
?catch?(rejectFn)?{
??return?this.then(undefined,?rejectFn)
}
//?promise.finally方法
finally(callback)?{
??return?this.then(value?=>?MyPromise.resolve(callback()).then(()?=>?value),?error?=>?{
????MyPromise.resolve(callback()).then(()?=>?error)
??})
}
?//?靜態(tài)resolve方法
?static?resolve(value)?{
??????return?value?instanceof?MyPromise???value?:?new?MyPromise(resolve?=>?resolve(value))
??}
?//?靜態(tài)reject方法
?static?reject(error)?{
??????return?new?MyPromise((resolve,?reject)?=>?reject(error))
????}
?//?靜態(tài)all方法
?static?all(promiseArr)?{
??????let?count?=?0
??????let?result?=?[]
??????return?new?MyPromise((resolve,?reject)?=>???????{
????????if?(!promiseArr.length)?{
??????????return?resolve(result)
????????}
????????promiseArr.forEach((p,?i)?=>?{
??????????MyPromise.resolve(p).then(value?=>?{
????????????count++
????????????result[i]?=?value
????????????if?(count?===?promiseArr.length)?{
??????????????resolve(result)
????????????}
??????????},?error?=>?{
????????????reject(error)
??????????})
????????})
??????})
????}
?//?靜態(tài)race方法
?static?race(promiseArr)?{
??????return?new?MyPromise((resolve,?reject)?=>?{
????????promiseArr.forEach(p?=>?{
??????????MyPromise.resolve(p).then(value?=>?{
????????????resolve(value)
??????????},?error?=>?{
????????????reject(error)
??????????})
????????})
??????})
????}
}
11. 手寫原生AJAX
創(chuàng)建 XMLHttpRequest 實例 發(fā)出 HTTP 請求 服務器返回 XML 格式的字符串 JS 解析 XML,并更新局部頁面 不過隨著歷史進程的推進,XML 已經被淘汰,取而代之的是?JSON。
myButton.addEventListener('click',?function?()?{
??ajax()
})
function?ajax()?{
??let?xhr?=?new?XMLHttpRequest()?//實例化,以調用方法
??xhr.open('get',?'https://www.google.com')??//參數2,url。參數三:異步
??xhr.onreadystatechange?=?()?=>?{??//每當?readyState?屬性改變時,就會調用該函數。
????if?(xhr.readyState?===?4)?{??//XMLHttpRequest?代理當前所處狀態(tài)。
??????if?(xhr.status?>=?200?&&?xhr.status?300)?{??//200-300請求成功
????????let?string?=?request.responseText
????????//JSON.parse()?方法用來解析JSON字符串,構造由字符串描述的JavaScript值或對象
????????let?object?=?JSON.parse(string)
??????}
????}
??}
??request.send()?//用于實際發(fā)出?HTTP?請求。不帶參數為GET請求
}
function?ajax(url)?{
??const?p?=?new?Promise((resolve,?reject)?=>?{
????let?xhr?=?new?XMLHttpRequest()
????xhr.open('get',?url)
????xhr.onreadystatechange?=?()?=>?{
??????if?(xhr.readyState?==?4)?{
????????if?(xhr.status?>=?200?&&?xhr.status?<=?300)?{
??????????resolve(JSON.parse(xhr.responseText))
????????}?else?{
??????????reject('請求出錯')
????????}
??????}
????}
????xhr.send()??//發(fā)送hppt請求
??})
??return?p
}
let?url?=?'/data.json'
ajax(url).then(res?=>?console.log(res))
??.catch(reason?=>?console.log(reason))
12. 手寫節(jié)流防抖函數
function?debounce(fn,?delay)?{
?????if(typeof?fn!=='function')?{
????????throw?new?TypeError('fn不是函數')
?????}
?????let?timer;?//?維護一個?timer
?????return?function?()?{
?????????var?_this?=?this;?//?取debounce執(zhí)行作用域的this(原函數掛載到的對象)
?????????var?args?=?arguments;
?????????if?(timer)?{
????????????clearTimeout(timer);
?????????}
?????????timer?=?setTimeout(function?()?{
????????????fn.apply(_this,?args);?//?用apply指向調用debounce的對象,相當于_this.fn(args);
?????????},?delay);
?????};
}
input1.addEventListener('keyup',?debounce(()?=>?{
?console.log(input1.value)
}),?600)
function?throttle(fn,?delay)?{
??let?timer;
??return?function?()?{
????var?_this?=?this;
????var?args?=?arguments;
????if?(timer)?{
??????return;
????}
????timer?=?setTimeout(function?()?{
??????fn.apply(_this,?args);?//?這里args接收的是外邊返回的函數的參數,不能用arguments
??????//?fn.apply(_this,?arguments);?需要注意:Chrome?14?以及?Internet?Explorer?9?仍然不接受類數組對象。如果傳入類數組對象,它們會拋出異常。
??????timer?=?null;?//?在delay后執(zhí)行完fn之后清空timer,此時timer為假,throttle觸發(fā)可以進入計時器
????},?delay)
??}
}
div1.addEventListener('drag',?throttle((e)?=>?{
??console.log(e.offsetX,?e.offsetY)
},?100))
13. 手寫Promise加載圖片
function?getData(url)?{
??return?new?Promise((resolve,?reject)?=>?{
????$.ajax({
??????url,
??????success(data)?{
????????resolve(data)
??????},
??????error(err)?{
????????reject(err)
??????}
????})
??})
}
const?url1?=?'./data1.json'
const?url2?=?'./data2.json'
const?url3?=?'./data3.json'
getData(url1).then(data1?=>?{
??console.log(data1)
??return?getData(url2)
}).then(data2?=>?{
??console.log(data2)
??return?getData(url3)
}).then(data3?=>
??console.log(data3)
).catch(err?=>
??console.error(err)
)
14. 函數實現一秒鐘輸出一個數
for(let?i=0;i<=10;i++){???//用var打印的都是11
?setTimeout(()=>{
????console.log(i);
?},1000*i)
}
15. 創(chuàng)建10個標簽,點擊的時候彈出來對應的序號?
var?a
for(let?i=0;i<10;i++){
?a=document.createElement('a')
?a.innerHTML=i+'
'
?a.addEventListener('click',function(e){
?????console.log(this)??//this為當前點擊的
?????e.preventDefault()??//如果調用這個方法,默認事件行為將不再觸發(fā)。
?????//例如,在執(zhí)行這個方法后,如果點擊一個鏈接(a標簽),瀏覽器不會跳轉到新的?URL?去了。我們可以用?event.isDefaultPrevented()?來確定這個方法是否(在那個事件對象上)被調用過了。
?????alert(i)
?})
?const?d=document.querySelector('div')
?d.appendChild(a)??//append向一個已存在的元素追加該元素。
}

評論
圖片
表情
