深刻call apply bind

前言

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

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

call

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

clipboard.png

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

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

clipboard.png

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

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

clipboard.png

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

咱們來分析一下 call是原型上的方法 那麼a.call 他自己也是一個函數 因此a.call.call.call 不就是a.call.call的原型上的call方法麼?
因此不就是執行call.call 並改變 call.call的上下文對象

咱們來擼一遍call的源碼,blog

clipboard.png

第一個參數是上下文, 當咱們call(null),this指向了window 當咱們傳入字符串 會把字符串包裝成對象ip

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(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 明天寫 累了

相關文章
相關標籤/搜索