手撕 32 個面試高頻知識,輕松應對編程題
作者:洛霞(字節(jié)跳動)
來源:https://juejin.im/post/6875152247714480136
作為前端開發(fā),JS是重中之重,最近結(jié)束了面試的高峰期,基本上offer也定下來了就等開獎,趁著這個時間總結(jié)下32個手寫JS問題,這些都是高頻面試題,希望對你能有所幫助。
關(guān)于源碼都緊遵規(guī)范,都可跑通MDN示例,其余的大多會涉及一些關(guān)于JS的應用題和本人面試過程
01.數(shù)組扁平化
數(shù)組扁平化是指將一個多維數(shù)組變?yōu)橐粋€一維數(shù)組
const?arr?=?[1,?[2,?[3,?[4,?5]]],?6];
//?=>?[1,?2,?3,?4,?5,?6]
方法一:使用flat()
const?res1?=?arr.flat(Infinity);
方法二:利用正則
const?res2?=?JSON.stringify(arr).replace(/\[|\]/g,?'').split(',');
但數(shù)據(jù)類型都會變?yōu)樽址?/p>
方法三:正則改良版本
const?res3?=?JSON.parse('['?+?JSON.stringify(arr).replace(/\[|\]/g,?'')?+?']');
方法四:使用reduce
const?flatten?=?arr?=>?{
??return?arr.reduce((pre,?cur)?=>?{
????return?pre.concat(Array.isArray(cur)???flatten(cur)?:?cur);
??},?[])
}
const?res4?=?flatten(arr);
方法五:函數(shù)遞歸
const?res5?=?[];
const?fn?=?arr?=>?{
??for?(let?i?=?0;?i?????if?(Array.isArray(arr[i]))?{
??????fn(arr[i]);
????}?else?{
??????res5.push(arr[i]);
????}
??}
}
fn(arr);
02.數(shù)組去重
const?arr?=?[1,?1,?'1',?17,?true,?true,?false,?false,?'true',?'a',?{},?{}];
//?=>?[1,?'1',?17,?true,?false,?'true',?'a',?{},?{}]
方法一:利用Set
const?res1?=?Array.from(new?Set(arr));
方法二:兩層for循環(huán)+splice
const?unique1?=?arr?=>?{
??let?len?=?arr.length;
??for?(let?i?=?0;?i?????for?(let?j?=?i?+?1;?j???????if?(arr[i]?===?arr[j])?{
????????arr.splice(j,?1);
????????//?每刪除一個樹,j--保證j的值經(jīng)過自加后不變。同時,len--,減少循環(huán)次數(shù)提升性能
????????len--;
????????j--;
??????}
????}
??}
??return?arr;
}
方法三:利用indexOf
const?unique2?=?arr?=>?{
??const?res?=?[];
??for?(let?i?=?0;?i?????if?(res.indexOf(arr[i])?===?-1)?res.push(arr[i]);
??}
??return?res;
}
當然也可以用include、filter,思路大同小異。
方法四:利用include
const?unique3?=?arr?=>?{
??const?res?=?[];
??for?(let?i?=?0;?i?????if?(!res.includes(arr[i]))?res.push(arr[i]);
??}
??return?res;
}
方法五:利用filter
const?unique4?=?arr?=>?{
??return?arr.filter((item,?index)?=>?{
????return?arr.indexOf(item)?===?index;
??});
}
方法六:利用Map
const?unique5?=?arr?=>?{
??const?map?=?new?Map();
??const?res?=?[];
??for?(let?i?=?0;?i?????if?(!map.has(arr[i]))?{
??????map.set(arr[i],?true)
??????res.push(arr[i]);
????}
??}
??return?res;
}
03.類數(shù)組轉(zhuǎn)化為數(shù)組
類數(shù)組是具有length屬性,但不具有數(shù)組原型上的方法。常見的類數(shù)組有arguments、DOM操作方法返回的結(jié)果。
方法一:Array.from
Array.from(document.querySelectorAll('div'))
方法二:Array.prototype.slice.call()
Array.prototype.slice.call(document.querySelectorAll('div'))
方法三:擴展運算符
[...document.querySelectorAll('div')]
方法四:利用concat
Array.prototype.concat.apply([],?document.querySelectorAll('div'));
04.Array.prototype.filter()

