手寫call、apply、bind實現及詳解
call 接收多個引數,第一個為函式上下文也就是this,後邊引數為函式本身的引數。
let obj = { name: "一個" } function allName(firstName, lastName) { console.log(this) console.log(`我的全名是“${firstName}${this.name}${lastName}”`) } // 很明顯此時allName函式是沒有name屬性的 allName('我是', '前端') //我的全名是“我是前端”this指向window allName.call(obj, '我是', '前端') //我的全名是“我是一個前端” this指向obj 複製程式碼

apply
apply接收兩個引數,第一個引數為函式上下文this,第二個引數為函式引數只不過是通過一個數組的形式傳入的。
allName.apply(obj, ['我是', '前端'])//我的全名是“我是一個前端” this指向obj 複製程式碼
bind
bind 接收多個引數,第一個是bind返回值 返回值是一個函式 上下文的this,不會立即執行。
let obj = { name: "一個" } function allName(firstName, lastName, flag) { console.log(this) console.log(`我的全名是"${firstName}${this.name}${lastName}"我的座右銘是"${flag}"`) } allName.bind(obj) //不會執行 let fn = allName.bind(obj) fn('我是', '前端', '好好學習天天向上') // 也可以這樣用,引數可以分開傳。bind後的函式引數預設排列在原函式引數後邊 fn = allName.bind(obj, "你是") fn('前端', '好好學習天天向上') 複製程式碼
接下來搓搓手實現call、apply和bind
實現call
let Person = { name: 'Tom', say() { console.log(this) console.log(`我叫${this.name}`) } } // 先看程式碼執行效果 Person.say() //我叫Tom Person1 = { name: 'Tom1' } // 我們嘗試用原生方法call來實現this指向Person1 Person.say.call(Person1) //我叫Tom1 複製程式碼
通過第一次列印執行和第二次列印執行我發現,如果Person1有say方法那麼Person1直接執行Person1.say() 結果就是我是Tom1,是的call就是這麼實現的。 再看程式碼
Function.prototype.MyCall = function(context) { //context就是demo中的Person1 // 必須此時呼叫MyCall的函式是say方法,那麼我們只需要在context上擴充套件一個say方法指向呼叫MyCall的say方法這樣this console.log(this) context.say = this //Mycall裡邊的this就是我們虛擬的say方法 context.say() } // 測試 Person.say.MyCall(Person1)//我叫Tom1 複製程式碼

perfect!爆棚的滿足感!不過拿腳趾頭想想也不會這麼簡單,繼續完善 我們自己找茬 1、call支援多個引數,有可能一個也不沒有 2、考慮多引數時要把引數傳給擴充套件方法。 3、給上下文定義的函式要保持唯一不能是say 4、擴充套件完我們需要吧自定義函式刪除 接下來針對找茬問題一一解決
let Person = { name: 'Tom', say() { console.log(this) console.log(`我叫${this.name}`) } } Person1 = { name: 'Tom1' } //如果沒有引數 Person.say.call() 複製程式碼

沒有指定this,this指向window
我們也要這樣
Function.prototype.MyCall = function(context) { // 如果沒有引數我們參考call的處理方式 context = context || window //context就是demo中的Person1 // 必須此時呼叫MyCall的函式是say方法,那麼我們只需要在context上擴充套件一個say方法指向呼叫MyCall的say方法這樣this context.say = this //Mycall裡邊的this就是我們虛擬的say方法 context.say() } Person.say.MyCall() 複製程式碼

沒毛病! 繼續解決
// 找茬2:我們預設定義context.say = thisfn如果已經被佔用 嘎嘎 sb了。 不怕 搞定它 // say需要是一個唯一值 是不是突然想到es6的新型別 Symbolfn = Symbol() 不過我們裝逼不嫌事大 都說自己實現了 function mySymbol(obj) { // 不要問我為什麼這麼寫,我也不知道就感覺這樣nb let unique = (Math.random() + new Date().getTime()).toString(32).slice(0, 8) // 牛逼也要嚴謹 if (obj.hasOwnProperty(unique)) { return mySymbol(obj) //遞迴呼叫 } else { return unique } } //接下來我們一併把多引數和執行完刪除自定義方法刪除掉一塊搞定 Function.prototype.myCall1 = function(context) { // 如果沒有傳或傳的值為空物件 context指向window context = context || window let fn = mySymbol(context) context.fn = this //給context新增一個方法 指向this // 處理引數 去除第一個引數this 其它傳入fn函式 let arg = [...arguments].slice(1) //[...xxx]把類陣列變成陣列,arguments為啥不是陣列自行搜尋 slice返回一個新陣列 context.fn(...arg) //執行fn delete context.fn //刪除方法 } let Person = { name: 'Tom', say(age) { console.log(this) console.log(`我叫${this.name}我今年${age}`) } } Person1 = { name: 'Tom1' } Person.say.call(Person1,18)//我叫Tom1我今年18 複製程式碼
測試結果相當完美!
實現apply
接下來apply就簡單多了,只有多引數時第二個引數是陣列,就不一步步細說了。
Function.prototype.myApply = function(context) { // 如果沒有傳或傳的值為空物件 context指向window if (typeof context === "undefined" || context === null) { context = window } let fn = mySymbol(context) context.fn = this //給context新增一個方法 指向this // 處理引數 去除第一個引數this 其它傳入fn函式 let arg = [...arguments].slice(1) //[...xxx]把類陣列變成陣列,arguments為啥不是陣列自行搜尋 slice返回一個新陣列 context.fn(arg) //執行fn delete context.fn //刪除方法 } 複製程式碼
實現bind
這個和call、apply區別還是很大的,容我去抽根菸回來收拾它 還是老套路先分析bind都能幹些什麼,有什麼特點 1、函式呼叫,改變this 2、返回一個繫結this的函式 3、接收多個引數 4、支援柯里化形式傳參 fn(1)(2)
Function.prototype.bind = function(context) { //返回一個繫結this的函式,我們需要在此儲存this let self = this // 可以支援柯里化傳參,儲存引數 let arg = [...arguments].slice(1) // 返回一個函式 return function() { //同樣因為支援柯里化形式傳參我們需要再次獲取儲存引數 let newArg = [...arguments] console.log(newArg) // 返回函式繫結this,傳入兩次儲存的引數 //考慮返回函式有返回值做了return return self.apply(context, arg.concat(newArg)) } } // 搞定測試 let fn = Person.say.bind(Person1) fn() fn(18) 複製程式碼

是的,完美,實現了繫結this,返回函式,不立即執行,可以柯里化形式傳參。柯里化相關講解請移步: segmentfault.com/a/119000001…
簡版的實現就算完成了
歡迎吐槽or點贊!