前言
稍微翻了一下call,apply, bind 的各種論壇上的文章, 發現講的都太淺了,大部分都只講了個用法, 對於實現的原理卻都沒有提,因此,在這裏,我寫下這篇文章, 希望能讓大家認識到原理所在。
衆所周知, 這三個函數都是改變執行上下文的 , 那麼我們來捋一捋,這些函數內部到底做了什麼。
call
Function是函數對象的構造方法,call,apply,bind 都是函數原型上的方法 作爲實例 他自身也有這三個方法
圈中的爲原型方法, 方塊的爲實例方法,另外length屬性就是argument的長度,我們通常調用一個函數
function a(){ console.log(this,'a')};
function b(b){console.log(b)}
a.call(b,'我是B的參數')
執行a, 並把context指向b, 這裏大家都沒有疑問, 那麼問題來了
function a(){ console.log(this,'a')};
function b(){console.log(this,'b')}
a.call.call.call(b,'b') // 這個結果是什麼呢?
答案是
傻眼了吧 ? 怎麼執行了B 並且this指向了這個 b字符串
我們來分析一下 call是原型上的方法 那麼a.call 他本身也是一個函數 所以a.call.call.call 不就是a.call.call的原型上的call方法麼?
所以不就是執行call.call 並改變 call.call的上下文
我們來擼一遍call的源碼,
第一個參數是上下文, 當我們call(null),this指向了window 當我們傳入字符串 會把字符串包裝成對象
a.call 執行 this是指向a的(誰調用this 指向誰) 然後又執行了a方法,所以內部是
Function.prototype.call = function(context){
context = context ? Object(context):window
this() // 因爲調用的都是函數 所以this是一個函數 也就是a
}
那這樣並未改變this指向啊,咋辦? 上句話不是說((誰調用this 指向誰)),所以我們要在內部改變掉this,做如下修改
Function.prototype.call = function(context){
context = context ? Object(context):window
context.fn = this
context.fn() // 通過調用context.fn 來改變調用者 實現fn的this指向context 即改變a內部的this
}
那麼參數怎麼傳呢,我們首先要拿到call的裏的參數
Function.prototype.call = function(context,...args){
context = context ? Object(context):window
context.fn = this
let r = context.fn(...args) // 通過調用context.fn 來改變調用者 實現fn的this指向context 即改變a內部的this
delete context.fn //刪除屬性
return r // 返回執行的結果
}
現在我們回頭分析一下a.call.call.call(b,'b')
a.call.call是個函數,它調用call方法 執行它 我們進入函數裏面看看
首先把context 也就是b轉爲字符串對象 它的屬性上賦予fn 也就是a.call.call ,然後執行context.fn(...args), 也就是a.call.call('b') 接着刪除fn 返回執行結果 宏觀來看 是a.call.call這個函數去執行並傳入('b') a.call.call 也就是Function.prototype.call 所以就是call('b') 所以啊, 結果纔是this是指向string的b 並且參數是undefined
Function.prototype.call = function(context,...args){
context = context ? Object(context):window
context.fn = this //a.call.call('b')
var r = context.fn(...args) // 通過調用context.fn 來改變調用者 實現fn的this指向context 即改變a內部的this
delete context.fn
return r
}
function a(){ console.log(this,'a')};
function b(args){console.log('我是this:' + this,'我是b的參數args:' + args)}
a.call.call.call(b,'我到底是參數呢還是this')
結論!
一個函數call2次或者2次以上 執行的永遠是b, 並且call的第二個參數成爲當前context
apply
apply 就是參數不同 直接上代碼
Function.prototype.apply = function(context,...args){
context = context ? Object(context):window
context.fn = this;
var r;
if(args.length){
r = context.fn(...args)
delete context.fn
}else{
r = context.fn()
}
return r
}
function a(args){ console.log(this,args)};
function b(){console.log('我是this:' + this)}
a.apply(b,[1,2,3,4])
bind 明天寫 累了