帶你“手撕代碼”,瞭解基本原理實現

前言

在前端面試有一個非常重要的環節,也是面試者最擔心的一個環節。對“手撕代碼”的考察需要面試者平時總結和積累(臨時抱佛腳是不好使的),在這裏筆者就自己如何攻破“手撕代碼”環節總結了一些經驗,希望能幫助你挑戰高薪,迎娶白富美😄😄😄。在這裏插入圖片描述

  1. 使用IDE時儘量避免直接使用提示API,親自輸入(孰能生巧,當然感覺沒問題的API就不用浪費時間了)
  2. 遇到不熟悉的API,一定要查文檔研究清楚(參數個數和具體意義以及返回值)
  3. 如在模擬某個原生API時,先寫出原生API並分析出形參和返回值
  4. 感覺功能完成時,需要在考慮一下邊界條件(參數非比填情況、undefined、null)
  5. 平常有空時多刷刷一二線大廠的面試題(擴充自己的知識廣度)
  6. 多關照一些前端動態(比如說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;
}

後記

代碼如若有誤,歡迎指正
在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章