溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JavaScript之this指向?qū)嵗治?/h1>
發(fā)布時間:2022-05-10 14:00:19 來源:億速云 閱讀:155 作者:zzz 欄目:開發(fā)技術(shù)

今天小編給大家分享一下JavaScript之this指向?qū)嵗治龅南嚓P(guān)知識點,內(nèi)容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

默認綁定,全局對象

正所謂近水樓臺先得月,全局對象作為對遙遠的對象是作為備胎的存在,為語言邊界護城河做兜底。

一般情況下,this 指向全局對象則屬于默認綁定。那么什么是默認綁定呢?

this 默認綁定,通俗地可理解為函數(shù)被調(diào)用時無任何調(diào)用前綴對象的情景,由于函數(shù)調(diào)用時無調(diào)用前綴對象或函數(shù)無特定綁定,所以非嚴格模式下此時 this 會指向全局對象。

在非嚴格模式下,不同終端的全局變量對象有所區(qū)別:

• 在瀏覽器端,this 指向 Window 對象;

• 在 Nodejs 環(huán)境,this 指向 global 對象;

• 在函數(shù)環(huán)境,this 指向 綁定當前函數(shù)的作用域;

在嚴格模式下:

• 在 use strict 環(huán)境, this 指向 undefined;

???? 在非嚴格模式下

{
  /* 在非嚴格模式下,this默認綁定 */
  console.log('window global this: ', this); // window
  function fnOuter() {
    console.log('fnOuter: ', this); // window
  }
  function windowThis() {
    console.log('windowThis: ', this); // window
    function fnInner() {
      console.log('fnInner: ', this); // window
      fnOuter();
    }
    fnInner();
  }
  windowThis();
}

上述栗子中,無論函數(shù)聲明在哪,在哪調(diào)用,由于函數(shù)調(diào)用時前面并未指定任何對象,這種情況下 this 均指向全局對象 window。

但須注意的是,在嚴格模式下,默認綁定下的 this 會指向 undefined。

???? 在嚴格模式下,再來看幾個栗子,然后在心中記下答案

{
  /* 在非嚴格模式下,this默認綁定 */
  var mode = '在非嚴格模式下,this默認綁定';
  function windowThis() {
    console.log('windowThis: ', this);
    console.log('windowThis: ', this.mode);
    function fnInner() {
      console.log('fnInner: ', this);
      console.log('fnInner: ', this.mode);
    }
    fnInner();
  }

  function windowStrictThis() {
    'use strict';
    windowThis();

    function fnInner() {
      console.log('windowStrictThis: ', this);
      console.log('windowStrictThis: ', this.mode);
    }
    fnInner();
  }
  windowStrictThis();
}

建議得出答案再看下文,

一起來倒數(shù)吧,“花栗鼠。。。。。?!?。

????????????? ????????????? ????????????? ????????????? ????????????? ????????????? ????????????? ????????????? ?????????????

好啦,來看正確輸出吧,都答對了吧~

// windowThis:  Window{}
// windowThis:  在非嚴格模式下,this默認綁定
// fnInner:  Window{}
// fnInner:  在非嚴格模式下,this默認綁定
// windowStrictThis:  undefined
// windowStrictThis:  TypeError: Cannot read property 'mode' of undefined

可見在函數(shù)內(nèi)部使用嚴格模式聲明后,this 指向變?yōu)?undefined 了,同時在函數(shù)內(nèi)聲明嚴格模式只對函數(shù)內(nèi)定義的變量與函數(shù)有關(guān),跟其調(diào)用的外部函數(shù)無關(guān)。

點石成金,隱式綁定

什么是隱式綁定呢?

this 隱式綁定:如果在函數(shù)調(diào)用的時,函數(shù)前面存在調(diào)用他的對象,那么 this 就會隱式綁定到這個調(diào)用對象上。

所以隱式綁定的關(guān)鍵點在于函數(shù)被調(diào)用的對象是誰,說白了就是找調(diào)用這個函數(shù)前面的點.是誰,誰就是 this 所綁定的對象了。

???? 舉個栗子:

{
  var mode = 'window';
  var boss1 = {
    mode: 'boss1',
    fn() {
      console.log(this.mode);
    },
  };
  var boss2 = {
    mode: 'boss2',
    call: boss1.fn,
    o: boss1,
  };
  boss2.o.fn(); // boss1
  boss2.call(); // boss2

  var boss1Copy = boss1.fn;
  boss1Copy(); // window
}