Array.prototype.filter?=?function(callback,?thisArg)?{
??if?(this?==?undefined)?{
????throw?new?TypeError('this?is?null?or?not?undefined');
??}
??if?(typeof?callback?!==?'function')?{
????throw?new?TypeError(callback?+?'is?not?a?function');
??}
??const?res?=?[];
??//?讓O成為回調(diào)函數(shù)的對象傳遞(強制轉(zhuǎn)換對象)
??const?O?=?Object(this);
??//?>>>0?保證len為number,且為正整數(shù)
??const?len?=?O.length?>>>?0;
??for?(let?i?=?0;?i?????//?檢查i是否在O的屬性(會檢查原型鏈)
????if?(i?in?O)?{
??????//?回調(diào)函數(shù)調(diào)用傳參
??????if?(callback.call(thisArg,?O[i],?i,?O))?{
????????res.push(O[i]);
??????}
????}
??}
??return?res;
}
對于>>>0有疑問的:解釋>>>0的作用
05.Array.prototype.map()

Array.prototype.map?=?function(callback,?thisArg)?{
??if?(this?==?undefined)?{
????throw?new?TypeError('this?is?null?or?not?defined');
??}
??if?(typeof?callback?!==?'function')?{
????throw?new?TypeError(callback?+?'?is?not?a?function');
??}
??const?res?=?[];
??//?同理
??const?O?=?Object(this);
??const?len?=?O.length?>>>?0;
??for?(let?i?=?0;?i?????if?(i?in?O)?{
??????//?調(diào)用回調(diào)函數(shù)并傳入新數(shù)組
??????res[i]?=?callback.call(thisArg,?O[i],?i,?this);
????}
??}
??return?res;
}
06.Array.prototype.forEach()

forEach跟map類似,唯一不同的是forEach是沒有返回值的。
Array.prototype.forEach?=?function(callback,?thisArg)?{
??if?(this?==?null)?{
????throw?new?TypeError('this?is?null?or?not?defined');
??}
??if?(typeof?callback?!==?"function")?{
????throw?new?TypeError(callback?+?'?is?not?a?function');
??}
??const?O?=?Object(this);
??const?len?=?O.length?>>>?0;
??let?k?=?0;
??while?(k?????if?(k?in?O)?{
??????callback.call(thisArg,?O[k],?k,?O);
????}
????k++;
??}
}
07.Array.prototype.reduce()

