欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

你可能不知道的JavaScript之this指向詳解

 更新時間:2022年05月10日 09:20:51   作者:架構(gòu)科學(xué)  
理解this之前,先糾正一個觀點(diǎn),this?既不指向函數(shù)自身,也不指函數(shù)的詞法作用域,下面這篇文章主要給大家介紹了關(guān)于JavaScript之this指向的相關(guān)資料,需要的朋友可以參考下

前言

JavaScript 對 this 指向 話題 的理解是永不過時的,鑒于 JavaScript 中 this 風(fēng)騷的運(yùn)作方式,本文將試圖將其拆解分析,烹飪再食用~。

this is all about context.,大概意思就是:this 與當(dāng)前執(zhí)行上下文相關(guān)。

this 說白了就是找離自己最近的對象,即擁有當(dāng)前上下文(context)的對象(context object)。

換句話說,this 與函數(shù)被調(diào)用時,調(diào)用函數(shù)的對象有關(guān)。

默認(rèn)綁定,全局對象

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

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

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

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

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

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

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

在嚴(yán)格模式下:

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

?? 在非嚴(yán)格模式下

{
  /* 在非嚴(yán)格模式下,this默認(rèn)綁定 */
  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。

但須注意的是,在嚴(yán)格模式下,默認(rèn)綁定下的 this 會指向 undefined。

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

{
  /* 在非嚴(yán)格模式下,this默認(rèn)綁定 */
  var mode = '在非嚴(yán)格模式下,this默認(rèn)綁定';
  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:  在非嚴(yán)格模式下,this默認(rèn)綁定
// fnInner:  Window{}
// fnInner:  在非嚴(yán)格模式下,this默認(rèn)綁定
// windowStrictThis:  undefined
// windowStrictThis:  TypeError: Cannot read property 'mode' of undefined

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

點(diǎn)石成金,隱式綁定

什么是隱式綁定呢?

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

所以隱式綁定的關(guān)鍵點(diǎn)在于函數(shù)被調(diào)用的對象是誰,說白了就是找調(diào)用這個函數(shù)前面的點(diǎn).是誰,誰就是 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 屬性,當(dāng) boss2 上不存在 mode 屬性時則返回 undefined。注意不要與作用域鏈混淆了。

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

{
? 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ū)別,基本這里就不做解析了,請各自查漏補(bǔ)缺~。

隱式綁定丟失

相信細(xì)心的同學(xué)已經(jīng)發(fā)現(xiàn),上述例子有一個函數(shù)賦值給變量再調(diào)用的情景。當(dāng)函數(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ù)傳遞進(jìn) 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 指向。

這里我將顯式綁定細(xì)分為:

• 顯式綁定:在運(yùn)行時改變 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 中,當(dāng)調(diào)用一個函數(shù)時,我們習(xí)慣稱之為函數(shù)調(diào)用,此時函數(shù)處于一個被動的狀態(tài);而 bind、 call 與 apply 讓函數(shù)從被動變主動,函數(shù)能主動選擇自己的上下文,所以這種寫法我們又稱之為函數(shù)應(yīng)用。

注意,如果在使用 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ù)通常會導(dǎo)致執(zhí)行包裝函數(shù)。

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

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

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

一個比較容易忽略的會綁定 this 指向 的方法就是使用 new。當(dāng)我們 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{}
}


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

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

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

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

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

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

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

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

{
? 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 綁定成功,也無法被再次修改,有點(diǎn)硬綁定的意思。

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

this 綁定優(yōu)先級

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

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

• 顯式綁定 > 隱式綁定 > 默認(rèn)綁定

• new 綁定 > 隱式綁定 > 默認(rèn)綁定

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

總結(jié)

文章到這里,對于 this 的幾種綁定場景就全部總結(jié)完畢了,如果你有跟著例子一起練習(xí)下來,相信聰明的你現(xiàn)在對于 this 的理解一定更上一層樓了。

那么通過本文,我們學(xué)習(xí)了默認(rèn)綁定在嚴(yán)格模式與非嚴(yán)格模式下 this 指向會有所不同。

也知道了隱式綁定的觸發(fā)條件與隱式丟失的幾種情況。

相對于隱式綁定的不可見,我們還學(xué)習(xí)到顯式綁定以及硬綁定,并提到非嚴(yán)格模式下,當(dāng)綁定指向為 null 或 undefined 時 this 會指向全局。

接著重新認(rèn)識了 new 綁定。

最后我們了解了不太合群的箭頭函數(shù)中的 this 綁定,了解到箭頭函數(shù)的 this 由外層作用域的 this 指向決定,并有一旦綁定成功也無法再修改的特性。

好了,內(nèi)容如有不妥之處請指正,希望你能受益良多~

相關(guān)文獻(xiàn)

• MDN-this[1]

• MDN-Function.prototype.bind()[2]

• MDN-Function.prototype.call()[3]

• MDN-Function.prototype.apply()[4]

• MDN-new 運(yùn)算符[5]

• MDN-箭頭函數(shù)[6]

引用鏈接

[1] MDN-this: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this

[2] MDN-Function.prototype.bind(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

[3] MDN-Function.prototype.call(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call

[4] MDN-Function.prototype.apply(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

[5] MDN-new 運(yùn)算符: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new

[6] MDN-箭頭函數(shù): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions

到此這篇關(guān)于你可能不知道的JavaScript之this指向詳解的文章就介紹到這了,更多相關(guān)JS this指向內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論