函數(shù)隱式綁定時,如果函數(shù)調(diào)用時存在多個對象,this 指向距離自己最近的對象,也就是 . 前面的對象是誰,this 就指向誰。

那么問題來了,如果刪除 boss2 上的 mode,會有什么不一樣呢?

???? 舉個栗子:

{
  var mode = 'window';
  var boss1 = {
    mode: 'boss1',
    fn() {
      console.log(this.mode);
    },
  };
  var boss2 = {
    call: boss1.fn,
    o: boss1,
  };
  boss2.call(); // undefined
}

答案是輸出 undefined,因為此時由于 boss1 只是 boss2 的屬性,boss1 與 boss2 的原型鏈各不相同相同,不屬于父子關(guān)系,因此符合作用域鏈查找規(guī)則,所以 this 須從 boss2 上找 mode 屬性,當 boss2 上不存在 mode 屬性時則返回 undefined。注意不要與作用域鏈混淆了。

???? 下面這個例子就要小心點咯,能想出答案么?

{
  var mode = 'window';
  var boss1 = {
    mode: 'boss1 mode',
    fn() {
      console.log(this.mode);
    },
  };
  function Fn() {}
  Fn.prototype.mode = 'Fn mode';
  Fn.prototype.fnProto = function() {
    console.log(this.mode);
  };
  var boss2 = {
    mode: 'boss2 mode',
    fn: function() {
      return boss1.fn();
    },
    proto: new Fn(),
  };
  boss2.fn(); // boss1 mode
  boss2.proto.fnProto(); // Fn mode
}

答案是 boss1 mode 和 Fn mode 哦,猜對了嗎。

涉及到原型鏈與作用域鏈的以一些區(qū)別,基本這里就不做解析了,請各自查漏補缺~。

隱式綁定丟失

相信細心的同學已經(jīng)發(fā)現(xiàn),上述例子有一個函數(shù)賦值給變量再調(diào)用的情景。當函數(shù)賦值再調(diào)用后,原本 this 指向會發(fā)生改變,函數(shù)的 this 不會指向其原對象,從而引起隱形綁定丟失問題。

常見引起隱形丟失的方式:

1. 函數(shù)賦值變量再調(diào)用

???? 舉個栗子:

{
  var mode = 'window';
  var boss1 = {
    mode: 'boss1',
    fn() {
      console.log(this.mode);
    },
  };
  var boss2 = {
    mode: 'boss2',
    call: boss1.fn,
    o: boss1,
  };
  boss2.o.fn(); // boss1
  boss2.call(); // boss2

  var boss1Copy = boss1.fn;
  boss1Copy(); // window
}

上述案例 boss1Copy 和 boss2.call 就是函數(shù)賦值變量再調(diào)用的情況

1. 函數(shù)以形參傳遞

???? 舉個栗子:

{
  var mode = 'window';
  var boss1 = {
    mode: 'boss1',
    fn() {
      console.log(this.mode);
    },
  };
  function exce(params) {
    params && params();
  }
  exce(boss1.fn); // window
}

上述例子中我們將 boss1.fn 也就是一個函數(shù)傳遞進 exce 中執(zhí)行,這里只是單純傳遞了一個函數(shù)而已,this 并沒有跟函數(shù)綁在一起,此時 this 指向原對象發(fā)送 丟失從而指向了 window。

可見,隱式丟失本質(zhì)上是因為函數(shù)賦值引起的,在函數(shù)賦值給變量或另一個函數(shù)形參 Fn 后,在調(diào)用 Fn 時 this 會指向離其最近的對象。

指腹為婚,顯式綁定

this 顯式綁定:指通過 Object.prototype.call、Object.prototype.apply、Object.prototype.bind 方法改變 this 指向。

這里我將顯式綁定細分為:

• 顯式綁定:在運行時改變 this 指向

• call

• apply

• 顯式硬綁定:一次綁定后,永久不能改變 this 指向

• bind

接下來看個例子,分別通過 call、apply、bind 改變了函數(shù) log 的 this 指向。

???? 舉個栗子:

{
  function log() {
    console.log(this.name);
  }
  var boss1 = { name: 'boss1' };
  var boss2 = { name: 'boss2' };
  var boss3 = { name: 'boss3' };

  log.call(boss1); // boss1
  log.apply(boss2); // boss2
  log.bind(boss3)(); // boss3

  var logBind = log.bind(boss3);
  logBind.apply(boss1); // boss3
  logBind.bind(boss2); // boss3
}

