this指向與call,apply,bind

this問題對于每個前端同學來說相信都不陌生,在平時開發(fā)中也經(jīng)常能碰到,有時候因為this還踩過不少坑,并且this問題在面試題中出現(xiàn)的概率也非常高,我們一起來了解一下this的指向與call,applybind

this的指向

ES5中的this

在ES5中,this一般指向函數(shù)調(diào)用時所在的執(zhí)行環(huán)境,與函數(shù)定義的位置無關(guān)。也可以理解成this永遠指向最后調(diào)用它的對象

  • 在普通函數(shù)中的this總是指向它的直接調(diào)用者,默認情況下指向全局對象(瀏覽器為window)
  • 在嚴格模式下,沒有直接調(diào)用者的函數(shù)中的thisundefined
  • Call, apply,bind函數(shù),this指向的是綁定的對象
  • 對象函數(shù)調(diào)用,this指向調(diào)用它的那個對象
  • 構(gòu)造函數(shù)中的this,指向該構(gòu)造函數(shù)new出來的實例對象
var obj = {
    a:function(){
        console.log(this)
        console.log(this.b)
        console.log(this.c)
        console.log(this.a)
    },
    b:2,
    c:3
}
var b = obj.a
b()
// 結(jié)果:window,f(){...},undefined,undefined
obj.a()
// 結(jié)果:{a:..,b:2,c:3},2,3,f(){...}

/**
 * 解析:
 * b()調(diào)用,此時b函數(shù)所處的執(zhí)行環(huán)境是全局環(huán)境,this指向window
 * obj.a()調(diào)用,此時a是作為對象方法進行調(diào)用,this指向調(diào)用對象obj
 */
ES6中的this

在ES6中新增了一種箭頭函數(shù),箭頭函數(shù)的this始終指向它定義時的this,而非執(zhí)行時

  • 箭頭函數(shù)沒有自己的this,它的this是繼承來的,默認指向它定義時所在的對象,即箭頭函數(shù)中的this指向外層代碼的this
  • 不可以當作構(gòu)造函數(shù),也就是說,不可以用new命令調(diào)用,否則會拋出一個錯誤
  • 箭頭函數(shù)內(nèi)沒有arguments對象,可以用rest參數(shù)代替
  • 不可以使用yield命令,因此箭頭函數(shù)不能用作generator函數(shù)
  • 箭頭函數(shù)沒有自己的this,所以不能用call,apply,bind這些方法改變this指向
var obj = {
            hi: function(){
                console.log(this);
                return ()=>{
                    console.log(this);
                }
            },
            sayHi: function(){
                return function() {
                    console.log(this);
                    return ()=>{
                        console.log(this);
                    }
                }
            },
            say: ()=>{
                console.log(this);
            }
        }
        const hi = obj.hi()   //this->obj對象
        hi()   // this->obj對象
        const sayHi = obj.sayHi()
        const sayHiBack = sayHi()  //this->window
        sayHiBack() //this->window
        obj.say() //this->window

輸出結(jié)果依次為obj對象,obj對象,window,window,window

解析:

1.第一個obj.hi()很好理解,hi為普通函數(shù),this指向調(diào)用它的那個對象,即obj

2.第二個執(zhí)行hi(),它其實是上一個執(zhí)行后返回的函數(shù),并且是箭頭函數(shù),箭頭函數(shù)本身沒有this,我們往他的上一級去查找,我們剛剛得出上一級的this為obj,所以這里的this也指向obj

3.第三個執(zhí)行obj.sayHi(),這里沒有打印this,而是返回了一個普通函數(shù)

4.第四個執(zhí)行sayHi(),其實執(zhí)行的是剛剛返回的那個普通函數(shù),這里的this則指向調(diào)用它的那個對象,沒有則指向window

5.第五個執(zhí)行sa yHiBack(),指向的是剛剛第四次執(zhí)行返回的箭頭函數(shù),OK,箭頭函數(shù)我們往上一層找,也是window

5.第六個執(zhí)行obj.say(),這里這個say()是一個箭頭函數(shù),當前代碼塊obj不存在this,只能往上一層查找,指向window

call, apply,bind的區(qū)別

我們都知道call,apply,bind都可以用來改變this指向,但這三個函數(shù)稍稍有些不同。

  • call與apply唯一的區(qū)別就是它們的傳參方式不同,call從第二個參數(shù)開始都是傳給函數(shù)的,apply只有兩個參數(shù),第二個參數(shù)是一個數(shù)組,傳給函數(shù)的參數(shù)都寫在這個數(shù)組里面
  • call與apply改變了函數(shù)的this指向后會立即執(zhí)行,而bind是改變函數(shù)的this指向并返回這個這個函數(shù),不會立即執(zhí)行
  • call與apply的返回值是函數(shù)的執(zhí)行結(jié)果,bind的返回值是改變了this指向的函數(shù)的拷貝
call

call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數(shù)來調(diào)用一個函數(shù)。(來自MDN)

語法: fun.call(thisArg,arg1[,arg2,arg3...])

解釋: call方法用來為一個函數(shù)指定this對象,第一個參數(shù)是你想要指定的那個對象,后面都是傳給該函數(shù)的參數(shù),之間用逗號隔開

var person = {
  name:'南玖',
  gender: 'boy',
}
var speak = function(age,hobbit){
  console.log(`我是${this.name},今年${age}歲,愛好${hobbit},歡迎優(yōu)秀的你關(guān)注~`)
}
speak.call(person,18,'前端開發(fā)') // 我是南玖,今年18歲,愛好前端開發(fā),歡迎優(yōu)秀的你關(guān)注~
apply

