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接收兩個參數,第一個參數爲函數上下文this,第二個參數爲函數參數只不過是經過一個數組的形式傳入的。es6
allName.apply(obj, ['我是', '前端'])//個人全名是「我是一個前端」 this指向obj
bind 接收多個參數,第一個是bind返回值返回值是一個函數上下文的this,不會當即執行。segmentfault
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數組
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就是這麼實現的。
再看代碼app
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!爆棚的知足感!不過拿腳趾頭想一想也不會這麼簡單,繼續完善
咱們本身找茬
一、call支持多個參數,有可能一個也不沒有
二、考慮多參數時要把參數傳給擴展方法。
三、給上下文定義的函數要保持惟一不能是say
四、擴展完咱們須要吧自定義函數刪除
接下來針對找茬問題一一解決dom
let Person = { name: 'Tom', say() { console.log(this) console.log(`我叫${this.name}`) } } Person1 = { name: 'Tom1' } //若是沒有參數 Person.say.call()
咱們也要這樣函數
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 = this fn若是已經被佔用 嘎嘎 sb了。 不怕 搞定它 // say須要是一個惟一值 是否是忽然想到es6的新類型 Symbol fn = 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就簡單多了,只有多參數時第二個參數是數組,就不一步步細說了。this
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 //刪除方法 }
這個和call、apply區別仍是很大的,容我去抽根菸回來收拾它
仍是老套路先分析bind都能幹些什麼,有什麼特色
一、函數調用,改變this 二、返回一個綁定this的函數 三、接收多個參數 四、支持柯里化形式傳參 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,返回函數,不當即執行,能夠柯里化形式傳參。柯里化相關講解請移步:https://segmentfault.com/a/11...
簡版的實現就算完成了
歡迎吐槽or點贊!