前一篇文章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();
等。
建立乙個引用型別值時,兩種建立方式即obj1
與obj2
沒什麼區別。
建立乙個基本型別值時:通過字面量方式建立得到的結果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 驅動物件模...