它們有什麼不同?怎麼用?
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 = 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
接下來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,返回函數,不立即執行,可以柯里化形式傳參。柯里化相關講解請移步:https://segmentfault.com/a/11...
簡版的實現就算完成了
歡迎吐槽or點贊!