一文幫你搞定 90% 的 JS 手寫題
點(diǎn)擊上方關(guān)注?前端技術(shù)江湖,一起學(xué)習(xí),天天進(jìn)步
還在害怕手寫題嗎,本文可以幫你擴(kuò)展并鞏固自己的JS基礎(chǔ),順便搞定90%的手寫題。在工作中還可以對(duì)常用的需求進(jìn)行手寫實(shí)現(xiàn),比如深拷貝、防抖節(jié)流等可以直接用于往后的項(xiàng)目中,提高項(xiàng)目開發(fā)效率。不說(shuō)廢話了,下面就直接上代碼吧。
1.call的實(shí)現(xiàn)
第一個(gè)參數(shù)為null或者undefined時(shí),this指向全局對(duì)象window,值為原始值的指向該原始值的自動(dòng)包裝對(duì)象,如 String、Number、Boolean 為了避免函數(shù)名與上下文(context)的屬性發(fā)生沖突,使用Symbol類型作為唯一值 將函數(shù)作為傳入的上下文(context)屬性執(zhí)行 函數(shù)執(zhí)行完成后刪除該屬性 返回執(zhí)行結(jié)果
Function.prototype.myCall?=?function(context,...args){
????let?cxt?=?context?||?window;
????//將當(dāng)前被調(diào)用的方法定義在cxt.func上.(為了能以對(duì)象調(diào)用形式綁定this)
????//新建一個(gè)唯一的Symbol變量避免重復(fù)
????let?func?=?Symbol()?
????cxt[func]?=?this;
????args?=?args???args?:?[]
????//以對(duì)象調(diào)用形式調(diào)用func,此時(shí)this指向cxt?也就是傳入的需要綁定的this指向
????const?res?=?args.length?>?0???cxt[func](...args)?:?cxt[func]();
????//刪除該方法,不然會(huì)對(duì)傳入對(duì)象造成污染(添加該方法)
????delete?cxt[func];
????return?res;
}
2.apply的實(shí)現(xiàn)
前部分與call一樣 第二個(gè)參數(shù)可以不傳,但類型必須為數(shù)組或者類數(shù)組
Function.prototype.myApply?=?function(context,args?=?[]){
????let?cxt?=?context?||?window;
????//將當(dāng)前被調(diào)用的方法定義在cxt.func上.(為了能以對(duì)象調(diào)用形式綁定this)
????//新建一個(gè)唯一的Symbol變量避免重復(fù)
????let?func?=?Symbol()
????cxt[func]?=?this;
????//以對(duì)象調(diào)用形式調(diào)用func,此時(shí)this指向cxt?也就是傳入的需要綁定的this指向
????const?res?=?args.length?>?0???cxt[func](...args)?:?cxt[func]();
????delete?cxt[func];
????return?res;
}
3.bind的實(shí)現(xiàn)
需要考慮:
bind() 除了 this 外,還可傳入多個(gè)參數(shù); bind 創(chuàng)建的新函數(shù)可能傳入多個(gè)參數(shù); 新函數(shù)可能被當(dāng)做構(gòu)造函數(shù)調(diào)用; 函數(shù)可能有返回值;
實(shí)現(xiàn)方法:
bind 方法不會(huì)立即執(zhí)行,需要返回一個(gè)待執(zhí)行的函數(shù);(閉包) 實(shí)現(xiàn)作用域綁定(apply) 參數(shù)傳遞(apply 的數(shù)組傳參) 當(dāng)作為構(gòu)造函數(shù)的時(shí)候,進(jìn)行原型繼承
Function.prototype.myBind?=?function?(context,?...args)?{
????//新建一個(gè)變量賦值為this,表示當(dāng)前函數(shù)
????const?fn?=?this
????//判斷有沒有傳參進(jìn)來(lái),若為空則賦值[]
????args?=?args???args?:?[]
????//返回一個(gè)newFn函數(shù),在里面調(diào)用fn
????return?function?newFn(...newFnArgs)?{
????????if?(this?instanceof?newFn)?{
????????????return?new?fn(...args,?...newFnArgs)
????????}
????????return?fn.apply(context,?[...args,...newFnArgs])
????}
}
測(cè)試
let?name?=?'小王',age?=17;
let?obj?=?{
????name:'小張',
????age:?this.age,
????myFun:?function(from,to){
????????console.log(this.name?+?'?年齡?'?+?this.age+'來(lái)自?'+from+'去往'+?to)
????}
}
let?db?=?{
????name:?'德瑪',
????age:?99
}
//結(jié)果
obj.myFun.myCall(db,'成都','上海');?????//?德瑪?年齡?99??來(lái)自?成都去往上海
obj.myFun.myApply(db,['成都','上海']);??????//?德瑪?年齡?99??來(lái)自?成都去往上海
obj.myFun.myBind(db,'成都','上海')();???????//?德瑪?年齡?99??來(lái)自?成都去往上海
obj.myFun.myBind(db,['成都','上海'])();???//?德瑪?年齡?99??來(lái)自?成都,?上海去往?undefined
4.寄生式組合繼承
function?Person(obj)?{
????this.name?=?obj.name
????this.age?=?obj.age
}
Person.prototype.add?=?function(value){
????console.log(value)
}
var?p1?=?new?Person({name:"番茄",?age:?18})
function?Person1(obj)?{
????Person.call(this,?obj)
????this.sex?=?obj.sex
}
//?這一步是繼承的關(guān)鍵
Person1.prototype?=?Object.create(Person.prototype);
Person1.prototype.constructor?=?Person1;
Person1.prototype.play?=?function(value){
????console.log(value)
}
var?p2?=?new?Person1({name:"雞蛋",?age:?118,?sex:?"男"})
5.ES6繼承
//class?相當(dāng)于es5中構(gòu)造函數(shù)
//class中定義方法時(shí),前后不能加function,全部定義在class的protopyte屬性中
//class中定義的所有方法是不可枚舉的
//class中只能定義方法,不能定義對(duì)象,變量等
//class和方法內(nèi)默認(rèn)都是嚴(yán)格模式
//es5中constructor為隱式屬性
class?People{
??constructor(name='wang',age='27'){
????this.name?=?name;
????this.age?=?age;
??}
??eat(){
????console.log(`${this.name}?${this.age}?eat?food`)
??}
}
//繼承父類
class?Woman?extends?People{?
???constructor(name?=?'ren',age?=?'27'){?
?????//繼承父類屬性
?????super(name,?age);?
???}?
????eat(){?
?????//繼承父類方法
??????super.eat()?
????}?
}?
let?wonmanObj=new?Woman('xiaoxiami');?
wonmanObj.eat();
//es5繼承先創(chuàng)建子類的實(shí)例對(duì)象,然后再將父類的方法添加到this上(Parent.apply(this))。?
//es6繼承是使用關(guān)鍵字super先創(chuàng)建父類的實(shí)例對(duì)象this,最后在子類class中修改this。
6.new的實(shí)現(xiàn)
一個(gè)繼承自 Foo.prototype 的新對(duì)象被創(chuàng)建。 使用指定的參數(shù)調(diào)用構(gòu)造函數(shù) Foo,并將 this 綁定到新創(chuàng)建的對(duì)象。new Foo 等同于 new Foo(),也就是沒有指定參數(shù)列表,F(xiàn)oo 不帶任何參數(shù)調(diào)用的情況。 由構(gòu)造函數(shù)返回的對(duì)象就是 new 表達(dá)式的結(jié)果。如果構(gòu)造函數(shù)沒有顯式返回一個(gè)對(duì)象,則使用步驟1創(chuàng)建的對(duì)象。 一般情況下,構(gòu)造函數(shù)不返回值,但是用戶可以選擇主動(dòng)返回對(duì)象,來(lái)覆蓋正常的對(duì)象創(chuàng)建步驟
function?Ctor(){
????....
}
function?myNew(ctor,...args){
????if(typeof?ctor?!==?'function'){
??????throw?'myNew?function?the?first?param?must?be?a?function';
????}
????var?newObj?=?Object.create(ctor.prototype);?//創(chuàng)建一個(gè)繼承自ctor.prototype的新對(duì)象
????var?ctorReturnResult?=?ctor.apply(newObj,?args);?//將構(gòu)造函數(shù)ctor的this綁定到newObj中
????var?isObject?=?typeof?ctorReturnResult?===?'object'?&&?ctorReturnResult?!==?null;
????var?isFunction?=?typeof?ctorReturnResult?===?'function';
????if(isObject?||?isFunction){
????????return?ctorReturnResult;
????}
????return?newObj;
}
let?c?=?myNew(Ctor);
7.instanceof的實(shí)現(xiàn)
instanceof 是用來(lái)判斷A是否為B的實(shí)例,表達(dá)式為:A instanceof B,如果A是B的實(shí)例,則返回true,否則返回false。 instanceof 運(yùn)算符用來(lái)測(cè)試一個(gè)對(duì)象在其原型鏈中是否存在一個(gè)構(gòu)造函數(shù)的 prototype 屬性。 不能檢測(cè)基本數(shù)據(jù)類型,在原型鏈上的結(jié)果未必準(zhǔn)確,不能檢測(cè)null,undefined 實(shí)現(xiàn):遍歷左邊變量的原型鏈,直到找到右邊變量的 prototype,如果沒有找到,返回 false
function?myInstanceOf(a,b){
????let?left?=?a.__proto__;
????let?right?=?b.prototype;
????while(true){
????????if(left?==?null){
????????????return?false
????????}
????????if(left?==?right){
????????????return?true
????????}
????????left?=?left.__proto__
????}
}
//instanceof 運(yùn)算符用于判斷構(gòu)造函數(shù)的 prototype 屬性是否出現(xiàn)在對(duì)象的原型鏈中的任何位置。
function?myInstanceof(left,?right)?{
????let?proto?=?Object.getPrototypeOf(left),?//?獲取對(duì)象的原型
????prototype?=?right.prototype;?//?獲取構(gòu)造函數(shù)的?prototype?對(duì)象
????//?判斷構(gòu)造函數(shù)的?prototype?對(duì)象是否在對(duì)象的原型鏈上
????while?(true)?{
????????if?(!proto)?return?false;
????????if?(proto?===?prototype)?return?true;
????????proto?=?Object.getPrototypeOf(proto);
????}
}
8.Object.create()的實(shí)現(xiàn)
MDN文檔 Object.create()會(huì)將參數(shù)對(duì)象作為一個(gè)新創(chuàng)建的空對(duì)象的原型, 并返回這個(gè)空對(duì)象
//簡(jiǎn)略版
function?myCreate(obj){
????//?新聲明一個(gè)函數(shù)
????function?C(){};
????//?將函數(shù)的原型指向obj
????C.prototype?=?obj;
????//?返回這個(gè)函數(shù)的實(shí)力化對(duì)象
????return?new?C()
}
//官方版Polyfill
if?(typeof?Object.create?!==?"function")?{
????Object.create?=?function?(proto,?propertiesObject)?{
????????if?(typeof?proto?!==?'object'?&&?typeof?proto?!==?'function')?{
????????????throw?new?TypeError('Object?prototype?may?only?be?an?Object:?'?+?proto);
????????}?else?if?(proto?===?null)?{
????????????throw?new?Error("This?browser's?implementation?of?Object.create?is?a?shim?and?doesn't?support?'null'?as?the?first?argument.");
????????}
????????if?(typeof?propertiesObject?!==?'undefined')?throw?new?Error("This?browser's?implementation?of?Object.create?is?a?shim?and?doesn't?support?a?second?argument.");
????????function?F()?{}
????????F.prototype?=?proto;
????????return?new?F();
????};
}
9.實(shí)現(xiàn) Object.assign
Object.assign2?=?function(target,?...source)?{
????if?(target?==?null)?{
????????throw?new?TypeError('Cannot?convert?undefined?or?null?to?object')
????}
????let?ret?=?Object(target)?
????source.forEach(function(obj)?{
????????if?(obj?!=?null)?{
????????????for?(let?key?in?obj)?{
????????????????if?(obj.hasOwnProperty(key))?{
????????????????????ret[key]?=?obj[key]
????????????????}
????????????}
????????}
????})
????return?ret
}
10.Promise的實(shí)現(xiàn)
實(shí)現(xiàn) Promise 需要完全讀懂 Promise A+ 規(guī)范,不過(guò)從總體的實(shí)現(xiàn)上看,有如下幾個(gè)點(diǎn)需要考慮到:
Promise本質(zhì)是一個(gè)狀態(tài)機(jī),且狀態(tài)只能為以下三種:Pending(等待態(tài))、Fulfilled(執(zhí)行態(tài))、Rejected(拒絕態(tài)),狀態(tài)的變更是單向的,只能從Pending -> Fulfilled 或 Pending -> Rejected,狀態(tài)變更不可逆 then 需要支持鏈?zhǔn)秸{(diào)用
class?Promise?{
????callbacks?=?[];
????state?=?'pending';//增加狀態(tài)
????value?=?null;//保存結(jié)果
????constructor(fn)?{
????????fn(this._resolve.bind(this),?this._reject.bind(this));
????}
????then(onFulfilled,?onRejected)?{
????????return?new?Promise((resolve,?reject)?=>?{
????????????this._handle({
????????????????onFulfilled:?onFulfilled?||?null,
????????????????onRejected:?onRejected?||?null,
????????????????resolve:?resolve,
????????????????reject:?reject
????????????});
????????});
????}
????_handle(callback)?{
????????if?(this.state?===?'pending')?{
????????????this.callbacks.push(callback);
????????????return;
????????}
?
????????let?cb?=?this.state?===?'fulfilled'???callback.onFulfilled?:?callback.onRejected;
?
????????if?(!cb)?{//如果then中沒有傳遞任何東西
????????????cb?=?this.state?===?'fulfilled'???callback.resolve?:?callback.reject;
????????????cb(this.value);
????????????return;
????????}
?
????????let?ret?=?cb(this.value);
????????cb?=?this.state?===?'fulfilled'???callback.resolve?:?callback.reject;
????????cb(ret);
????}
????_resolve(value)?{
?
????????if?(value?&&?(typeof?value?===?'object'?||?typeof?value?===?'function'))?{
????????????var?then?=?value.then;
????????????if?(typeof?then?===?'function')?{
????????????????then.call(value,?this._resolve.bind(this),?this._reject.bind(this));
????????????????return;
????????????}
????????}
?
????????this.state?=?'fulfilled';//改變狀態(tài)
????????this.value?=?value;//保存結(jié)果
????????this.callbacks.forEach(callback?=>?this._handle(callback));
????}
????_reject(error)?{
????????this.state?=?'rejected';
????????this.value?=?error;
????????this.callbacks.forEach(callback?=>?this._handle(callback));
????}
}
Promise.resolve
Promsie.resolve(value) 可以將任何值轉(zhuǎn)成值為 value 狀態(tài)是 fulfilled 的 Promise,但如果傳入的值本身是 Promise 則會(huì)原樣返回它。
Promise.resolve(value)?{
??if?(value?&&?value?instanceof?Promise)?{
????return?value;
??}?else?if?(value?&&?typeof?value?===?'object'?&&?typeof?value.then?===?'function')?{
????let?then?=?value.then;
????return?new?Promise(resolve?=>?{
??????then(resolve);
????});
??}?else?if?(value)?{
????return?new?Promise(resolve?=>?resolve(value));
??}?else?{
????return?new?Promise(resolve?=>?resolve());
??}
}
Promise.reject
和 Promise.resolve() 類似,Promise.reject() 會(huì)實(shí)例化一個(gè) rejected 狀態(tài)的 Promise。但與 Promise.resolve() 不同的是,如果給 Promise.reject() 傳遞一個(gè) Promise 對(duì)象,則這個(gè)對(duì)象會(huì)成為新 Promise 的值。
Promise.reject?=?function(reason)?{
????return?new?Promise((resolve,?reject)?=>?reject(reason))
}
Promise.all
傳入的所有 Promsie 都是 fulfilled,則返回由他們的值組成的,狀態(tài)為 fulfilled 的新 Promise; 只要有一個(gè) Promise 是 rejected,則返回 rejected 狀態(tài)的新 Promsie,且它的值是第一個(gè) rejected 的 Promise 的值; 只要有一個(gè) Promise 是 pending,則返回一個(gè) pending 狀態(tài)的新 Promise;
Promise.all?=?function(promiseArr)?{
????let?index?=?0,?result?=?[]
????return?new?Promise((resolve,?reject)?=>?{
????????promiseArr.forEach((p,?i)?=>?{
????????????Promise.resolve(p).then(val?=>?{
????????????????index++
????????????????result[i]?=?val
????????????????if?(index?===?promiseArr.length)?{
????????????????????resolve(result)
????????????????}
????????????},?err?=>?{
????????????????reject(err)
????????????})
????????})
????})
}
Promise.race
Promise.race 會(huì)返回一個(gè)由所有可迭代實(shí)例中第一個(gè) fulfilled 或 rejected 的實(shí)例包裝后的新實(shí)例。
Promise.race?=?function(promiseArr)?{
????return?new?Promise((resolve,?reject)?=>?{
????????promiseArr.forEach(p?=>?{
????????????Promise.resolve(p).then(val?=>?{
????????????????resolve(val)
????????????},?err?=>?{
????????????????rejecte(err)
????????????})
????????})
????})
}
11.Ajax的實(shí)現(xiàn)
function?ajax(url,method,body,headers){
????return?new?Promise((resolve,reject)=>{
????????let?req?=?new?XMLHttpRequest();
????????req.open(methods,url);
????????for(let?key?in?headers){
????????????req.setRequestHeader(key,headers[key])
????????}
????????req.onreadystatechange(()=>{
????????????if(req.readystate?==?4){
????????????????if(req.status?>=?'200'?&&?req.status?<=?300){
????????????????????resolve(req.responeText)
????????????????}else{
????????????????????reject(req)
????????????????}
????????????}
????????})
????????req.send(body)
????})
}
12.實(shí)現(xiàn)防抖函數(shù)(debounce)
連續(xù)觸發(fā)在最后一次執(zhí)行方法,場(chǎng)景:輸入框匹配
let?debounce?=?(fn,time?=?1000)?=>?{
????let?timeLock?=?null
????return?function?(...args){
????????clearTimeout(timeLock)
????????timeLock?=?setTimeout(()=>{
????????????fn(...args)
????????},time)
????}
}
13.實(shí)現(xiàn)節(jié)流函數(shù)(throttle)
在一定時(shí)間內(nèi)只觸發(fā)一次,場(chǎng)景:長(zhǎng)列表滾動(dòng)節(jié)流
let?throttle?=?(fn,time?=?1000)?=>?{
????let?flag?=?true;
????return?function?(...args){
????????if(flag){
????????????flag?=?false;
????????????setTimeout(()=>{
????????????????flag?=?true;
????????????????fn(...args)
????????????},time)
????????}
????}
}
14.深拷貝(deepclone)
判斷類型,正則和日期直接返回新對(duì)象 空或者非對(duì)象類型,直接返回原值 考慮循環(huán)引用,判斷如果hash中含有直接返回hash中的值 新建一個(gè)相應(yīng)的new obj.constructor加入hash 遍歷對(duì)象遞歸(普通key和key是symbol情況)
function?deepClone(obj,hash?=?new?WeakMap()){
????if(obj?instanceof?RegExp)?return?new?RegExp(obj);
????if(obj?instanceof?Date)?return?new?Date(obj);
????if(obj?===?null?||?typeof?obj?!==?'object')?return?obj;
????//循環(huán)引用的情況
????if(hash.has(obj)){
????????return?hash.get(obj)
????}
????//new?一個(gè)相應(yīng)的對(duì)象
????//obj為Array,相當(dāng)于new?Array()
????//obj為Object,相當(dāng)于new?Object()
????let?constr?=?new?obj.constructor();
????hash.set(obj,constr);
????for(let?key?in?obj){
????????if(obj.hasOwnProperty(key)){
????????????constr[key]?=?deepClone(obj[key],hash)
????????}
????}
????//考慮symbol的情況
????let?symbolObj?=?Object.getOwnPropertySymbols(obj)
????for(let?i=0;i????????if(obj.hasOwnProperty(symbolObj[i])){
????????????constr[symbolObj[i]]?=?deepClone(obj[symbolObj[i]],hash)
????????}
????}
????return?constr
}
15.數(shù)組扁平化的實(shí)現(xiàn)(flat)
let?arr?=?[1,2,[3,4,[5,[6]]]]
console.log(arr.flat(Infinity))//flat參數(shù)為指定要提取嵌套數(shù)組的結(jié)構(gòu)深度,默認(rèn)值為?1
//用reduce實(shí)現(xiàn)
function?fn(arr){
???return?arr.reduce((prev,cur)=>{
??????return?prev.concat(Array.isArray(cur)?fn(cur):cur)
???},[])
}
16.函數(shù)柯里化
function?sumFn(a,b,c){return?a+?b?+?c};
let?sum?=?curry(sumFn);
sum(2)(3)(5)//10
sum(2,3)(5)//10
function?curry(fn,...args){
??let?fnLen?=?fn.length,
??????argsLen?=?args.length;
??//對(duì)比函數(shù)的參數(shù)和當(dāng)前傳入?yún)?shù)
??//若參數(shù)不夠就繼續(xù)遞歸返回curry
??//若參數(shù)夠就調(diào)用函數(shù)返回相應(yīng)的值
??if(fnLen?>?argsLen){
????return?function(...arg2s){
??????return?curry(fn,...args,...arg2s)
????}
??}else{
????return?fn(...args)
??}
}
17.使用閉包實(shí)現(xiàn)每隔一秒打印 1,2,3,4
for?(var?i=1;?i<=5;?i++)?{
??(function?(i)?{
????setTimeout(()?=>?console.log(i),?1000*i)
??})(i)
}
18.手寫一個(gè) jsonp
const?jsonp?=?function?(url,?data)?{
????return?new?Promise((resolve,?reject)?=>?{
????????//?初始化url
????????let?dataString?=?url.indexOf('?')?===?-1???'?'?:?''
????????let?callbackName?=?`jsonpCB_${Date.now()}`
????????url?+=?`${dataString}callback=${callbackName}`
????????if?(data)?{
????????????//?有請(qǐng)求參數(shù),依次添加到url
????????????for?(let?k?in?data)?{
????????????????url?+=?`${k}=${data[k]}`
????????????}
????????}
????????let?jsNode?=?document.createElement('script')
????????jsNode.src?=?url
????????//?觸發(fā)callback,觸發(fā)后刪除js標(biāo)簽和綁定在window上的callback
????????window[callbackName]?=?result?=>?{
????????????delete?window[callbackName]
????????????document.body.removeChild(jsNode)
????????????if?(result)?{
????????????????resolve(result)
????????????}?else?{
????????????????reject('沒有返回?cái)?shù)據(jù)')
????????????}
????????}
????????//?js加載異常的情況
????????jsNode.addEventListener('error',?()?=>?{
????????????delete?window[callbackName]
????????????document.body.removeChild(jsNode)
????????????reject('JavaScript資源加載失敗')
????????},?false)
????????//?添加js節(jié)點(diǎn)到document上時(shí),開始請(qǐng)求
????????document.body.appendChild(jsNode)
????})
}
jsonp('http://192.168.0.103:8081/jsonp',?{
????a:?1,
????b:?'heiheihei'
})
.then(result?=>?{
????console.log(result)
})
.catch(err?=>?{
????console.error(err)
})
19.手寫一個(gè)觀察者模式
class?Subject{
??constructor(name){
????this.name?=?name
????this.observers?=?[]
????this.state?=?'XXXX'
??}
??//?被觀察者要提供一個(gè)接受觀察者的方法
??attach(observer){
????this.observers.push(observer)
??}
??//?改變被觀察著的狀態(tài)
??setState(newState){
????this.state?=?newState
????this.observers.forEach(o=>{
??????o.update(newState)
????})
??}
}
class?Observer{
??constructor(name){
????this.name?=?name
??}
??update(newState){
????console.log(`${this.name}say:${newState}`)
??}
}
//?被觀察者?燈
let?sub?=?new?Subject('燈')
let?mm?=?new?Observer('小明')
let?jj?=?new?Observer('小健')
?
//?訂閱?觀察者
sub.attach(mm)
sub.attach(jj)
?
sub.setState('燈亮了來(lái)電了')
20.EventEmitter 實(shí)現(xiàn)
class?EventEmitter?{
????constructor()?{
????????this.events?=?{};
????}
????on(event,?callback)?{
????????let?callbacks?=?this.events[event]?||?[];
????????callbacks.push(callback);
????????this.events[event]?=?callbacks;
????????return?this;
????}
????off(event,?callback)?{
????????let?callbacks?=?this.events[event];
????????this.events[event]?=?callbacks?&&?callbacks.filter(fn?=>?fn?!==?callback);
????????return?this;
????}
????emit(event,?...args)?{
????????let?callbacks?=?this.events[event];
????????callbacks.forEach(fn?=>?{
????????????fn(...args);
????????});
????????return?this;
????}
????once(event,?callback)?{
????????let?wrapFun?=?function?(...args)?{
????????????callback(...args);
????????????this.off(event,?wrapFun);
????????};
????????this.on(event,?wrapFun);
????????return?this;
????}
}
21.生成隨機(jī)數(shù)的各種方法?
function?getRandom(min,?max)?{
??return?Math.floor(Math.random()?*?(max?-?min))?+?min???
}
22.如何實(shí)現(xiàn)數(shù)組的隨機(jī)排序?
let?arr?=?[2,3,454,34,324,32]
arr.sort(randomSort)
function?randomSort(a,?b)?{
??return?Math.random()?>?0.5???-1?:?1;
}
23.寫一個(gè)通用的事件偵聽器函數(shù)。
const?EventUtils?=?{
??//?視能力分別使用dom0||dom2||IE方式?來(lái)綁定事件
??//?添加事件
??addEvent:?function(element,?type,?handler)?{
????if?(element.addEventListener)?{
??????element.addEventListener(type,?handler,?false);
????}?else?if?(element.attachEvent)?{
??????element.attachEvent("on"?+?type,?handler);
????}?else?{
??????element["on"?+?type]?=?handler;
????}
??},
??//?移除事件
??removeEvent:?function(element,?type,?handler)?{
????if?(element.removeEventListener)?{
??????element.removeEventListener(type,?handler,?false);
????}?else?if?(element.detachEvent)?{
??????element.detachEvent("on"?+?type,?handler);
????}?else?{
??????element["on"?+?type]?=?null;
????}
??},
?//?獲取事件目標(biāo)
??getTarget:?function(event)?{
????return?event.target?||?event.srcElement;
??},
??//?獲取?event?對(duì)象的引用,取到事件的所有信息,確保隨時(shí)能使用?event
??getEvent:?function(event)?{
????return?event?||?window.event;
??},
?//?阻止事件(主要是事件冒泡,因?yàn)?IE?不支持事件捕獲)
??stopPropagation:?function(event)?{
????if?(event.stopPropagation)?{
??????event.stopPropagation();
????}?else?{
??????event.cancelBubble?=?true;
????}
??},
??//?取消事件的默認(rèn)行為
??preventDefault:?function(event)?{
????if?(event.preventDefault)?{
??????event.preventDefault();
????}?else?{
??????event.returnValue?=?false;
????}
??}
};
24.使用迭代的方式實(shí)現(xiàn) flatten 函數(shù)。
var?arr?=?[1,?2,?3,?[4,?5],?[6,?[7,?[8]]]]
/**?*?使用遞歸的方式處理?*?wrap?內(nèi)保
存結(jié)果?ret?*?返回一個(gè)遞歸函數(shù)?**/
function?wrap()?{
????var?ret?=?[];
????return?function?flat(a)?{
????????for?(var?item?of
????????????a)?{
????????????????if?(item.constructor?===?Array)?{
????????????????????ret.concat(flat(item))
????????????????}?else?{
????????????????????ret.push(item)
????????????????}
????????}
????????return?ret
????}
}?
console.log(wrap()(arr));
25.怎么實(shí)現(xiàn)一個(gè)sleep
sleep函數(shù)作用是讓線程休眠,等到指定時(shí)間在重新喚起。
function?sleep(delay)?{
??var?start?=?(new?Date()).getTime();
??while?((new?Date()).getTime()?-?start?????continue;
??}
}
function?test()?{
??console.log('111');
??sleep(2000);
??console.log('222');
}
test()
26.實(shí)現(xiàn)正則切分千分位(10000 => 10,000)
//無(wú)小數(shù)點(diǎn)
let?num1?=?'1321434322222'
num1.replace(/(\d)(?=(\d{3})+$)/g,'$1,')
//有小數(shù)點(diǎn)
let?num2?=?'342243242322.3432423'
num2.replace(/(\d)(?=(\d{3})+\.)/g,'$1,')
27.對(duì)象數(shù)組去重
輸入:
[{a:1,b:2,c:3},{b:2,c:3,a:1},{d:2,c:2}]
輸出:
[{a:1,b:2,c:3},{d:2,c:2}]
首先寫一個(gè)函數(shù)把對(duì)象中的key排序,然后再轉(zhuǎn)成字符串 遍歷數(shù)組利用Set將轉(zhuǎn)為字符串后的對(duì)象去重
function?objSort(obj){
????let?newObj?=?{}
????//遍歷對(duì)象,并將key進(jìn)行排序
????Object.keys(obj).sort().map(key?=>?{
????????newObj[key]?=?obj[key]
????})
????//將排序好的數(shù)組轉(zhuǎn)成字符串
????return?JSON.stringify(newObj)
}
function?unique(arr){
????let?set?=?new?Set();
????for(let?i=0;i????????let?str?=?objSort(arr[i])
????????set.add(str)
????}
????//將數(shù)組中的字符串轉(zhuǎn)回對(duì)象
????arr?=?[...set].map(item?=>?{
????????return?JSON.parse(item)
????})
????return?arr
}
28.解析 URL Params 為對(duì)象
let?url?=?'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/*?結(jié)果
{?user:?'anonymous',
??id:?[?123,?456?],?//?重復(fù)出現(xiàn)的?key?要組裝成數(shù)組,能被轉(zhuǎn)成數(shù)字的就轉(zhuǎn)成數(shù)字類型
??city:?'北京',?//?中文需解碼
??enabled:?true,?//?未指定值得?key?約定為?true
}
*/
function?parseParam(url)?{
??const?paramsStr?=?/.+\?(.+)$/.exec(url)[1];?//?將???后面的字符串取出來(lái)
??const?paramsArr?=?paramsStr.split('&');?//?將字符串以?&?分割后存到數(shù)組中
??let?paramsObj?=?{};
??//?將?params?存到對(duì)象中
??paramsArr.forEach(param?=>?{
????if?(/=/.test(param))?{?//?處理有?value?的參數(shù)
??????let?[key,?val]?=?param.split('=');?//?分割?key?和?value
??????val?=?decodeURIComponent(val);?//?解碼
??????val?=?/^\d+$/.test(val)???parseFloat(val)?:?val;?//?判斷是否轉(zhuǎn)為數(shù)字
??????if?(paramsObj.hasOwnProperty(key))?{?//?如果對(duì)象有?key,則添加一個(gè)值
????????paramsObj[key]?=?[].concat(paramsObj[key],?val);
??????}?else?{?//?如果對(duì)象沒有這個(gè)?key,創(chuàng)建?key?并設(shè)置值
????????paramsObj[key]?=?val;
??????}
????}?else?{?//?處理沒有?value?的參數(shù)
??????paramsObj[param]?=?true;
????}
??})
??return?paramsObj;
}
29.模板引擎實(shí)現(xiàn)
let?template?=?'我是{{name}},年齡{{age}},性別{{sex}}';
let?data?=?{
??name:?'姓名',
??age:?18
}
render(template,?data);?//?我是姓名,年齡18,性別undefined
function?render(template,?data)?{
??const?reg?=?/\{\{(\w+)\}\}/;?//?模板字符串正則
??if?(reg.test(template))?{?//?判斷模板里是否有模板字符串
????const?name?=?reg.exec(template)[1];?//?查找當(dāng)前模板里第一個(gè)模板字符串的字段
????template?=?template.replace(reg,?data[name]);?//?將第一個(gè)模板字符串渲染
????return?render(template,?data);?//?遞歸的渲染并返回渲染后的結(jié)構(gòu)
??}
??return?template;?//?如果模板沒有模板字符串直接返回
}
30.轉(zhuǎn)化為駝峰命名
var?s1?=?"get-element-by-id"
//?轉(zhuǎn)化為?getElementById
var?f?=?function(s)?{
????return?s.replace(/-\w/g,?function(x)?{
????????return?x.slice(1).toUpperCase();
????})
}
31.查找字符串中出現(xiàn)最多的字符和個(gè)數(shù)
例: abbcccddddd -> 字符最多的是d,出現(xiàn)了5次
let?str?=?"abcabcabcbbccccc";
let?num?=?0;
let?char?=?'';
?//?使其按照一定的次序排列
str?=?str.split('').sort().join('');
//?"aaabbbbbcccccccc"
//?定義正則表達(dá)式
let?re?=?/(\w)\1+/g;
str.replace(re,($0,$1)?=>?{
????if(num?$0.length){
????????num?=?$0.length;
????????char?=?$1;????????
????}
});
console.log(`字符最多的是${char},出現(xiàn)了${num}次`);
32.圖片懶加載
let?imgList?=?[...document.querySelectorAll('img')]
let?length?=?imgList.length
const?imgLazyLoad?=?function()?{
????let?count?=?0
????return?(function()?{
????????let?deleteIndexList?=?[]
????????imgList.forEach((img,?index)?=>?{
????????????let?rect?=?img.getBoundingClientRect()
????????????if?(rect.top?????????????????img.src?=?img.dataset.src
????????????????deleteIndexList.push(index)
????????????????count++
????????????????if?(count?===?length)?{
????????????????????document.removeEventListener('scroll',?imgLazyLoad)
????????????????}
????????????}
????????})
????????imgList?=?imgList.filter((img,?index)?=>?!deleteIndexList.includes(index))
????})()
}
//?這里最好加上防抖處理
document.addEventListener('scroll',?imgLazyLoad)
參考資料
高頻 JavaScript 手寫 初、中級(jí)前端應(yīng)該要掌握的手寫代碼實(shí)現(xiàn) 22 道高頻 JavaScript 手寫 死磕 36 個(gè) JS 手寫題(搞懂后,提升真的大)
作者:xpsilvester
https://juejin.cn/post/6963167124881670152
The End
歡迎自薦投稿到《前端技術(shù)江湖》,如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),記得點(diǎn)個(gè)?「在看」哦
點(diǎn)個(gè)『在看』支持下?
評(píng)論
圖片
表情
