這些手寫程式碼會了嗎?少年

童歐巴發表於2020-08-03

圖怪獸_aba93a3814353520e3427ff5d8f73f4c_26222.png
觀感度:?????

口味:蟹黃豆腐

烹飪時間:5min

本文已收錄在前端食堂同名倉庫Github github.com/Geekhyt,歡迎光臨食堂,如果覺得酒菜還算可口,賞個 Star 對食堂老闆來說是莫大的鼓勵。

從略帶銀絲的頭髮和乾淨利落的步伐我察覺到,面前坐著的這個面試官有點深不可測。我像往常一樣,準備花 3 分鐘的時間給面試官來一套昨天晚上精心準備的自我介紹。我自信且得意的訴說著對過往專案所付出的心血,所做的優化取得了怎樣的成果,為公司提高了多少的收入。。。

顯然,面試官對我說的數字很感興趣,嘴角微微上揚,經過了一番細節的探討和技術的對線後。面試官拿出了一張紙。

手寫程式碼。

注重基礎的面試官是靠譜的,為了征服他,我一邊講解著實現原理一邊寫出了程式碼。

手寫 call

call 和 apply 的區別:call 方法接收的是一個引數列表,apply 方法接收的是一個包含多個引數的陣列。

  • 1.context 存在就使用 context,否則是 window
  • 2.使用 Object(context)context 轉換成物件,並通過 context.fnthis 指向 context
  • 3.迴圈引數,注意從 1 開始,第 0 個是上下文,後面才是我們需要的引數
  • 4.將引數字串 pushargs
  • 5.字串和陣列拼接時,陣列會呼叫 toString 方法,這樣可以實現將引數一個個傳入,並通過 eval 執行
  • 6.拿到結果返回前,刪除掉 fn
Function.prototype.call = function(context) {
    context = context ? Object(context) : window;
    context.fn = this;
    let args = [];
    for (let i = 1; i < arguments.length; i++) {
        args.push('arguments['+ i +']');
    }
    let res = eval('context.fn('+ args +')');
    delete context.fn;
    return res;
}

手寫 apply

  • 1.apply 無需迴圈引數列表,傳入的 args 就是陣列
  • 2.但是 args 是可選引數,如果不傳入的話,直接執行
Function.prototype.apply = function(context, args) {
    context = context ? Object(context) : window;
    context.fn = this;
    if (!args) {
        return context.fn();
    }
    let res = eval('context.fn('+ args +')');
    delete context.fn;
    return res;
}

手寫 bind

  • 1.bind 的引數可以在繫結和呼叫的時候分兩次傳入
  • 2.bindArgs 是繫結時除了第一個引數以外傳入的引數,args 是呼叫時候傳入的引數,將二者拼接後一起傳入
  • 3.如果使用 new 運算子構造繫結函式,則會改變 this 指向,this 指向當前的例項
  • 4.通過 Fn 連結原型,這樣 fBound 就可以通過原型鏈訪問父類 Fn 的屬性
Function.prototype.bind = function(context) {
    let that = this;
    let bindArgs = Array.prototype.slice.call(arguments, 1);
    function Fn () {};
    function fBound(params) {
        let args = Array.prototype.slice.call(arguments) ;
        return that.apply(this instanceof fBound ? this : context, bindArgs.concat(args));
    }
    Fn.prototype = this.prototype;
    fBound.prototype = new Fn();
    return fBound;
}

手寫 new

  • 1.Constructor 就是 new 時傳入的第一個引數,剩餘的 arguments 是其他的引數
  • 2.使用obj.__proto__ = Constructor.prototype 繼承原型上的方法
  • 3.將剩餘的 arguments 傳給 Contructor ,繫結 this 指向為 obj,並執行
  • 4.如果建構函式返回的是引用型別,直接返回該引用型別,否則返回 obj
const myNew = function() {
    let Constructor = Array.prototype.shift.call(arguments);
    let obj = {};
    obj.__proto__ = Constructor.prototype;
    let res = Constructor.apply(obj, arguments);
    return res instanceof Object ? res : obj;
}

手寫 instanceOf

  • 1.在 left 的原型鏈中層層查詢,是否有原型等於 prototype
  • 2.確定邊界條件,如果 left === null,即找到頭沒找到返回 falseright === left,即找到返回 true
  • 3.left = left.__proto__,不停的向上查詢
const myInstanceof = function(left, right) {
    right = right.prototype;
    left = left.__proto__;
    while (true) {
        if (left === null) {
            return false;
        }
        if (right === left) {
            return true;
        }
        left = left.__proto__;
    }
}

手寫 Object.create

  • 新建一個空的建構函式 F ,然後讓 F.prototype 指向 obj,最後返回 F 的例項
const myCreate = function (obj) {
  function F() {};
  F.prototype = obj;
  return new F();
}

掘金尾圖.png

相關文章