手撕JS(可能持續更新···)

手撕JS(可能持續更新···)

  關於實現js中一些常見的方法屬於面試中的常問問題,可能剛開始接觸的時候會一籌莫展。知道和理解其中的原理能夠在日常開發中更如魚得水,面對面試也不成問題。另外,學會以目的(實現的功能)爲導向一層一層反推,總結出實現的思路就能按照步驟直接實現或者曲線實現(整理不易記得點贊哈)。

一、call的實現

  call()方法:讓call()中的對象調用當前對象所擁有的function。例如:test.call(obj,arg1,arg2,···) 等價於 obj.test(arg1,arg2,···);在手寫實現call()方法前我們先進行分析,test調用call方法可以看作將test方法作爲obj的一個屬性(方法)調用,等obj.test()執行完畢後,再從obj屬性上刪除test方法:

  • 1、將函數設置爲對象的屬性;
  • 2、處理傳入的參數;
  • 3、執行對象上設置的函數;
  • 4、刪除對象上第一步設置的函數;

myCall:

function test(a, b) {
  console.log(a);
  console.log(b);
  console.log(this.c);
}

let obj = {
  c: "hello",
};

//myCall
Function.prototype.myCall = function () {
  //聲明傳入上下文爲傳入的第一個參數,如果沒有傳參默認爲global(node環境),如果是瀏覽器環境則爲 window;
  let context = arguments[0] || global; 
  //將調用myCall方法函數(this)設置爲 聲明的傳入上下文中的fn函數;
  context.fn = this;
  //對函數參數進行處理
  var args = [];
  for (let index = 0; index < arguments.length; index++) {
    index > 0 && args.push(arguments[index]);
  }
  //執行fn,也就是調用myCall方法的函數
  context.fn(...args);
    //執行完畢後刪除傳入上下文的fn,不改變原來的對象
  delete context.fn;
};

test.myCall(obj, "a", 123);
console.log(obj)

打印的結果:

a
123
hello
{ c: 'hello' }

從結果可以看出:testthis.c輸出爲hello,說明thisobj;最後輸出的obj也沒有改變。

二、apply的實現

  apply()方法作用和call()完全一樣,只是apply的參數第一個爲需要指向的對象,第二個參數以數組形式傳入。例如:test.apply(obj,[arg1,arg2,···]) 等價於 obj.test(arg1,arg2,···)

myApply:

//myApply
Function.prototype.myApply = function(){
  let context = arguments[0] || global;
  context.fn = this;
  var args = arguments.length > 1 ? arguments[1] : [];
  context.fn(...args);
  delete context.fn;
}

test.myApply(obj, ["world", 123]);
console.log(obj)

打印的結果:

world
123
hello
{ c: 'hello' }

三、bind的實現

  bind方法:創建一個新的函數, 當被調用時,將其this關鍵字設置爲提供的值,在調用新函數時,在任何提供之前提供一個給定的參數序列。例如:let fn = test.bind(obj,arg1,arg2,···); fn() 等價於 let fn = obj.test; fn(arg1,arg2,···);實現思路爲:

  • 1、將函數設置爲對象的屬性;
  • 2、處理傳入的參數;
  • 3、返回函數的定義/引用;
  • 4、外層執行接收的函數;
  • 5、刪除對象上第一步設置的函數;

myBind:

Function.prototype.myBind = function(){
  //1.1、聲明傳入上下文爲傳入的第一個參數,如果沒有傳參默認爲global(node環境),如果是瀏覽器環境則爲 window;
  let context = arguments[0] || global;
  //1.2、將調用myBind方法函數(this)設置爲 聲明的傳入上下文中的fn函數;
  context.fn = this;
  //2.1、對調用myBind的函數參數進行處理
  var args = [];
  for (let index = 0; index < arguments.length; index++) {
    index > 0 && args.push(arguments[index]);
  }
    //3、聲明和定義函數變量F,用於返回給外層
  let F = function  (){
    //2.2、對再次傳入的參數進行處理,追加到
    for (let index = 0; index < arguments.length; index++) {
      args.push(arguments[index]);
    }
    //4.2、執行實際的調用myBind方法函數
    context.fn(...args);
    //5、執行完畢後刪除傳入上下文的fn,不改變原來的對象
    delete context.fn;
  }
  return F;
}