apply() 方法調(diào)用一個具有給定this值的函數(shù),以及以一個數(shù)組(或類數(shù)組對象)的形式提供的參數(shù)。(來自MDN)

語法: fun.apply(thisArg,[arg1,arg2,arg3...])
解釋: apply方法與call方法基本類似,不同的是,兩者的參數(shù)形式,apply方法傳遞的是一個由若干個參數(shù)組成的數(shù)組。

var person = {
  name:'南玖',
  gender: 'boy',
}
function speak(age,hobbit){
  console.log(`我是${this.name},今年${age}歲,愛好${hobbit},歡迎優(yōu)秀的你關(guān)注~`)
}
speak.apply(person,[18,'打籃球??']) //我是南玖,今年18歲,愛好打籃球??,歡迎優(yōu)秀的你關(guān)注~
bind

bind() 方法會創(chuàng)建一個新函數(shù)。當這個新函數(shù)被調(diào)用時,bind() 的第一個參數(shù)將作為它運行時的 this,之后的一序列參數(shù)將會在傳遞的實參前傳入作為它的參數(shù)。(來自于 MDN )

語法: fun.bind(thisArg,arg1[,arg2,arg3...])()
解釋: bind方法用來為方法指定this對象并返回一個新的函數(shù),它的參數(shù)與call函數(shù)一樣。它本身是不會調(diào)用的,需要自己手動調(diào)用。

var person = {
  name:'南玖',
  gender: 'boy',
}
function speak(age,hobbit){
  console.log(`我是${this.name},今年${age}歲,愛好${hobbit},歡迎優(yōu)秀的你關(guān)注~`)
}
speak.bind(person,18,'旅游??')() //我是南玖,今年18歲,愛好旅游??,歡迎優(yōu)秀的你關(guān)注~

注意這里需要自己再調(diào)用一次,因為bind只會返回這個改變了this指向的函數(shù),并不會自己執(zhí)行

call,apply該用哪個?

  • 參數(shù)數(shù)量,順序確定就用call,參數(shù)數(shù)量,順序不確定就用apply
  • 參數(shù)數(shù)量少用call,參數(shù)數(shù)量多用apply
  • 參數(shù)集合已經(jīng)是一個數(shù)組的情況,最好用apply

bind的應(yīng)用場景

1.保存參數(shù)

我們先來看一道經(jīng)典面試題

for(var i=1;i<6;i++){
  setTimeout(()=>{
    console.log(i)  // 6,6,6,6,6
  },i*1000)
}

相信大家都知道這里會打印出五個6,因為在執(zhí)行settimeout回調(diào)函數(shù)時,i已經(jīng)變成了6

那么如何讓它打印出1,2,3,4,5呢?

當然方法有很多,比如閉包、將var改成let使它形成塊級作用域,這里先不講,后面單獨講閉包會提出來

我們也可以用bind來解決

for(var i=1;i<6;i++){
  setTimeout(function(i){
    console.log(i) // 1,2,3,4,5
  }.bind(null,i),i*1000)
}
2.回調(diào)函數(shù)this丟失問題
var student = {
  subject:['JS','VUE','REACT'],
  study: function(){
    setTimeout(function(){
      console.log(`我是南玖,我在學習${this.subject.join('、')}`)
    }.bind(this),0)
  }
}
student.study() //我是南玖,我在學習JS、VUE、REACT

想一想這里settimeout的回調(diào)如果不用bind綁定this,結(jié)果會怎樣?

結(jié)果是報錯,因為不給settimeout回調(diào)函數(shù)綁定this的話,那它的this應(yīng)該指向的是全局window,全局沒有subject,調(diào)用join會報錯

模擬call

思路:

  1. 根據(jù)call的規(guī)則設(shè)置上下文對象,也就是this的指向。
  2. 通過設(shè)置context的屬性,將函數(shù)的this指向到context上
  3. 通過隱式綁定執(zhí)行函數(shù)并傳遞參數(shù)。
  4. 刪除臨時屬性,返回函數(shù)執(zhí)行結(jié)果
Function.prototype.myCall = function(context){
    // context指的是那個想要借方法的對象,并為它指定默認值,沒傳就是window
    var context = context || window
    // 將要借用的那個方法綁定在當前要使用該方法的對象的fn屬性上
    context.fn = this
    // 這里的this指向你想要借用的那個方法也就是.myCall前面的調(diào)用者(這里的this指的是一個函數(shù))
    console.log(this)
    //獲取參數(shù),也就是相當于call的參數(shù)列表
    var args = [...arguments].slice(1)
    // 將參數(shù)傳給該函數(shù)并執(zhí)行
    var res = context.fn(...args)
    // 刪除該方法
    delete context.fn
    // 返回執(zhí)行結(jié)果
    return res
}

模擬apply

思路:

  • 與call類似,主要區(qū)別是參數(shù)的處理
/* 
實現(xiàn)原理與call類似,主要是參數(shù)不同,apply接受一個參數(shù)數(shù)組
*/
Function.prototype.myApply = function (context){
    var context = context || window
    context.fn = this
    // 判斷第二個參數(shù)是否為數(shù)組,不為數(shù)組需提示用戶(報錯提示)
    console.log(arguments.length)
    if(arguments.length > 2){
        throw new Error('只能傳遞兩個參數(shù)')
    }else if(!(arguments[1] instanceof Array)){
        throw new Error('第二個參數(shù)需要是數(shù)組類型')
    }
    var res = context.fn(...arguments[1])
    delete context.fn
    return res
}

模擬bind

Function.prototype.myBind = function(context){
    var context = context || window
    var _this = this
    var args = [...arguments].slice(1)
//    這里返回的是一個函數(shù)
    var res = function(){
        return _this.apply(context,...args)
    }
    return res
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容