深入call apply bind

前言

稍微翻了一下call,apply, bind 的各種論壇上的文章, 發現講的都太淺了,大部分都只講了個用法, 對於實現的原理卻都沒有提,因此,在這裏,我寫下這篇文章, 希望能讓大家認識到原理所在。

衆所周知, 這三個函數都是改變執行上下文的 , 那麼我們來捋一捋,這些函數內部到底做了什麼。

call

Function是函數對象的構造方法,call,apply,bind 都是函數原型上的方法 作爲實例 他自身也有這三個方法

clipboard.png

圈中的爲原型方法, 方塊的爲實例方法,另外length屬性就是argument的長度,我們通常調用一個函數

function a(){ console.log(this,'a')};
function b(b){console.log(b)}
a.call(b,'我是B的參數')

clipboard.png

執行a, 並把context指向b, 這裏大家都沒有疑問, 那麼問題來了

function a(){ console.log(this,'a')};
function b(){console.log(this,'b')}
a.call.call.call(b,'b')  // 這個結果是什麼呢?
答案是


clipboard.png

傻眼了吧 ? 怎麼執行了B 並且this指向了這個 b字符串

我們來分析一下 call是原型上的方法 那麼a.call 他本身也是一個函數 所以a.call.call.call 不就是a.call.call的原型上的call方法麼?
所以不就是執行call.call 並改變 call.call的上下文

我們來擼一遍call的源碼,

clipboard.png

第一個參數是上下文, 當我們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')

clipboard.png

結論!
一個函數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])

clipboard.png

bind 明天寫 累了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章