關於JS中一些重要的api實現, 鞏固你的原生JS功底

在面試中,經常會遇到一些手寫XXX之類的面試題,所以好好總結一下,對於鞏固咱們的原生js的基礎是很是必要的。面試

儘管在網上已經有了很是多的總結文章,但在我看來有一個廣泛的問題,那就是把原理性的東西過於複雜化了。若是站在面試官的角度,他的目的是在最短的時間內考察出面試者對於JS語言的理解程度,可是在看了網站的諸多總結文章後我發現其中的代碼有很大一部分是作意義不大的操做,好比實現一個簡單的防抖,只要是核心的流程展現便可,至於其餘的一些等模式則沒有必要再去深挖,一大堆的if-else讓人看上去也眼花繚亂,甚至誤導別人直接去背代碼,另外,核心的邏輯都能展現出來,再去橫向的實現其餘的相似狀況恐怕也不是什麼問題了。segmentfault

在如下的整理中,建議你們先照的核心要點本身寫一遍,而後對照下面的代碼,複習效果更好。本文的目的就在於以最簡潔的代碼幫你從第一性原理的角度理解api的內部運做流程,凡是對於咱們理解api沒有幫助的的邊界狀況都不作處理api

1、用ES5實現數組的map方法

核心要點:數組

1.回調函數的參數有哪些,返回值如何處理。markdown

2.不修改原來的數組。閉包

Array.prototype.MyMap = function(fn, context){
  var arr = Array.prototype.slice.call(this);//因爲是ES5因此就不用...展開符了
  var mappedArr = [];
  for (var i = 0; i < arr.length; i++ ){
    mappedArr.push(fn.call(context, arr[i], i, this));
  }
  return mappedArr;
}
複製代碼

2、用ES5實現數組的reduce方法

核心要點:app

一、初始值不傳怎麼處理函數

二、回調函數的參數有哪些,返回值如何處理。oop

Array.prototype.myReduce = function(fn, initialValue) {
  var arr = Array.prototype.slice.call(this);
  var res, startIndex;
  res = initialValue ? initialValue : arr[0];
  startIndex = initialValue ? 0 : 1;
  for(var i = startIndex; i < arr.length; i++) {
    res = fn.call(null, res, arr[i], i, this);
  }
  return res;
}
複製代碼

3、實現call/apply

思路: 利用this的上下文特性。post

//實現apply只要把下一行中的...args換成args便可 
Function.prototype.myCall = function(context = window, ...args) {
  let func = this;
  let fn = Symbol("fn");
  context[fn] = func;

  let res = context[fn](...args);//重點代碼,利用this指向,至關於context.caller(...args)

  delete context[fn];
  return res;
}
複製代碼

4、實現Object.create方法(經常使用)

function create(proto) {
    function F() {};
    F.prototype = proto;
    F.prototype.constructor = F;
    
    return new F();
}
複製代碼

5、實現bind方法

核心要點:

1.對於普通函數,綁定this指向

2.對於構造函數,要保證原函數的原型對象上的屬性不能丟失

Function.prototype.bind = function(context, ...args) {
    let self = this;//謹記this表示調用bind的函數
    let fBound = function() {
        //this instanceof fBound爲true表示構造函數的狀況。如new func.bind(obj)
        return self.apply(this instanceof fBound ? this : context || window, args.concat(Array.prototype.slice.call(arguments)));
    }
    fBound.prototype = Object.create(this.prototype);//保證原函數的原型對象上的屬性不丟失
    return fBound;
}
複製代碼

你們平時說的手寫bind,其實就這麼簡單:)

6、實現new關鍵字

核心要點:

  1. 建立一個全新的對象,這個對象的__proto__要指向構造函數的原型對象
  2. 執行構造函數
  3. 返回值爲object類型則做爲new方法的返回值返回,不然返回上述全新對象
function myNew(fn, ...args) {
    let instance = Object.create(fn.prototype);
    let res = fn.apply(instance, args);
    return typeof res === 'object' ? res: instance;
}
複製代碼

7、實現instanceof的做用

核心要點:原型鏈的向上查找。

function myInstanceof(left, right) {
    let proto = Object.getPrototypeOf(left);
    while(true) {
        if(proto == null) return false;
        if(proto == right.prototype) return true;
        proto = Object.getPrototypeof(proto);
    }
}
複製代碼

8、實現單例模式

核心要點: 用閉包和Proxy屬性攔截

function proxy(func) {
    let instance;
    let handler = {
        construct(target, args) {
            if(!instance) {
                instance = Reflect.construct(func, args);
            }
            return instance;
        }
    }
    return new Proxy(func, handler);
}
複製代碼

9、實現數組的flat

方式其實不少,以前我作過系統整理,有六種方法,請參考:

JS數組扁平化(flat)方法總結

10、實現防抖功能

核心要點:

若是在定時器的時間範圍內再次觸發,則從新計時。

const debounce = (fn, delay) => {
  let timer = null;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
};
複製代碼

11、實現節流功能

核心要點:

若是在定時器的時間範圍內再次觸發,則不予理睬,等當前定時器完成,才能啓動下一個定時器。

const throttle = (fn, delay = 500) => {
  let flag = true;
  return (...args) => {
    if (!flag) return;
    flag = false;
    setTimeout(() => {
      fn.apply(this, args);
      flag = true;
    }, delay);
  };
};
複製代碼

12、用發佈訂閱模式實現EventEmit

參考個人另外一篇文章:

基於"發佈-訂閱"的原生JS插件封裝中的手寫發佈訂閱部分。

十3、實現深拷貝

如下爲簡易版深拷貝,沒有考慮循環引用的狀況和Buffer、Promise、Set、Map的處理,若是一一實現,過於複雜,面試短期寫出來不太現實,若是有興趣能夠去這裏深刻實現:

深拷貝終極探索

const clone = parent => {
  // 判斷類型
  const isType =  (target, type) => `[object ${type}]` === Object.prototype.toString.call(target)

  // 處理正則
  const getRegExp = re => {
    let flags = "";
    if (re.global) flags += "g";
    if (re.ignoreCase) flags += "i";
    if (re.multiline) flags += "m";
    return flags;
  };

  const _clone = parent => {
    if (parent === null) return null;
    if (typeof parent !== "object") return parent;

    let child, proto;

    if (isType(parent, "Array")) {
      // 對數組作特殊處理
      child = [];
    } else if (isType(parent, "RegExp")) {
      // 對正則對象作特殊處理
      child = new RegExp(parent.source, getRegExp(parent));
      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    } else if (isType(parent, "Date")) {
      // 對Date對象作特殊處理
      child = new Date(parent.getTime());
    } else {
      // 處理對象原型
      proto = Object.getPrototypeOf(parent);
      // 利用Object.create切斷原型鏈
      child = Object.create(proto);
    }
    for (let i in parent) {
      // 遞歸
      child[i] = _clone(parent[i]);
    }
    return child;
  };
  return _clone(parent);
};
複製代碼

十4、實現Promise

重點難點,比較複雜,請參考個人另外一篇步步拆解文章:

我如何實現Promise

十5、使用ES5實現類的繼承效果

也是重點知識,我以前作過詳細拆解,有五個版本,若是每一版本都能說清楚,可以很好的體現本身對於原型鏈的理解,文章地址:

ES5實現繼承的那些事

相關文章
相關標籤/搜索