前言
在前端面試有一個非常重要的環節,也是面試者最擔心的一個環節。對“手撕程式碼”的考察需要面試者平時總結和積累(臨時抱佛腳是不好使的),在這裡筆者就自己如何攻破“手撕程式碼”環節總結了一些經驗,希望能幫助你挑戰高薪,迎娶白富美???。
- 使用IDE時儘量避免直接使用提示API,親自輸入(孰能生巧,當然感覺沒問題的API就不用浪費時間了)
- 遇到不熟悉的API,一定要查文件研究清楚(引數個數和具體意義以及返回值)
- 如在模擬某個原生API時,先寫出原生API並分析出形參和返回值
- 感覺功能完成時,需要在考慮一下邊界條件(引數非比填情況、undefined、null)
- 平常有空時多刷刷一二線大廠的面試題(擴充自己的知識廣度)
- 多關照一些前端動態(比如說curry、compose你沒聽過,這就有點尷尬)
常見的“手撕程式碼”,都是高頻題哦
curry(柯里化)
function curry(fn: any) {
return function judgeCurry(...args: any) {
return fn.length > args.length ?
(...args1: any) => judgeCurry(...args,...args1):
fn(...args);
}
}
複製程式碼
compose(函式組合)
function compose(...args: any[]) {
return (subArgs: any) => {
// for(let i = args.length - 1; i >= 0; i--) {
// res = args[i](res);
// }
return args.reverse().reduce((acc, func,index) => {
return func(acc);
}, subArgs);
}
}
複製程式碼
pipe(函式管道)
export function pipe(...args: any[]) {
return (subArgs: any) => {
// for(let i = args.length - 1; i >= 0; i--) {
// res = args[i](res);
// }
return args.reduce((acc, func,index) => {
return func(acc);
}, subArgs);
}
}
複製程式碼
throttle(函式節流)
function throttle(fn: any, wait: number){
let last: any;
return function() {
let now: any = Date.now();
// 初次執行
if (!last) {
fn.apply(this, arguments);
last = now;
return;
}
// 以後觸發,需要判斷是否到延遲
if(now - last >= wait) {
fn.apply(this, arguments);
last = now;
}
}
}
複製程式碼
debounce(函式防抖)
function debounce(func: any, delay: number) {
// 初次觸發定時器為null,後面產生一份定時器並記下定時器id
let timer: any = null;
// 閉包使定時器id逃逸
return function() {
let args = arguments;
// 如果已有定時器id,則需要清除,重新開始延遲執行
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout( () => {
func.apply(this, args);
// 銷燬定時器id,以便下次節流函式觸發
timer = null;
}, delay);
}
}
複製程式碼
formatMoney(千分位)
function fmoney(num: number){
/* 正則實現 */
// 參考:https://www.cnblogs.com/lvmylife/p/8287247.html
let [integer, decimal] = String(num).split('.');
let regExp = /\d{1,3}(?=(\d{3})+$)/g;
integer = integer.replace(regExp, '$&,');
return `${integer}${decimal === undefined ? '': '.'+decimal}`;
// 正則解釋
// 正規表示式 \d{1,3}(?=(\d{3})+$) 表示前面有1~3個數字,後面的至少由一組3個數字結尾
// 先行肯定斷言(?=)會作為匹配校驗,但不會出現在匹配結果字串裡面
// ?=表示正向引用,可以作為匹配的條件,但匹配到的內容不獲取,並且作為下一次查詢的開始
// $& 表示與正規表示式相匹配的內容,具體的可檢視 w3school的replace()方法
/* Number.prototype.toLocaleString()實現 */
// Number.prototype.toLocaleString()
// return num.toLocaleString('en');
/* Intl.NumberFormat().format(number)實現 */
// Intl.NumberFormat().format(number)
// return Intl.NumberFormat('en').format(num);
// reduce 方案
// let arr = String(num).split('.');
// let char = arr[0].split('').reverse();
// let IntStr = char.reduce((acc, value, index) => {
// return `${index % 3 === 0 ? String(value)+',' : String(value)}${acc}`;
// }, '').slice(0, -1);
// return `${IntStr}${arr[1]? '.'+arr[1] : '' }`;
}
複製程式碼
deepClone(深拷貝)
說明:通過new WeakMap()來避免迴圈引用(拷貝引用型別時並儲存其地址,後面遇到引用型別先檢查是否已經儲存了)
通過Reflect.ownKeys(obj)遍歷出obj自身的所有可列舉和不可列舉的屬性以及symbol屬性
拷貝對應屬性的屬性描述符
function checkType(obj: any): string {
const type = Object.prototype.toString.call(obj);
return type.slice(8, -1);
}
// 深拷貝(hash = new WeakMap()考慮迴圈引用的問題)
export function deepClone(obj: any, hash = new WeakMap()) : any{
if(checkType(obj) === 'RegExp') {
// regExp.source 正則物件的源模式文字;
// regExp.flags 正規表示式物件的標誌字串;
// regExp.lastIndex 下次匹配開始的字串索引位置
let temp = new RegExp(obj.source, obj.flags);
temp.lastIndex = obj.lastIndex;
return temp;
}
if(checkType(obj) === 'Date') {
return new Date(obj);
}
// 非複雜型別(null、undefined、string、number、symbol、boolean、function)
if(obj === null || typeof obj !== 'object') {
return obj;
}
// 還可以擴充套件其他型別。。。
// 與後面hash.set()防止迴圈引用
if(hash.has(obj)) {
return hash.get(obj);
}
let newObj = new obj.constructor();
hash.set(obj, newObj);
// Object.keys(obj)型別於 for in 和 obj.hasOwnProperty
// 是否應該拷貝自身屬性(可列舉的和不可列舉的以及symbol)
Reflect.ownKeys(obj).forEach(function(key) {
if(typeof obj[key] === 'object' && obj[key] !== null) {
newObj[key] = deepClone(obj[key], hash);
}else{
// 直接賦值
// newObj[key] = obj[key];
// 是否應該保留屬性描述符
Object.defineProperty(newObj, key, Object.getOwnPropertyDescriptor(obj, key));
}
});
return newObj;
}
複製程式碼
模擬instanceof
function instance_of(L: Object, R: any){
let protoChain = Object.getPrototypeOf(L);
const Lprototype = R.prototype;
// 最壞情況遞迴查到Object.prototype === null
while(protoChain) {
// 兩個物件指向同一個記憶體地址,則為同一個物件
if(protoChain === Lprototype) {
return true;
}
protoChain = Object.getPrototypeOf(protoChain);
}
// 找到終點還沒找到,那就沒有了唄
return false;
}
複製程式碼
實現call方法
Function.prototype.myCall = function myCall() {
let [thisArg, ...args] = Array.from(arguments);
if (!thisArg) {
//context 為 null 或者是 undefined
thisArg = typeof window === 'undefined' ? global : window;
}
// this 的指向的是當前函式 func (func.call)
// 為thisArg物件新增func方法,func方法又指向myCall,所以在func中this指向thisArg
thisArg.func = this;
// 執行函式
let result = thisArg.func(...args);
// thisArg 上並沒有 func 屬性,因此需要移除
delete thisArg.func;
return result;
}
複製程式碼
實現apply方法
Function.prototype.myApply = function myApply() {
// 第一個引數為this物件,第二個引數為陣列(與myCall唯一的區別就在第二個引數是陣列)
let [thisArg, args] = Array.from(arguments);
if (!thisArg) {
//context 為 null 或者是 undefined
thisArg = typeof window === 'undefined' ? global : window;
}
// this 的指向的是當前函式 func (func.call)
thisArg.func = this;
// 執行函式
let result = thisArg.func(...args);
// thisArg 上並沒有 func 屬性,因此需要移除
delete thisArg.func;
return result;
}
複製程式碼
實現bind方法
Function.prototype.myBind = function myBind() {
let [thisArg, ...args] = [...arguments];
if (!thisArg) {
//context 為 null 或者是 undefined
thisArg = typeof window === 'undefined' ? global : window;
}
let that = this;
return function() {
// 防止第二次呼叫 func 是,該func已經被delete了,需要重新賦值
if(!thisArg.func) {
thisArg.func = that;
}
let result = thisArg.func(...args);
// thisArg原本沒有func方法
delete thisArg.func;
return result;
}
}
複製程式碼
模擬Promise.all(多個Promise並行執行)
目前還存在引數適配的問題
var p1 = function(){
return new Promise((resolve, reject) => {setTimeout(function(){resolve('12')}, 1000)})
};
var p2 = function(){
return new Promise((resolve, reject) => {setTimeout(function(){resolve(2)}, 2000)})
};
var p3 = function(){
return new Promise((resolve, reject) => {setTimeout(function(){resolve(3)}, 1000)})
};
function promiseAll(tasks) {
let ary = new Array(tasks.length).fill(1).map(item => {return {val: undefined, success: false}});
return new Promise((resolve, reject) => {
for(let i = 0; i < tasks.length; i++) {
tasks[i]().then(res => {
ary[i].val = res;
ary[i].success = true;
if(ary.every(item => item.success === true)){
resolve(ary.map(item => item.val))
}
}).catch(err => {
reject(err);
});
}
});
}
// test
promiseAll([p1, p2, p3]).then(res => console.log(res)).catch(err => {
console.log(err);
});
複製程式碼
多個Promise序列執行(兩種方式)
function parallelPromises1(tasks){
var result = [];
return tasks.reduce((accumulator,item,index)=>{
return accumulator.then(res=>{
item = typeof item === 'function' ? item() : item;
return item.then(res=>{
// debugger
result[index] = res
return index == tasks.length - 1 ? result : item
})
})
},Promise.resolve())
}
async function parallelPromises2(tasks) {
let ary = [];
for (let task of tasks) {
let temp = await task();
ary.push(temp);
}
return ary;
}
複製程式碼
寫在後面
上面程式碼完全是筆者手敲,難免有錯誤,還望斧正。這種題目還有很多(實現簡易版的EventEmitter、簡易版模版引擎等),筆者會持續更新。如果對你有幫助,俺希望送上你的github小星星,在此感謝。
本文同步釋出於個人部落格、掘金、知乎專欄