在 JavaScript 中,當調(diào)用一個函數(shù)時,我們習慣稱之為函數(shù)調(diào)用,此時函數(shù)處于一個被動的狀態(tài);而 bind、 call 與 apply 讓函數(shù)從被動變主動,函數(shù)能主動選擇自己的上下文,所以這種寫法我們又稱之為函數(shù)應用。

注意,如果在使用 bind、 call 與 apply 之類的方法改變 this 指向時,指向參數(shù)提供的是 null 或者 undefined 時, this 將指向全局對象。

???? 舉個栗子:

{
  var name = 'window';
  function log() {
    console.log(this.name);
  }
  var boss1 = { name: 'boss1' };
  var boss2 = { name: 'boss2' };
  var boss3 = { name: 'boss3' };

  log.call(null); // window
  log.apply(undefined); // window
  log.bind(undefined)(); // window
}

同樣值得注意的是,bind 在顯式改變 this 指向之后會返回一個新的綁定函數(shù)(bound function,BF)。綁定函數(shù)是一個 exotic function object(怪異函數(shù)對象,ECMAScript 2015 中的術(shù)語),它包裝了原函數(shù)對象。調(diào)用綁定函數(shù)通常會導致執(zhí)行包裝函數(shù)。

另外 簡明 補充一下 call、apply、bind 的區(qū)別:

• bind:函數(shù)硬綁定 this 指向并返回一個全新函數(shù) BF,且返回的 BF 無法再次被 call、apply、bind 改變 this 指向,且需要執(zhí)行 BF 才會運行函數(shù)。

• function.bind(thisArg[, arg1[, arg2[, ...]]])()

• call:改變 this 指向的同時還會執(zhí)行函數(shù),一個以散列形式的形參。

• function.bind(thisArg[, arg1[, arg2[, ...]]])

• apply:改變 this 指向的同時還會執(zhí)行函數(shù),可接受一個數(shù)組形式的形參。

• function.apply(thisArg,[param1,param2...])

call & apply 主要區(qū)別在于傳參形式不同,在傳參的情況下,call 的性能要高于 apply,因為 apply 在執(zhí)行時還要多一步解析數(shù)組。

內(nèi)有乾坤,new 綁定

嚴格來說,JavaScript 中的構(gòu)造函數(shù)只是使用 關(guān)鍵字 new 調(diào)用的普通函數(shù),它并不是一個類,最終返回的對象也不是一個實例,只是為了便于理解習慣這么說罷了。

一個比較容易忽略的會綁定 this 指向 的方法就是使用 new。當我們 new 一個函數(shù)時,就會自動把 this 綁定在新對象上,然后再調(diào)用這個函數(shù)。new 會覆蓋 bind 的綁定讓其無法生效。

那么 new 對函數(shù)到底起到什么作用呢,大致分為三步:

1. 創(chuàng)建一個空的簡單 JavaScript 對象(即{});

2. 為步驟 1 新創(chuàng)建的對象添加屬性__proto__,將該屬性鏈接至構(gòu)造函數(shù)的原型對象 ;

3. 將步驟 1 新創(chuàng)建的對象作為 this 的上下文 ;

4. 如果該函數(shù)沒有返回對象,則返回 this。

這個過程我們稱之為構(gòu)造調(diào)用。

???? 那么 new 在構(gòu)造調(diào)用時對 this 產(chǎn)生什么影響呢?請看栗子:

{
  function log() {
    console.log(this);
  }
  log(); // window{}
  new log(); // log{}

  var boss1 = { name: 'boss1' };
  log.call(boss1); // boss1{}
  new log.call(boss1); // Uncaught TypeError: log.call is not a constructor
  new log.bind(boss1); // Uncaught TypeError: log.call is not a constructor

  var logBind = log.bind(boss1);
  logBind(); // boss1{}
  new logBind(); // log{}
}


當 new 一個函數(shù)時,this 會被綁定為函數(shù)本身,即使函數(shù)在 bind 改變 this 指向的情況下,關(guān)鍵字 new 依舊會將 this 指向為函數(shù)本身。且 new 綁定與顯式綁定互不兼容。

軍令如山,箭頭函數(shù)