var f = test.myBind(obj, "a")
//4.1、執行返回的函數
f(9527);

打印的結果:

a
9527
hello
{ c: 'hello' }

四、Promise的實現

1、分析Promise使用

  MDN中關Promise的定義如下:

Promise 對象用於表示一個異步操作的最終完成 (或失敗)及其結果值。<br/>一個 Promise 對象代表一個在這個 promise 被創建出來時不一定已知的值。它讓您能夠把異步操作最終的成功返回值或者失敗原因和相應的處理程序關聯起來。 這樣使得異步方法可以像同步方法那樣返回值:異步方法並不會立即返回最終的值,而是會返回一個 promise,以便在未來某個時候把值交給使用者。<br/>
  <br/>一個 Promise 必然處於以下幾種狀態之一:<br/>
    <br/>待定(pending): 初始狀態,既沒有被兌現,也沒有被拒絕。<br/>
    <br/>已兌現(fulfilled): 意味着操作成功完成。<br/>
    <br/>已拒絕(rejected): 意味着操作失敗。<br/>
  <br/>待定狀態的 Promise 對象要麼會通過一個值被兌現(fulfilled),要麼會通過一個原因(錯誤)被拒絕(rejected)。當這些情況之一發生時,我們用 promise 的 then 方法排列起來的相關處理程序就會被調用。如果 promise 在一個相應的處理程序被綁定時就已經被兌現或被拒絕了,那麼這個處理程序就會被調用,因此在完成異步操作和綁定處理方法之間不會存在競爭狀態<br/>




new Promise((resolve, reject) => {
  //異步操作
  //···
  //執行完後調用resolve和reject輸出兩種不同結果
  if (true) {
    resolve("res");
  } else {
    reject("err");
  }
})
  .then((res) => { //then接受resolve中的結果
    console.log(res);
  })
  .catch((err) => { //catch接受reject中的結果
    console.log(err);
  });

Promise的使用分爲三步:

  • 1、新建Promise實例,即通過new實現,同時接受一個函數參數,函數參數中接受resolve和reject兩個形參(實質上也是函數);
  • 2、新建的Promise實例接受的函數參數中就是要執行的異步代碼,並且用resolve和reject對異步結果進行調用輸出;
  • 3、新建的Promise實例可以調用then和catch方法對異步結果進行接受和處理;

上述新建實例代碼可以轉化爲:

function fn(resolve, reject) {
  //異步操作
  //···
  //執行完後調用resolve和reject輸出兩種不同結果
  if (true) {
    resolve("res");
  } else {
    reject("err");
  }
}

let p = new Promise(fn);

p.then((res) => { //then接受resolve中的結果
    console.log(res);
  })

p.catch((err) => { //catch接受reject中的結果
    console.log(err);
  });

  上述中的使用者就是then和catch,結合代碼中的使用方式,簡單來說就是Promise中執行異步操作,then和catch只會在異步執行完後纔會接到返回結果繼續執行!

2、手撕Promise

  瞭解了Promise的定義和使用步驟後,接下來直接手撕Promise的實現,直接上實現Promise的代碼(內涵大量註釋,基本一句一解釋,但是邏輯還是得第三部分來講):

// 定義promise中的三種狀態
const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

// 定義promise的類
class myPromise {
  //class的構造函數,接受新建實例時的參數:executor在promise中是一個函數
  constructor(executor) {
    //初始化該class中的初始狀態
    this.status = STATUS_PENDING;
    //定義class中成功(res)和失敗(err)時的變量值
    this.res = "";
    this.err = "";

    //promis異步中最重要的異步,定義成功和錯誤函數存儲的數組,存放異步時還沒有執行的操作
    this.onResCallbacks = [];
    this.onErrCallbacks = [];

    //定義該構造函數constructor定義域中的變量resolve
    let resolve = (res) => {
      // 首先判斷該class中的狀態,只有狀態爲pending時才能轉化class轉態爲fulfilled或者rejected
      if (this.status === STATUS_PENDING) {
        //修改class的轉態爲fulfilled,也就表示不會轉進行其他轉態的轉化了
        this.status = STATUS_FULFILLED;
        //將成功(resolve)狀態下的值賦給class的成功返回res
        this.res = res;
        //此時狀態由pending轉爲fulfilled,執行之前在then中存放的需要執行的異步操作,promise的then中參數res接受結果
        this.onResCallbacks.forEach((fn) => {
          fn();
        });
      }
    };

    //定義該構造函數constructor定義域中的變量reject
    let reject = (err) => {
      // 首先判斷該class中的狀態,只有狀態爲pending時才能轉化class轉態爲fulfilled或者rejected
      if (this.status === STATUS_PENDING) {
        //修改class的轉態爲rejected,也就表示不會轉進行其他轉態的轉化了
        this.status = STATUS_REJECTED;
        //將失敗(reject)狀態下的值賦給class的失敗返回err
        this.err = err;
        //此時狀態由pending轉爲rejected,執行之前在catch中存放的需要執行的異步操作,promise的catch中參數err接受結果
        this.onErrCallbacks.forEach((fn) => {
          fn();
        });
      }
    };

    //按照promise中的邏輯,在調用時就立即執行了,所以在手寫的myPromise創建構造函數constructor時就執行executor
    try {
      //執行參入的函數,並將上述定義的resolve和reject作爲參數傳入
      executor(resolve, reject);
    } catch (err) {
      //報錯時調用失敗的狀態函數
      reject(err);
    }
  }

  //在class中定義promise的成功狀態接收函數then,按照promise邏輯,then中傳入的一般都是一個函數
  then(onRes = () => {}) {
    //如果是異步的,此時在constructor中status的狀態還沒變成fulfilled,所以會跳過onRes調用,沒有返回
    if (this.status === STATUS_FULFILLED) {
      onRes(this.res);
    }
    //但是我們將此時的異步放入數組存放
    if (this.status === STATUS_PENDING) {
      this.onResCallbacks.push(() => onRes(this.res));
    }
    //這步操作保證了then和catch能夠在同級一起"."調起,當then上述操作完後,返回class實例,便可以接在後面繼續調用catch
    return this;
  }

  //在class中定義promise的失敗狀態接收函數catch,按照promise邏輯,catch中傳入的一般都是一個函數
  catch(onErr = () => {}) {
    //如果是異步的,此時在constructor中status的狀態還沒變成rejected,所以會跳過onErr調用,沒有返回
    if (this.status === STATUS_REJECTED) {
      onErr(this.err);
    }
    //但是我們將此時的異步放入數組存放
    if (this.status === STATUS_PENDING) {
      this.onErrCallbacks.push(() => onErr(this.err));
    }
     //這步操作保證了then和catch能夠在同級一起"."調起,當catch上述操作完後,返回class實例,便可以接在後面繼續調用then
    return this;
  }
}

//調用自己手寫的promise
new myPromise((resolve, reject) => {
  console.log("進入了手寫的promise");
  //用setTimeOut模擬異步操作
  setTimeout(() => {
    if (false) {
      resolve("輸出成功結果resolve");
    } else {
      reject("輸出失敗結果reject");
    }
  }, 2000); //按照js的特性,此時不會等待異步完成,直接調用then或者catch
})
  .then((res) => {
    console.log("then:", res);
  })
  .catch((err) => { //return this具體作用體現在這裏
    console.log("catch:", err);
  });

手撕JS(可能持續更新···)

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