Array.prototype.reduce?=?function(callback,?initialValue)?{
??if?(this?==?undefined)?{
????throw?new?TypeError('this?is?null?or?not?defined');
??}
??if?(typeof?callback?!==?'function')?{
????throw?new?TypeError(callbackfn?+?'?is?not?a?function');
??}
??const?O?=?Object(this);
??const?len?=?this.length?>>>?0;
??let?accumulator?=?initialValue;
??let?k?=?0;
??//?如果第二個參數(shù)為undefined的情況下
??//?則數(shù)組的第一個有效值作為累加器的初始值
??if?(accumulator?===?undefined)?{
????while?(k?in?O))?{
??????k++;
????}
????//?如果超出數(shù)組界限還沒有找到累加器的初始值,則TypeError
????if?(k?>=?len)?{
??????throw?new?TypeError('Reduce?of?empty?array?with?no?initial?value');
????}
????accumulator?=?O[k++];
??}
??while?(k?????if?(k?in?O)?{
??????accumulator?=?callback.call(undefined,?accumulator,?O[k],?k,?O);
????}
????k++;
??}
??return?accumulator;
}
08.Function.prototype.apply()
第一個參數(shù)是綁定的this,默認為window,第二個參數(shù)是數(shù)組或類數(shù)組
Function.prototype.apply?=?function(context?=?window,?args)?{
??if?(typeof?this?!==?'function')?{
????throw?new?TypeError('Type?Error');
??}
??const?fn?=?Symbol('fn');
??context[fn]?=?this;
??const?res?=?context[fn](...args);
??delete?context[fn];
??return?res;
}
09.Function.prototype.call
于call唯一不同的是,call()方法接受的是一個參數(shù)列表
Function.prototype.call?=?function(context?=?window,?...args)?{
??if?(typeof?this?!==?'function')?{
????throw?new?TypeError('Type?Error');
??}
??const?fn?=?Symbol('fn');
??context[fn]?=?this;
??const?res?=?context[fn](...args);
??delete?context[fn];
??return?res;
}
10.Function.prototype.bind
Function.prototype.bind?=?function(context,?...args)?{
??if?(typeof?this?!==?'function')?{
????throw?new?Error("Type?Error");
??}
??//?保存this的值
??var?self?=?this;
??return?function?F()?{
????//?考慮new的情況
????if(this?instanceof?F)?{
??????return?new?self(...args,?...arguments)
????}
????return?self.apply(context,?[...args,?...arguments])
??}
}
11.debounce(防抖)
觸發(fā)高頻時間后n秒內(nèi)函數(shù)只會執(zhí)行一次,如果n秒內(nèi)高頻時間再次觸發(fā),則重新計算時間。
const?debounce?=?(fn,?time)?=>?{
??let?timeout?=?null;
??return?function()?{
????clearTimeout(timeout)
????timeout?=?setTimeout(()?=>?{
??????fn.apply(this,?arguments);
????},?time);
??}
};
防抖常應用于用戶進行搜索輸入節(jié)約請求資源,window觸發(fā)resize事件時進行防抖只觸發(fā)一次。
12.throttle(節(jié)流)
高頻時間觸發(fā),但n秒內(nèi)只會執(zhí)行一次,所以節(jié)流會稀釋函數(shù)的執(zhí)行頻率。
const?throttle?=?(fn,?time)?=>?{
??let?flag?=?true;
??return?function()?{
????if?(!flag)?return;
????flag?=?false;
????setTimeout(()?=>?{
??????fn.apply(this,?arguments);
??????flag?=?true;
????},?time);
??}
}
節(jié)流常應用于鼠標不斷點擊觸發(fā)、監(jiān)聽滾動事件。
13.函數(shù)珂里化
指的是將一個接受多個參數(shù)的函數(shù) 變?yōu)?接受一個參數(shù)返回一個函數(shù)的固定形式,這樣便于再次調(diào)用,例如f(1)(2)
經(jīng)典面試題:實現(xiàn)add(1)(2)(3)(4)=10; 、 add(1)(1,2,3)(2)=9;
function?add()?{
??const?_args?=?[...arguments];
??function?fn()?{
????_args.push(...arguments);
????return?fn;
??}
??fn.toString?=?function()?{
????return?_args.reduce((sum,?cur)?=>?sum?+?cur);
??}
??return?fn;
}
14.模擬new操作
3個步驟:
以`ctor.prototype`為原型創(chuàng)建一個對象。執(zhí)行構(gòu)造函數(shù)并將this綁定到新創(chuàng)建的對象上。判斷構(gòu)造函數(shù)執(zhí)行返回的結(jié)果是否是引用數(shù)據(jù)類型,若是則返回構(gòu)造函數(shù)執(zhí)行的結(jié)果,否則返回創(chuàng)建的對象。
function?newOperator(ctor,?...args)?{
??if?(typeof?ctor?!==?'function')?{
????throw?new?TypeError('Type?Error');
??}
??const?obj?=?Object.create(ctor.prototype);
??const?res?=?ctor.apply(obj,?args);
??const?isObject?=?typeof?res?===?'object'?&&?res?!==?null;
??const?isFunction?=?typeof?res?===?'function';
??return?isObject?||?isFunction???res?:?obj;
}
15.instanceof
instanceof運算符用于檢測構(gòu)造函數(shù)的prototype屬性是否出現(xiàn)在某個實例對象的原型鏈上。
const?myInstanceof?=?(left,?right)?=>?{
??//?基本數(shù)據(jù)類型都返回false
??if?(typeof?left?!==?'object'?||?left?===?null)?return?false;
??let?proto?=?Object.getPrototypeOf(left);
??while?(true)?{
????if?(proto?===?null)?return?false;
????if?(proto?===?right.prototype)?return?true;
????proto?=?Object.getPrototypeOf(proto);
??}
}
16.原型繼承
這里只寫寄生組合繼承了,中間還有幾個演變過來的繼承但都有一些缺陷
function?Parent()?{
??this.name?=?'parent';
}
function?Child()?{
??Parent.call(this);
??this.type?=?'children';
}
Child.prototype?=?Object.create(Parent.prototype);
Child.prototype.constructor?=?Child;
17.Object.is
Object.is解決的主要是這兩個問題:
+0?===?-0??//?true
NaN?===?NaN?//?false
const?is=?(x,?y)?=>?{
??if?(x?===?y)?{
????//?+0和-0應該不相等
????return?x?!==?0?||?y?!==?0?||?1/x?===?1/y;
??}?else?{
????return?x?!==?x?&&?y?!==?y;
??}
}
18.Object.assign
Object.assign()方法用于將所有可枚舉屬性的值從一個或多個源對象復制到目標對象。它將返回目標對象(請注意這個操作是淺拷貝)
Object.defineProperty(Object,?'assign',?{
??value:?function(target,?...args)?{
????if?(target?==?null)?{
??????return?new?TypeError('Cannot?convert?undefined?or?null?to?object');
????}
????
????//?目標對象需要統(tǒng)一是引用數(shù)據(jù)類型,若不是會自動轉(zhuǎn)換
????const?to?=?Object(target);
????for?(let?i?=?0;?i???????//?每一個源對象
??????const?nextSource?=?args[i];
??????if?(nextSource?!==?null)?{
????????//?使用for...in和hasOwnProperty雙重判斷,確保只拿到本身的屬性、方法(不包含繼承的)
????????for?(const?nextKey?in?nextSource)?{
??????????if?(Object.prototype.hasOwnProperty.call(nextSource,?nextKey))?{
????????????to[nextKey]?=?nextSource[nextKey];
??????????}
????????}
??????}
????}
????return?to;
??},
??//?不可枚舉
??enumerable:?false,
??writable:?true,
??configurable:?true,
})
19.深拷貝
遞歸的完整版本(考慮到了Symbol屬性):
const?cloneDeep1?=?(target,?hash?=?new?WeakMap())?=>?{
??//?對于傳入?yún)?shù)處理
??if?(typeof?target?!==?'object'?||?target?===?null)?{
????return?target;
??}
??//?哈希表中存在直接返回
??if?(hash.has(target))?return?hash.get(target);
??const?cloneTarget?=?Array.isArray(target)???[]?:?{};
??hash.set(target,?cloneTarget);
??//?針對Symbol屬性
??const?symKeys?=?Object.getOwnPropertySymbols(target);
??if?(symKeys.length)?{
????symKeys.forEach(symKey?=>?{
??????if?(typeof?target[symKey]?===?'object'?&&?target[symKey]?!==?null)?{
????????cloneTarget[symKey]?=?cloneDeep1(target[symKey]);
??????}?else?{
????????cloneTarget[symKey]?=?target[symKey];
??????}
????})
??}
??for?(const?i?in?target)?{
????if?(Object.prototype.hasOwnProperty.call(target,?i))?{
??????cloneTarget[i]?=
????????typeof?target[i]?===?'object'?&&?target[i]?!==?null
??????????cloneDeep1(target[i],?hash)
????????:?target[i];
????}
??}
??return?cloneTarget;
}
20.Promise
實現(xiàn)思路:Promise源碼實現(xiàn)
const?PENDING?=?'PENDING';??????//?進行中
const?FULFILLED?=?'FULFILLED';??//?已成功
const?REJECTED?=?'REJECTED';????//?已失敗
class?Promise?{
??constructor(exector)?{
????//?初始化狀態(tài)
????this.status?=?PENDING;
????//?將成功、失敗結(jié)果放在this上,便于then、catch訪問
????this.value?=?undefined;
????this.reason?=?undefined;
????//?成功態(tài)回調(diào)函數(shù)隊列
????this.onFulfilledCallbacks?=?[];
????//?失敗態(tài)回調(diào)函數(shù)隊列
????this.onRejectedCallbacks?=?[];
????const?resolve?=?value?=>?{
??????//?只有進行中狀態(tài)才能更改狀態(tài)
??????if?(this.status?===?PENDING)?{
????????this.status?=?FULFILLED;
????????this.value?=?value;
????????//?成功態(tài)函數(shù)依次執(zhí)行
????????this.onFulfilledCallbacks.forEach(fn?=>?fn(this.value));
??????}
????}
????const?reject?=?reason?=>?{
??????//?只有進行中狀態(tài)才能更改狀態(tài)
??????if?(this.status?===?PENDING)?{
????????this.status?=?REJECTED;
????????this.reason?=?reason;
????????//?失敗態(tài)函數(shù)依次執(zhí)行
????????this.onRejectedCallbacks.forEach(fn?=>?fn(this.reason))
??????}
????}
????try?{
??????//?立即執(zhí)行executor
??????//?把內(nèi)部的resolve和reject傳入executor,用戶可調(diào)用resolve和reject
??????exector(resolve,?reject);
????}?catch(e)?{
??????//?executor執(zhí)行出錯,將錯誤內(nèi)容reject拋出去
??????reject(e);
????}
??}
??then(onFulfilled,?onRejected)?{
????onFulfilled?=?typeof?onFulfilled?===?'function'???onFulfilled?:?value?=>?value;
????onRejected?=?typeof?onRejected?===?'function'??onRejected:
??????reason?=>?{?throw?new?Error(reason?instanceof?Error???reason.message:reason)?}
????//?保存this
????const?self?=?this;
????return?new?Promise((resolve,?reject)?=>?{
??????if?(self.status?===?PENDING)?{
????????self.onFulfilledCallbacks.push(()?=>?{
??????????//?try捕獲錯誤
??????????try?{
????????????//?模擬微任務
????????????setTimeout(()?=>?{
??????????????const?result?=?onFulfilled(self.value);
??????????????//?分兩種情況:
??????????????//?1.?回調(diào)函數(shù)返回值是Promise,執(zhí)行then操作
??????????????//?2.?如果不是Promise,調(diào)用新Promise的resolve函數(shù)
??????????????result?instanceof?Promise???result.then(resolve,?reject)?:?resolve(result);
????????????})
??????????}?catch(e)?{
????????????reject(e);
??????????}
????????});
????????self.onRejectedCallbacks.push(()?=>?{
??????????//?以下同理
??????????try?{
????????????setTimeout(()?=>?{
??????????????const?result?=?onRejected(self.reason);
??????????????//?不同點:此時是reject
??????????????result?instanceof?Promise???result.then(resolve,?reject)?:?reject(result);
????????????})
??????????}?catch(e)?{
????????????reject(e);
??????????}
????????})
??????}?else?if?(self.status?===?FULFILLED)?{
????????try?{
??????????setTimeout(()?=>?{
????????????const?result?=?onFulfilled(self.value);
????????????result?instanceof?Promise???result.then(resolve,?reject)?:?resolve(result);
??????????});
????????}?catch(e)?{
??????????reject(e);
????????}
??????}?else?if?(self.status?===?REJECTED){
????????try?{
??????????setTimeout(()?=>?{
????????????const?result?=?onRejected(self.reason);
????????????result?instanceof?Promise???result.then(resolve,?reject)?:?reject(result);
??????????})
????????}?catch(e)?{
??????????reject(e);
????????}
??????}
????});
??}
??catch(onRejected)?{
????return?this.then(null,?onRejected);
??}
??static?resolve(value)?{
????if?(value?instanceof?Promise)?{
??????//?如果是Promise實例,直接返回
??????return?value;
????}?else?{
??????//?如果不是Promise實例,返回一個新的Promise對象,狀態(tài)為FULFILLED
??????return?new?Promise((resolve,?reject)?=>?resolve(value));
????}
??}
??static?reject(reason)?{
????return?new?Promise((resolve,?reject)?=>?{
??????reject(reason);
????})
??}
}
21.Promise.all
Promise.all是支持鏈式調(diào)用的,本質(zhì)上就是返回了一個Promise實例,通過resolve和reject來改變實例狀態(tài)。
Promise.myAll?=?function(promiseArr)?{
??return?new?Promise((resolve,?reject)?=>?{
????const?ans?=?[];
????let?index?=?0;
????for?(let?i?=?0;?i???????promiseArr[i]
??????.then(res?=>?{
????????ans[i]?=?res;
????????index++;
????????if?(index?===?promiseArr.length)?{
??????????resolve(ans);
????????}
??????})
??????.catch(err?=>?reject(err));
????}
??})
}
22.Promise.race
Promise.race?=?function(promiseArr)?{
??return?new?Promise((resolve,?reject)?=>?{
????promiseArr.forEach(p?=>?{
??????//?如果不是Promise實例需要轉(zhuǎn)化為Promise實例
??????Promise.resolve(p).then(
????????val?=>?resolve(val),
????????err?=>?reject(err),
??????)
????})
??})
}
23.Promise并行限制
就是實現(xiàn)有并行限制的Promise調(diào)度器問題。
詳細實現(xiàn)思路:某條高頻面試原題:實現(xiàn)有并行限制的Promise調(diào)度器
class?Scheduler?{
??constructor()?{
????this.queue?=?[];
????this.maxCount?=?2;
????this.runCounts?=?0;
??}
??add(promiseCreator)?{
????this.queue.push(promiseCreator);
??}
??taskStart()?{
????for?(let?i?=?0;?i?this.maxCount;?i++)?{
??????this.request();
????}
??}
??request()?{
????if?(!this.queue?||?!this.queue.length?||?this.runCounts?>=?this.maxCount)?{
??????return;
????}
????this.runCounts++;
????this.queue.shift()().then(()?=>?{
??????this.runCounts--;
??????this.request();
????});
??}
}
???
const?timeout?=?time?=>?new?Promise(resolve?=>?{
??setTimeout(resolve,?time);
})
??
const?scheduler?=?new?Scheduler();
??
const?addTask?=?(time,order)?=>?{
??scheduler.add(()?=>?timeout(time).then(()=>console.log(order)))
}
??
??
addTask(1000,?'1');
addTask(500,?'2');
addTask(300,?'3');
addTask(400,?'4');
scheduler.taskStart()
//?2
//?3
//?1
//?4
24.JSONP
script標簽不遵循同源協(xié)議,可以用來進行跨域請求,優(yōu)點就是兼容性好但僅限于GET請求
const?jsonp?=?({?url,?params,?callbackName?})?=>?{
??const?generateUrl?=?()?=>?{
????let?dataSrc?=?'';
????for?(let?key?in?params)?{
??????if?(Object.prototype.hasOwnProperty.call(params,?key))?{
????????dataSrc?+=?`${key}=${params[key]}&`;
??????}
????}
????dataSrc?+=?`callback=${callbackName}`;
????return?`${url}?${dataSrc}`;
??}
??return?new?Promise((resolve,?reject)?=>?{
????const?scriptEle?=?document.createElement('script');
????scriptEle.src?=?generateUrl();
????document.body.appendChild(scriptEle);
????window[callbackName]?=?data?=>?{
??????resolve(data);
??????document.removeChild(scriptEle);
????}
??})
}
25.AJAX
const?getJSON?=?function(url)?{
??return?new?Promise((resolve,?reject)?=>?{
????const?xhr?=?XMLHttpRequest???new?XMLHttpRequest()?:?new?ActiveXObject('Mscrosoft.XMLHttp');
????xhr.open('GET',?url,?false);
????xhr.setRequestHeader('Accept',?'application/json');
????xhr.onreadystatechange?=?function()?{
??????if?(xhr.readyState?!==?4)?return;
??????if?(xhr.status?===?200?||?xhr.status?===?304)?{
????????resolve(xhr.responseText);
??????}?else?{
????????reject(new?Error(xhr.responseText));
??????}
????}
????xhr.send();
??})
}
26.event模塊
實現(xiàn)node中回調(diào)函數(shù)的機制,node中回調(diào)函數(shù)其實是內(nèi)部使用了觀察者模式。
觀察者模式:定義了對象間一種一對多的依賴關(guān)系,當目標對象Subject發(fā)生改變時,所有依賴它的對象Observer都會得到通知。
function?EventEmitter()?{
??this.events?=?new?Map();
}
//?需要實現(xiàn)的一些方法:
//?addListener、removeListener、once、removeAllListeners、emit
//?模擬實現(xiàn)addlistener方法
const?wrapCallback?=?(fn,?once?=?false)?=>?({?callback:?fn,?once?});
EventEmitter.prototype.addListener?=?function(type,?fn,?once?=?false)?{
??const?hanlder?=?this.events.get(type);
??if?(!hanlder)?{
????//?沒有type綁定事件
????this.events.set(type,?wrapCallback(fn,?once));
??}?else?if?(hanlder?&&?typeof?hanlder.callback?===?'function')?{
????//?目前type事件只有一個回調(diào)
????this.events.set(type,?[hanlder,?wrapCallback(fn,?once)]);
??}?else?{
????//?目前type事件數(shù)>=2
????hanlder.push(wrapCallback(fn,?once));
??}
}
//?模擬實現(xiàn)removeListener
EventEmitter.prototype.removeListener?=?function(type,?listener)?{
??const?hanlder?=?this.events.get(type);
??if?(!hanlder)?return;
??if?(!Array.isArray(this.events))?{
????if?(hanlder.callback?===?listener.callback)?this.events.delete(type);
????else?return;
??}
??for?(let?i?=?0;?i?????const?item?=?hanlder[i];
????if?(item.callback?===?listener.callback)?{
??????hanlder.splice(i,?1);
??????i--;
??????if?(hanlder.length?===?1)?{
????????this.events.set(type,?hanlder[0]);
??????}
????}
??}
}
//?模擬實現(xiàn)once方法
EventEmitter.prototype.once?=?function(type,?listener)?{
??this.addListener(type,?listener,?true);
}
//?模擬實現(xiàn)emit方法
EventEmitter.prototype.emit?=?function(type,?...args)?{
??const?hanlder?=?this.events.get(type);
??if?(!hanlder)?return;
??if?(Array.isArray(hanlder))?{
????hanlder.forEach(item?=>?{
??????item.callback.apply(this,?args);
??????if?(item.once)?{
????????this.removeListener(type,?item);
??????}
????})
??}?else?{
????hanlder.callback.apply(this,?args);
????if?(hanlder.once)?{
??????this.events.delete(type);
????}
??}
??return?true;
}
EventEmitter.prototype.removeAllListeners?=?function(type)?{
??const?hanlder?=?this.events.get(type);
??if?(!hanlder)?return;
??this.events.delete(type);
}
27.圖片懶加載
可以給img標簽統(tǒng)一自定義屬性src='default.png',當檢測到圖片出現(xiàn)在窗口之后再補充src屬性,此時才會進行圖片資源加載。
function?lazyload()?{
??const?imgs?=?document.getElementsByTagName('img');
??const?len?=?imgs.length;
??//?視口的高度
??const?viewHeight?=?document.documentElement.clientHeight;
??//?滾動條高度
??const?scrollHeight?=?document.documentElement.scrollTop?||?document.body.scrollTop;
??for?(let?i?=?0;?i?????const?offsetHeight?=?imgs[i].offsetTop;
????if?(offsetHeight???????const?src?=?imgs[i].dataset.src;
??????imgs[i].src?=?src;
????}
??}
}
//?可以使用節(jié)流優(yōu)化一下
window.addEventListener('scroll',?lazyload);
28.滾動加載
原理就是監(jiān)聽頁面滾動事件,分析clientHeight、scrollTop、scrollHeight三者的屬性關(guān)系。
window.addEventListener('scroll',?function()?{
??const?clientHeight?=?document.documentElement.clientHeight;
??const?scrollTop?=?document.documentElement.scrollTop;
??const?scrollHeight?=?document.documentElement.scrollHeight;
??if?(clientHeight?+?scrollTop?>=?scrollHeight)?{
????//?檢測到滾動至頁面底部,進行后續(xù)操作
????//?...
??}
},?false);
一個Demo:頁面滾動加載的Demo
29.渲染幾萬條數(shù)據(jù)不卡住頁面
渲染大數(shù)據(jù)時,合理使用createDocumentFragment和requestAnimationFrame,將操作切分為一小段一小段執(zhí)行。
setTimeout(()?=>?{
??//?插入十萬條數(shù)據(jù)
??const?total?=?100000;
??//?一次插入的數(shù)據(jù)
??const?once?=?20;
??//?插入數(shù)據(jù)需要的次數(shù)
??const?loopCount?=?Math.ceil(total?/?once);
??let?countOfRender?=?0;
??const?ul?=?document.querySelector('ul');
??//?添加數(shù)據(jù)的方法
??function?add()?{
????const?fragment?=?document.createDocumentFragment();
????for(let?i?=?0;?i???????const?li?=?document.createElement('li');
??????li.innerText?=?Math.floor(Math.random()?*?total);
??????fragment.appendChild(li);
????}
????ul.appendChild(fragment);
????countOfRender?+=?1;
????loop();
??}
??function?loop()?{
????if(countOfRender???????window.requestAnimationFrame(add);
????}
??}
??loop();
},?0)
30.打印出當前網(wǎng)頁使用了多少種HTML元素
一行代碼可以解決:
const?fn?=?()?=>?{
??return?[...new?Set([...document.querySelectorAll('*')].map(el?=>?el.tagName))].length;
}
值得注意的是:DOM操作返回的是類數(shù)組,需要轉(zhuǎn)換為數(shù)組之后才可以調(diào)用數(shù)組的方法。
31.將VirtualDom轉(zhuǎn)化為真實DOM結(jié)構(gòu)
這是當前SPA應用的核心概念之一
//?vnode結(jié)構(gòu):
//?{
//???tag,
//???attrs,
//???children,
//?}
//Virtual?DOM?=>?DOM
function?render(vnode,?container)?{
??container.appendChild(_render(vnode));
}
function?_render(vnode)?{
??//?如果是數(shù)字類型轉(zhuǎn)化為字符串
??if?(typeof?vnode?===?'number')?{
????vnode?=?String(vnode);
??}
??//?字符串類型直接就是文本節(jié)點
??if?(typeof?vnode?===?'string')?{
????return?document.createTextNode(vnode);
??}
??//?普通DOM
??const?dom?=?document.createElement(vnode.tag);
??if?(vnode.attrs)?{
????//?遍歷屬性
????Object.keys(vnode.attrs).forEach(key?=>?{
??????const?value?=?vnode.attrs[key];
??????dom.setAttribute(key,?value);
????})
??}
??//?子數(shù)組進行遞歸操作
??vnode.children.forEach(child?=>?render(child,?dom));
??return?dom;
}
32.字符串解析問題
var?a?=?{
????b:?123,
????c:?'456',
????e:?'789',
}
var?str=`a{a.b}aa{a.c}aa?{a.d}aaaa`;
//?=>?'a123aa456aa?{a.d}aaaa'
實現(xiàn)函數(shù)使得將str字符串中的{}內(nèi)的變量替換,如果屬性不存在保持原樣(比如{a.d})
類似于模版字符串,但有一點出入,實際上原理大差不差
const?fn1?=?(str,?obj)?=>?{
????let?res?=?'';
????//?標志位,標志前面是否有{
????let?flag?=?false;
????let?start;
????for?(let?i?=?0;?i?????????if?(str[i]?===?'{')?{
????????????flag?=?true;
????????????start?=?i?+?1;
????????????continue;
????????}
????????if?(!flag)?res?+=?str[i];
????????else?{
????????????if?(str[i]?===?'}')?{
????????????????flag?=?false;
????????????????res?+=?match(str.slice(start,?i),?obj);
????????????}
????????}
????}
????return?res;
}
//?對象匹配操作
const?match?=?(str,?obj)?=>?{
????const?keys?=?str.split('.').slice(1);
????let?index?=?0;
????let?o?=?obj;
????while?(index?????????const?key?=?keys[index];
????????if?(!o[key])?{
????????????return?`{${str}}`;
????????}?else?{
????????????o?=?o[key];
????????}
????????index++;
????}
????return?o;
}

