手寫call、apply、bind

哇喔WEB發表於2021-01-16

  關於this的指向性和執行上下文等問題本人也寫過相關文章對其進行詳細的解析,但是這些文章中好像遺忘了什麼?對,那就是如何改變this指向的作用域,所以也就引出今天的主角——callapplybind;這“三兄弟”是Function原型上的三個改變呼叫函式作用域/上下文的函式,具體如何使用可以直接檢視MDN,今天只研究如何實現這三種方法。

一、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' }

四、總結

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

相關文章