ES6 的箭頭函數(shù)是另類的存在,為什么要單獨說呢,這是因為箭頭函數(shù)中的 this 不適用上面介紹的幾種綁定規(guī)則。

準確來說,箭頭函數(shù)中沒有 this,箭頭函數(shù)的 this 指向取決于外層作用域中的 this,外層作用域或函數(shù)的 this 指向誰,箭頭函數(shù)中的 this 便指向誰。

因為箭頭函數(shù)里的 this 是永遠指向到當前詞法作用域(Lexical this)之中 ,在代碼編碼時就可以確定。沒有其它 this 綁定方式可以覆蓋。

這樣的好處就是方便讓回調(diào)函數(shù)的 this 使用當前的作用域,不怕引起混淆。

所以對于箭頭函數(shù),只要看它在哪里創(chuàng)建的就行。

???? 有點吃軟飯的嫌疑,一點都不硬朗,我們來看看栗子:

{
  function fn() {
    return () => {
      console.log('efnArrow: ', this);
    };
  }
  function callback(cb) {
    cb();
  }
  var boss1 = {
    name: 'boss1',
    fn() {
      console.log('fn: ', this);
    },
    fnArrow: () => {
      console.log('fnArrow: ', this);
    },
    ret() {
      return function() {
        console.log('ret: ', this);
      };
    },
    retArrow() {
      return () => {
        console.log('retArrow: ', this);
      };
    },
    cb() {
      callback(function() {
        console.log('cb: ', this);
      });
    },
    cbArrow() {
      callback(() => {
        console.log('cbArrow: ', this);
      });
    },
  };
  var boss2 = {
    name: 'boss2',
    fn: boss1.retArrow,
  };

  boss1.fn(); // fn: boss1{}
  boss1.fnArrow(); // fnArrow: window{}
  boss1.ret()(); // ret: window{}
  boss1.retArrow()(); // retArrow: boss1{}
  boss1.cb(); // cb: window{}
  boss1.cbArrow(); // cbArrow: boss1{}

  boss1.fn.call(boss2); // fn: boss2{}
  boss1.fnArrow.call(boss2); // fnArrow: window{}
  boss1.ret.call(boss2)(); // ret: window{}
  boss1.retArrow.call(boss2)(); // retArrow: boss2{}
  boss1.ret().call(boss2); // ret: boss2{}
  boss1.retArrow().call(boss2); // retArrow: boss1{}
  boss1.cb.call(boss2); // cb: window{}
  boss1.cbArrow.call(boss2); // cbArrow: boss2{}

  var bar = boss1.retArrow.call(boss2);
  bar(); // returnArrowLog: boss2{}
  bar.call(boss1); // returnArrowLog: boss2{}
  bar.bind(boss1)(); // returnArrowLog: boss2{}
}

對 boss1.retArrow 為啥我們第一次綁定 this 并返回箭頭函數(shù)后,再次改變 this 指向沒生效呢?

前面說了,箭頭函數(shù)的 this 取決于外層作用域的 this,boss1.retArrow 函數(shù)執(zhí)行時 this 指向了 boss1,所以箭頭函數(shù)的 this 也指向 boss1。除此之外,箭頭函數(shù) this 還有一個特性,那就是一旦箭頭函數(shù)的 this 綁定成功,也無法被再次修改,有點硬綁定的意思。

當然,箭頭函數(shù)的 this 也不是真的無法修改,我們知道箭頭函數(shù)的 this 就像作用域繼承一樣從上層作用域找,因此我們可以修改外層函數(shù) this 指向達到間接修改箭頭函數(shù) this 的目的,如 boss1.retArrow.call(boss2)()成功將 this 指向 boss2。

this 綁定優(yōu)先級

前面已經(jīng)介紹了幾種 this 綁定規(guī)則,那么問題來了,如果一個函數(shù)調(diào)用存在多種綁定方法,this 最終指向誰呢?這里直接先上答案。

this 綁定優(yōu)先級:

• 顯式綁定 > 隱式綁定 > 默認綁定

• new 綁定 > 隱式綁定 > 默認綁定

為什么顯式綁定不和 new 綁定比較呢?因為不存在這種綁定同時生效的情景,如果同時寫這兩種代碼會直接拋錯,所以大家只用記住上面的規(guī)律即可。

以上就是“JavaScript之this指向?qū)嵗治觥边@篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI