你應(yīng)該了解的this

  • this機(jī)制:this在運(yùn)行時(shí)進(jìn)行綁定,并不是在編寫是進(jìn)行綁定,它的上下文取決于函數(shù)調(diào)用時(shí)的各種條件。this的綁定和和函數(shù)的聲明位置沒有任何關(guān)系,只取決于函數(shù)的調(diào)用方式
  • 當(dāng)一個(gè)函數(shù)被調(diào)用的時(shí)候,會(huì)創(chuàng)建一個(gè)活動(dòng)記錄(執(zhí)行上下文),這個(gè)記錄會(huì)包含函數(shù)在哪里被調(diào)用、函數(shù)的調(diào)用方式、傳入的參數(shù)等信息,this就是這個(gè)記錄的一個(gè)屬性,會(huì)在函數(shù)的執(zhí)行中被找到。

四種綁定規(guī)則

默認(rèn)綁定

我們先來(lái)看一段代碼:

 var name = 'global'

 function person(){
   var name = 'inner'
   console.log(this.name)
 }

 person()  // global

這里我們執(zhí)行person()、輸出結(jié)果是在全局上的name的值,那么為什么呢,因?yàn)檫@里的this指向全局對(duì)象,執(zhí)行的是默認(rèn)綁定。
那么我們?cè)趺粗篮螘r(shí)使用默認(rèn)綁定呢,這里函數(shù) person()在調(diào)用的時(shí)候沒有應(yīng)用任何的修飾,直接使用的是 person(),因此就只能使用默認(rèn)綁定,那么使用修飾調(diào)用是什么樣的、之后會(huì)進(jìn)行詳細(xì)說(shuō)明。
但是也不是默認(rèn)綁定的所以情況都是指向全局對(duì)象,當(dāng)你使用嚴(yán)格模式編寫代碼時(shí),
this指向undefined。

'use strict'
 var name = 'global'

 function person(){
   var name = 'inner'
   console.log(this.name)
 }

 person() //Cannot read property 'name' of undefined

隱式綁定

先上代碼:

 var name = 'global'

 function person() {
   var name = 'inner'
   console.log(this.name)
 }

 var obj = {
   name:'objName',
   person: person
 }

obj.person()  // objName

這里我們遇見第一種帶有修飾的調(diào)用obj.person() 而非默認(rèn)綁定中的直接調(diào)用person()

就像我們最開始說(shuō)的那種,this不是在聲明時(shí)進(jìn)行綁定的,而是在調(diào)用時(shí)進(jìn)行綁定的。當(dāng)函數(shù)的引用有上下文對(duì)象時(shí),隱式綁定會(huì)把函數(shù)調(diào)用中的this綁定到這個(gè)上下文對(duì)象,這個(gè)例子中this綁定到obj對(duì)象上,this.name就相當(dāng)于obj.name,我們最終就會(huì)得到objName

下面這個(gè)例子中,說(shuō)明只有在引用鏈的最后一層起作用。

 var name = 'global'

 function person() {
   var name = 'inner'
   console.log(this.name)
 }

 var obj1 = {
   name:'obj1Name',
   person: person
 }
 
 var obj2 = {
  name:'obj2Name',
  obj1: obj1
 }

obj2.obj1.person()  // obj1Name

常見面試題:隱式丟失問題

 var name = 'global'
 
  function person() {
    var name = 'inner'
    console.log(this.name)
  }
 
  var obj1 = {
    name:'obj1Name',
    person: person
  }
  
 var obj2 = obj1.person
 
 obj2()// global

這里聲明一個(gè)變量 obj2 = obj1.person 之后調(diào)用 obj(2) 雖然obj2是對(duì)obj1.person的引用,但是在下文的調(diào)用時(shí)使用的是沒有任何修飾的調(diào)用,即obj2,這將應(yīng)用我們上面提到的默認(rèn)綁定,即非嚴(yán)格模式下this綁定到全局對(duì)象,
嚴(yán)格模式下綁定到undefined,輸出golbal也符合我們的預(yù)期。

再看下面的代碼:

 var name = 'global'

 function person() {
   var name = 'inner'
   console.log(this.name)
 }

 function student(fn) {
   // fn=obj1.person
   fn()
 }

 var obj1 = {
   name: 'obj1Name',
   person: person
 }

 student(obj1.person) //global

這里執(zhí)行student()是相當(dāng)于 fn=obj1.person fn是對(duì)obj1.person的引用,和上個(gè)例子中同樣,發(fā)生隱式綁定丟失,對(duì)this使用默認(rèn)綁定。

