首先,它是函數的一個方法,咱們須要將其--
Function.prototype.mybind =... //這樣,全部繼承自Function的函數就可以使用.操做符來訪問mybind了! //PS:由於JS原型式繼承
而後,讓咱們先看看原生JS的bind方法有哪些行爲--
讓調用該方法的函數的this指向傳入的第一個參數
咱們能夠藉助apply方法實現javascript
Function.prototype.mybind = function (context) { this.apply(context); }; let obj = { name: "Crushdada", }; let fn = function (params) { console.log(this.name); }; fn.mybind(obj); //Crushdada
注意兩點:
與此同時,因爲匿名函數中的this指向window/global,咱們須要使用箭頭函數或者手動保存一下指向mybind中指向調用者fn的thisjava
Function.prototype.mybind = function (context) { return () => this.apply(context); }; let obj = { name: "Crushdada", }; let fn = function (params) { console.log(this.name); }; fn.mybind(obj)(); //Crushdada
我的理解:相比「容許傳入參數」這種說法,形容爲「傳遞參數」更貼切,bind方法做爲一箇中間方法,會代收參數後再傳遞給它返回的匿名綁定函數,其返回一個匿名函數這一點,自然支持柯里化(多是ES6引入它的初衷之一),由於這樣就容許咱們在調用bind時傳入一部分參數,在調用其綁定函數時再傳入剩下的參數。而後它會在接收完第二次傳參後再apply執行調用bind的那個方法segmentfault
實現柯里化的邏輯很簡單,僅僅須要在mybind中接收一次參數,而後在綁定函數中接收一次參數,並將兩者拼接後一塊兒傳給mybind的調用方法使用便可數組
下面,實現傳參&柯里化!
若使用的是普通函數,要處理參數,因爲arguments爲類數組,slice爲Array方法,故先在原型鏈上調用而後call一下app
使用箭頭函數能極大簡化代碼
下面咱們改億點點細節!
不得不說ES6引入的rest運算符、擴展運算符在處理參數這一點上提供了極大的便利函數
Function.prototype.mybind = function (context, ...args) { return (...bindArgs) => { //拼接柯里化的兩次傳參 let all_args = [...args, ...bindArgs]; //執行調用bind方法的那個函數 let call_fn = this.apply(context, all_args); return call_fn; }; }; let person = { name: "Crushdada", }; let getInfo = function (like, fav) { let info = `${this.name} likes ${like},but his favorite is ${fav}`; return info; }; //anonymous_bind:mybind返回的那個匿名的綁定函數 let anonymous_bind = getInfo.mybind(person, "南瓜子豆腐"); let info = anonymous_bind("皁角仁甜菜"); //執行綁定函數 console.log(info); //Crushdada likes 南瓜子豆腐,but his favorite is 皁角仁甜菜
寫到支持柯里化這一步,bind方法仍是可使用箭頭函數實現的,並且比普通函數更加簡潔post
可是想要繼續完善它的的行爲,就不能用繼續用Arrow Function了,由於箭頭函數不能被new!,要是嘗試去new它會報錯:性能
anonymous_bind is not a constructor
筆者也是寫到這纔想起箭頭函數這個機制的。那麼下面咱們須要用普通函數重寫mybind
不過也很簡單,只須要手動保存一下this便可。就再也不貼出改動後的代碼了。直接看下一步
bind的一個隱式行爲:ui
且new調用時傳入的參數照常被傳遞給調用函數。this
邏輯
實現這一步的邏輯也較爲簡單,咱們類比一下和通常調用new時的區別--
主要須要咱們寫的邏輯有:
讓getInfo函數中的this指向--new中建立的實例對象obj
判斷getInfo函數是否返回一個對象,如果,則返回該對象,不然返回new生成的obj
至於爲何這麼寫,就須要你先弄懂new關鍵字實現的機制了,個人筆記連接附在文末
下面,實現它!
Function.prototype.mybind= function (context, ...args) { let self = this; return function (...bindArgs) { //拼接柯里化的兩次傳參 let all_args = [...args, ...bindArgs]; // new.target 用來檢測是不是被 new 調用 if (new.target !== undefined) { // 讓調用mybind的那個函數的this指向new中建立的空對象 var result = self.apply(this, all_args); // 判斷調用mybind方法的那個實際的構造函數是否返回對象,沒有返回對象就返回new生成的實例對象obj return result instanceof Object ? result : this; } //若是不是 new 就原來的邏輯 //執行調用bind方法的那個函數 let call_fn = self.apply(context, all_args); return call_fn; }; }; let person = { name: "Crushdada", }; let getInfo = function (like, fav) { this.dear = "Bravetata"; let info = `${this.name} likes ${like},but his favorite is ${fav}`; return info; }; //anonymous_bind:mybind返回的那個匿名的綁定函數 let anonymous_bind = getInfo.mybind(person, "南瓜子豆腐"); let obj = new anonymous_bind("皁角仁甜菜"); //執行綁定函數 console.log(obj); //{ dear: 'Bravetata' } console.log(obj.name); //undefined
解釋一下以上代碼:
第一個邏輯
第二個邏輯
接下來咱們直接判斷一下調用的結果,即它是否return一個對象,而後return給new作最終的return便可
此外:能夠看到,當new mybind返回的綁定函數時,obj沒有獲取到person.name屬性,爲undefined。也就是說--
看個栗子,這樣清楚一點
var value = 2; var foo = { value: 1 }; function bar(name, age) { this.habit = 'shopping'; console.log(this.value); console.log(name); console.log(age); } bar.prototype.friend = 'kevin'; var bindFoo = bar.bind(foo, 'daisy'); var obj = new bindFoo('18'); // undefined // daisy // 18 console.log(obj.habit); console.log(obj.friend); // shopping // kevin
儘管在全局和 foo 中都聲明瞭 value 值,最後依然返回了 undefind,說明綁定的this 失效了,
這是爲何呢?
若是你們瞭解 new 的模擬實現,就會知道了--
new是JS模擬面向對象的一個關鍵字,它的目的之一是實現繼承,它要去繼承構造函數(類)之中的屬性,那麼new關鍵字是怎樣去實現的呢?它在內部應用了相似這樣一條語句:
Con.apply(obj, args) //Con是new 的那個構造函數
new 關鍵字會先聲明一個空對象obj,而後將構造函數的this指向這個對象
這樣作會發生什麼--
詳見:《JS中new操做符作了什麼?》--Crushdada's Notes
讓咱們回到爲何this會失效這一問題上
瞭解完new關鍵字的相關實現,咱們已經獲得答案了--
new完綁定函數後,綁定函數內部的this 已經指向了 obj,而obj中沒有value這個屬性,固然就返回undefined了
實際上這一步是對綁定函數內重寫new方法的一個補充--
由於new方法原本就支持原型鏈繼承
邏輯
那麼咱們只須要--
讓new的實例對象obj的原型指向實際構造器getInfo的prototype便可
Object.setPrototypeOf(this, self.prototype);
規範化/嚴謹性
能夠爲mybind方法加上一個判斷,調用者必須是一個函數,不然拋出TypeError--
if (typeof this !== 'function' || Object.prototype.toString.call(this) !== '[object Function]') { throw new TypeError(this + ' must be a function'); }
一個疑問?
咱們模擬實現bind方法,終歸是經過apply實現的。而它源碼是如何實現的,對於我來講就像一個黑盒。也就是說:不用apply,它是如何實現?
百度的各類版本大都藉助apply實現的,不過很幸運在思否找到了答案--JS bind方法如何實現?
答者給出的替代apply的方法很簡單:
那麼咱們只須要--
將caller做爲一個對象方法掛載到context上:context.callerFn = caller
這樣,當執行該句時,至關於context調用了caller函數,那麼caller函數中的this天然就指向其調用者context了。
以上,就替代了apply在本例中的核心功能--調用函數同時改變this指向
此外,爲提升代碼性能,用完callerFn後就刪掉它
context.__INTERNAL_SECRETS = func try { return context.__INTERNAL_SECRETS(...args) } finally { delete context.__INTERNAL_SECRETS }
將apply替換爲以上代碼,就獲得最終版了
FFunction.prototype.mybind = function (context, ...args) { if ( typeof this !== "function" || Object.prototype.toString.call(this) !== "[object Function]" ) { throw new TypeError(this + " must be a function"); } let self = this; //這裏的this和self即:調用mybind的方法--fn() context.caller2 = self; return function (...bindArgs) { let all_args = [...args, ...bindArgs]; //new調用時,this被換成new方法最後要返回的實例對象obj if (new.target !== undefined) { try { this.caller = self; var result = this.caller(...all_args); } finally { delete this.caller; } Object.setPrototypeOf(this, self.prototype); return result instanceof Object ? result : this; } //當不是new調用時,this指向global/window(由於匿名函數返回後由全局調用) try { var final_res = context.caller2(...all_args); } finally { delete context.caller2; } return final_res; //調用mybind的那個函數[可能]有返回 }; };
該方法可以將有length屬性的對象或字符串轉換爲數組
所以像是arguments對象這樣擁有length屬性的類數組就可使用該方法轉換爲真正的數組
JS中,只有String和Array擁有.slice方法,對象沒有。
let slice = (arrlike) => Array.prototype.slice.call(arrlike); var b = "123456"; let arr = slice(b); console.log(arr); // ["1", "2", "3", "4", "5", "6"]
返回一個新的數組對象,這一對象是一個由begin
和end
決定的原數組的淺拷貝(包括begin
,不包括end
)。
begin、end
是數組indexconst animals = ['ant', 'bison', 'camel', 'duck', 'elephant']; console.log(animals.slice(2)); // expected output: Array ["camel", "duck", "elephant"] console.log(animals.slice(2, 4)); // expected output: Array ["camel", "duck"]
a || b :
若true,返回前面的值
當一個函數擁有形參,但調用時沒有傳實參時,形參是undefined,會被按false處理
function name(params) { console.log(params); //undefined } name(); console.log(undefined == false); //false console.log(undefined || "undefined was reated as false"); //undefined was reated as false
會被邏輯或運算符當作false處理的總共6個--
0、null、""、false、undefined 或者 NaN
參考: