深入探索call apply bind的內部機制

2021-10-06 06:35:43 字數 2938 閱讀 4473

前一篇文章this的情況總結中,我們了解了bind方法的用法,簡單回顧:

[function].call([context],params1,params2,…),預先修改this,預先儲存引數,而函式不被立即執行。

下面我們就來康康到底是如何實現這個功能的!

let obj = 

function func()

function.prototype.bind = function bind(context = window,...params)

}document.body.onclick = func.bind(obj,10,20);

那麼問題來了,call方法是怎麼實現修改this指向並傳參的呢?

先上**:

/*

* @params

* context:把函式中的this指向修改為context

* ...params: es6中的剩餘運算子,除了context之外的其他引數,以陣列形式儲存在params中

* @return: 返回值與要執行的函式的返回值相同

* /function.prototype.call = function call(context,...params)else }

// 核心**

let key = symbol('aa');

let result;

context[key] = this;

result = context[key](...params);

delete context[key];

return result;

}let obj =

function func(x,y)

func.call(obj,10,20);

**解析:

首先看核心**部分:我們希望能執行函式,並把函式中的this修改為我們指定的引數context。上例中,我們想執行func函式並將函式中this修改為obj,obj怎樣才能成為func函式中的this呢?通過成員訪問的形式!obj.*** = func,給obj新增乙個屬性***(叫別的名字也行),並將要執行的函式賦值給這個屬性。那麼我們執行obj.***()時,不就是在執行func()麼!而且函式中的this也會變為obj!

context[key] = this;   // call函式中的this是要執行的函式func(因為func.call),這個操作是給context新增乙個屬性,並把要執行的函式賦值給這個屬性

result = context[key](...params); // 通過成員訪問的形式執行這個函式,同時把引數傳進去

delete context[keys]; // 新增了乙個屬性,用完了得再刪掉

為了保證我們給context新增屬性的時候,不會因為屬性名相同而覆蓋掉原有的屬性,最好是取乙個唯一的屬性名——通過symbol建立唯一值或時間戳。問題又來了:怎麼能保證context一定可以被新增屬性呢?

傳入值context型別判斷及處理

常見的資料型別有:number、string、boolean、null、undefined、symbol、bigint、object、function,其中,只有object和function兩種型別是可以為其新增屬性的。也就是說,當傳入的值不為object或function時,我們需要做一定的處理,以保證**能夠正常執行。

在此之前,我們需要了解建立乙個值的兩種方法

① 字面量建立:let num1 = 10;let obj1 = {};等。

② 建構函式建立:let num2 = new number(10);let obj2 = new object();等。

建立乙個引用型別值時,兩種建立方式即obj1obj2沒什麼區別。

建立乙個基本型別值時:通過字面量方式建立得到的結果num1為基本型別值,不可以為其設定屬性;通過建構函式建立得到的結果num2為物件型別值,可以為其設定屬性。但兩者都是所屬類的例項,都可以呼叫原型上的方法。

為了避免程式出錯,當context傳進來基本型別值時,需要將其轉換為對應的物件型別值。例如,new num1.constructor(num1),數字num1呼叫原型上的constructor屬性找到所屬類number,然後通過建構函式方式,建立乙個原始值為num1的物件型別值。

還有特殊情況 ( 手動捂臉: 怎麼那麼多特殊情況!!!):基本型別中,symbol和bigint不能被new。那咋辦捏,他倆簡單一點,直接用object套起來就能轉換為相應的物件型別。

(終於到了總結情況的時刻)

於是,當傳入的context為以下三種情況時,需要做特殊的處理:

(1) 不傳引數 / null / undefined 時,預設將this修改為window

context == undefined ? context = window : null;
(2) 傳入值為symbol / bigint 型別時,將其轉換為物件型別

context = object(context);
(3) 傳入值為number / string / boolean基本型別值時,通過建構函式建立對應的物件型別值:

new context.constructor(context);

就沒有什麼好說的啦,和call方法基本一樣,只不過執行函式傳引數的時候傳入陣列就好啦!

result = context[key](params);

深入探索建構函式

大致分為以下幾類 1 全預設引數初始化,不用傳參。2 半預設引數初始化,只需傳部分引數。3 無缺省引數初始化,定義多少個成員變數,傳多少個引數。4 使用初始化列表初始化,此類初始化更加高效,建議初始化的順序與宣告的順序相同。首先定義乙個日期類如下 class date 半預設引數 無缺省引數 初始化...

深入探索預設建構函式

問題 傳統認識為 如果我們自己在類中沒有定義任何建構函式,那麼編譯器就會為我們隱式自動生成乙個預設的建構函式,我們稱這種建構函式為 合成的預設建構函式 事實的真相果真如此嗎?結論 合成預設建構函式 只有在必要的時候,編譯器才會為我們自動合成出來,而不是必然為我們合成出來。那到底什麼時候是必要的呢?演...

深入探索C 物件模型

深入探索c 物件模型 本書目錄結構如下 第1章 關於物件 object lessons 加上封裝後的布局成本 layout costs for adding encapsulation 1.1 c 模式模式 the c object model 簡單物件模型 a object model 驅動物件模...