setTimeout:

 var name = 'global'

 function person() {
   var name = 'inner'
   console.log(this.name)
 }

 var obj1 = {
   name: 'obj1Name',
   person: person
 }

 setTimeout(obj1.person, 1000); // node 環(huán)境下 undefined

我們來(lái)看下 mdn對(duì)this指向錯(cuò)誤的解釋:

由setTimeout()調(diào)用的代碼運(yùn)行在與所在函數(shù)完全分離的執(zhí)行環(huán)境上。這會(huì)導(dǎo)致,這些代碼中包含的 this 關(guān)鍵字在非嚴(yán)格模式會(huì)指向 window (或全局)對(duì)象,嚴(yán)格模式下為 undefined,這和所期望的this的值是不一樣的
mdn上polyfill實(shí)現(xiàn)setTimeout的部分代碼

 window.setTimeout = function(vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */ ) {
  var aArgs = Array.prototype.slice.call(arguments, 2);
  return __nativeST__(vCallback instanceof Function ? function() {
    vCallback.apply(null, aArgs);
  } : vCallback, nDelay);
}

我們簡(jiǎn)化一下:去掉args

 window.setTimeout = function(vCallback, nDelay ) {
  return __nativeST__(vCallback instanceof Function ? vCallback() : vCallback, nDelay);
}

我們可以看出和上面一樣,vCallback = obj1.person 同樣是對(duì)obj1.person的引用。

顯示綁定

call、apply

一般來(lái)說(shuō)基本上所有的函數(shù)都是由Function創(chuàng)建,Function的原型鏈上有兩個(gè)方法call,apply

mdn上關(guān)于call和apply的方法接收的參數(shù):
call: fun.call(thisArg, arg1, arg2, ...)

apply:fun.apply(thisArg, [argsArray])

可以看出第一個(gè)參數(shù)是一樣的都是this,不同點(diǎn)在于call接收的是參數(shù)列表,apply接收一個(gè)數(shù)組。

這種顯示的更改this的指向方法,我們稱之為顯示綁定。

var name = 'global'

 function person() {
   var name = 'inner'
   console.log(this.name)
 }

 var obj1 = {
   name: 'obj1Name',
   person: person
 }

 person.call(obj1) //obj1Name

這樣我們雖然解決了this綁定的值,但是并沒有解決之前提到的綁定丟失問題。

bind:

變種:我們封裝一個(gè)函數(shù),當(dāng)每次調(diào)用函數(shù)時(shí)將this綁定到obj1上。

var name = 'global'

 function person() {
   var name = 'inner'
   console.log(this.name)
 }

 var obj1 = {
   name: 'obj1Name',
   person: person
 }

 function bind(fn,obj,...args){
   return function(){
     fn.apply(obj,args)
   }
 }

var res = bind(person,obj1)
res()  //obj1Name

同樣,javascript 也為我們提供了這個(gè)方法:Function.protoype.bind()

mdn 上關(guān)于bind()的polyfill:

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis,
                 // 獲取調(diào)用時(shí)(fBound)的傳參.bind 返回的函數(shù)入?yún)⑼沁@么傳遞的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 維護(hù)原型關(guān)系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}

可以看出都是和我們寫的bind()核心是一樣的內(nèi)部調(diào)用call、apply。

new 綁定

當(dāng)new 一個(gè)函數(shù)的時(shí)候會(huì)執(zhí)行一下的幾步:

  1. 創(chuàng)建一個(gè)空對(duì)象;
  2. 將空對(duì)象的原型指向函數(shù)的property
  3. 調(diào)用apply、call 方法空對(duì)象的this指向函數(shù)
  4. 如果函數(shù)返回了一個(gè)“對(duì)象”,那么這個(gè)對(duì)象會(huì)取代整個(gè)new出來(lái)的結(jié)果。如果構(gòu)造函數(shù)沒有返回對(duì)象,那么new出來(lái)的結(jié)果為步驟1創(chuàng)建的對(duì)象

之前寫了一篇模擬實(shí)現(xiàn) new的文章
new的模擬實(shí)現(xiàn)

示例:

 function Person(name) {
   this.name = name
 }


var student = new Person('lili')

console.log(student.name)  // lili

ES6 箭頭函數(shù)

箭頭函數(shù)本身沒有this,而是根據(jù)外層(函數(shù)或者全局)作用域來(lái)決定this.

 var name = 'outer'

 function person() {
   setTimeout(() => {
     console.log(this.name)
   }, 100)
 }

 var obj = {
   name: 'lili',
   person: person
 }

 obj.person() // lili
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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