溫馨提示×

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

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

JS 中 this 在各個(gè)場(chǎng)景下的指向

發(fā)布時(shí)間:2020-07-03 14:47:13 來源:網(wǎng)絡(luò) 閱讀:377 作者:可樂程序員 欄目:web開發(fā)

1. this 的奧秘

很多時(shí)候, JS 中的 this 對(duì)于咱們的初學(xué)者很容易產(chǎn)生困惑不解。 this 的功能很強(qiáng)大,但需要一定付出才能慢慢理解它。

對(duì)Java、PHP或其他標(biāo)準(zhǔn)語言來看,this 表示類方法中當(dāng)前對(duì)象的實(shí)例。大多數(shù)情況下,this 不能在方法之外使用,這樣就比較不會(huì)造成混淆。

在J要中情況就有所不同: this表示函數(shù)的當(dāng)前執(zhí)行上下文,JS 中函數(shù)調(diào)用主要有以下幾種方式:

  • 函數(shù)調(diào)用: alert('Hello World!')

  • 方法調(diào)用: console.log('Hello World!')

  • 構(gòu)造函數(shù): new RegExp('\\d')

  • 隱式調(diào)用: alert.call(undefined, 'Hello World!')

每種調(diào)用類型以自己的方式定義上下文,所以就很容易產(chǎn)生混淆。

此外,嚴(yán)格模式也會(huì)影響執(zhí)行上下文。

理解this關(guān)鍵是要清楚的知道函數(shù)調(diào)用及其如何影響上下文。

本文主要說明函數(shù)的調(diào)用方式及如何影響 this,并且說明執(zhí)行上下文的常見陷阱。

在開始之前,先知道幾個(gè)術(shù)語:

調(diào)用函數(shù)正在執(zhí)行創(chuàng)建函數(shù)體的代碼,或者只是調(diào)用函數(shù)。 例如,parseInt函數(shù)調(diào)用是parseInt('15')。

  • 函數(shù)調(diào)用:執(zhí)行構(gòu)成函數(shù)主體的代碼:例如,parseInt函數(shù)調(diào)用是parseInt('15')。

  • 調(diào)用的上下文:指 this 在函數(shù)體內(nèi)的值。 例如,map.set('key', 'value')的調(diào)用上下文是 map。

  • 函數(shù)的作用域:是在函數(shù)體中可訪問的變量、對(duì)象和函數(shù)的集合。

2.函數(shù)調(diào)用

當(dāng)一個(gè)表達(dá)式為函數(shù)接著一個(gè)(,一些用逗號(hào)分隔的參數(shù)以及一個(gè))時(shí),函數(shù)調(diào)用被執(zhí)行,例如parseInt('18')。

函數(shù)調(diào)用表達(dá)式不能是屬性方式的調(diào)用,如 obj.myFunc(),這種是創(chuàng)建一個(gè)方法調(diào)用。再如 [1,5].join(',')不是函數(shù)調(diào)用,而是方法調(diào)用,這種區(qū)別需要記住哈,很重要滴

函數(shù)調(diào)用的一個(gè)簡(jiǎn)單示例:

function?hello(name)?{?return?'Hello?'?+?name?+?'!';
}//?函數(shù)調(diào)用const?message?=?hello('World');console.log(message);?//?=>?'Hello?World!'復(fù)制代碼

hello('World')是函數(shù)調(diào)用: hello表達(dá)式等價(jià)于一個(gè)函數(shù),跟在它后面的是一對(duì)括號(hào)以及'World'參數(shù)。

一個(gè)更高級(jí)的例子是IIFE(立即調(diào)用的函數(shù)表達(dá)式)

const?message?=?(function(name)?{
?return?'Hello?'?+?name?+?'!';
})('World');console.log(message)?//?=>?'Hello?World!'復(fù)制代碼

IIFE也是一個(gè)函數(shù)調(diào)用:第一對(duì)圓括號(hào)(function(name) {...})是一個(gè)表達(dá)式,它的計(jì)算結(jié)果是一個(gè)函數(shù)對(duì)象,后面跟著一對(duì)圓括號(hào),圓括號(hào)的參數(shù)是“World”。

2.1. 在函數(shù)調(diào)用中的this

this 在函數(shù)調(diào)用中是一個(gè)全局對(duì)象

局對(duì)象由執(zhí)行環(huán)境決定。在瀏覽器中,this是 window 對(duì)象。

JS 中 this 在各個(gè)場(chǎng)景下的指向


在函數(shù)調(diào)用中,執(zhí)行上下文是全局對(duì)象。

再來看看下面函數(shù)中的上下文又是什么鬼:

function?sum(a,?b)?{?console.log(this?===?window);?//?=>?true
?this.myNumber?=?20;?//?將'myNumber'屬性添加到全局對(duì)象
?return?a?+?b;
}//?sum()?is?invoked?as?a?function//?sum()?中的?`this`?是一個(gè)全局對(duì)象(window)sum(15,?16);?//?=>?31window.myNumber;?//?=>?20復(fù)制代碼

在調(diào)用sum(15,16)時(shí),JS 自動(dòng)將this設(shè)置為全局對(duì)象,在瀏覽器中該對(duì)象是window。

當(dāng)this在任何函數(shù)作用域(最頂層作用域:全局執(zhí)行上下文)之外使用,this 表示 window 對(duì)象

console.log(this?===?window);?//?=>?true
this.myString?=?'Hello?World!';
console.log(window.myString);?//?=>?'Hello?World!'<!--?In?an?html?file?--><script?type="text/javascript">
?console.log(this?===?window);?//?=>?true</script>復(fù)制代碼

2.2 嚴(yán)格模式下的函數(shù)調(diào)用 this 又是什么樣的

this 在嚴(yán)格模式下的函數(shù)調(diào)用中為?undefined

嚴(yán)格模式是在 ECMAScript 5.1中引入的,它提供了更好的安全性和更強(qiáng)的錯(cuò)誤檢查。

要啟用嚴(yán)格模式,函數(shù)頭部寫入use strict 即可。

啟用后,嚴(yán)格模式會(huì)影響執(zhí)行上下文,this 在常規(guī)函數(shù)調(diào)用中值為undefined。 與上述情況2.1相反,執(zhí)行上下文不再是全局對(duì)象。

JS 中 this 在各個(gè)場(chǎng)景下的指向


嚴(yán)格模式函數(shù)調(diào)用示例:

function?multiply(a,?b)?{?'use?strict';?//?啟用嚴(yán)格模式
?console.log(this?===?undefined);?//?=>?true
?return?a?*?b;
}
multiply(2,?5);?//?=>?10復(fù)制代碼

當(dāng)multiply(2,5)作為函數(shù)調(diào)用時(shí),this是undefined。

嚴(yán)格模式不僅在當(dāng)前作用域中有效,在內(nèi)部作用域中也是有效的(對(duì)于在內(nèi)部聲明的所有函數(shù)):

function?execute()?{?'use?strict';?//?開啟嚴(yán)格模式?
?function?concat(str1,?str2)?{?//?嚴(yán)格模式仍然有效?
?console.log(this?===?undefined);?//?=>?true
?return?str1?+?str2;
?}?//?concat()?在嚴(yán)格模式下作為函數(shù)調(diào)用
?//?this?in?concat()?is?undefined
?concat('Hello',?'?World!');?//?=>?"Hello?World!"}
execute();
復(fù)制代碼

'use strict'被插入到執(zhí)行體的頂部,在其作用域內(nèi)啟用嚴(yán)格模式。 因?yàn)楹瘮?shù)concat是在執(zhí)行的作用域中聲明的,所以它繼承了嚴(yán)格模式。

單個(gè)JS文件可能包含嚴(yán)格和非嚴(yán)格模式。 因此,對(duì)于相同的調(diào)用類型,可以在單個(gè)腳本中具有不同的上下文行為:

function?nonStrictSum(a,?b)?{?//?非嚴(yán)格模式
?console.log(this?===?window);?//?=>?true
?return?a?+?b;
}function?strictSum(a,?b)?{?'use?strict';?//?啟用嚴(yán)格模式
?console.log(this?===?undefined);?//?=>?true
?return?a?+?b;
}
nonStrictSum(5,?6);?//?=>?11strictSum(8,?12);?//?=>?20復(fù)制代碼

2.3 陷阱:this 在內(nèi)部函數(shù)中的時(shí)候

函數(shù)調(diào)用的一個(gè)常見陷阱是,認(rèn)為this在內(nèi)部函數(shù)中的情況與外部函數(shù)中的情況相同。

正確地說,內(nèi)部函數(shù)的上下文只依賴于它的調(diào)用類型,而不依賴于外部函數(shù)的上下文。

要將 this 設(shè)置為所需的值,可以通過 .call()或.apply()修改內(nèi)部函數(shù)的上下文或使用.bind()創(chuàng)建綁定函數(shù)。

下面的例子是計(jì)算兩個(gè)數(shù)的和:

const?numbers?=?{?numberA:?5,?numberB:?10,?sum:?function()?{?console.log(this?===?numbers);?//?=>?true
?function?calculate()?{?console.log(this?===?numbers);?//?=>?false
?return?this.numberA?+?this.numberB;
?}?return?calculate();
?}
};
numbers.sum();?//?=>?NaN?復(fù)制代碼

sum()是對(duì)象上的方法調(diào)用,所以sum中的上下文是numbers對(duì)象。calculate函數(shù)是在sum中定義的,你可能希望在calculate()中this也表示number對(duì)象。

calculate()是一個(gè)函數(shù)調(diào)用(不是方法調(diào)用),它將this作為全局對(duì)象window(非嚴(yán)格模下)。即使外部函數(shù)sum將上下文作為number對(duì)象,它在calculate里面沒有影響。

sum()的調(diào)用結(jié)果是NaN,不是預(yù)期的結(jié)果5 + 10 = 15,這都是因?yàn)闆]有正確調(diào)用calculate。

為了解決這個(gè)問題,calculate函數(shù)中上下文應(yīng)該與 sum 中的一樣,以便可以訪問numberA和numberB屬性。

一種解決方案是通過調(diào)用calculator.call(this)手動(dòng)將calculate上下文更改為所需的上下文。

const?numbers?=?{?numberA:?5,?numberB:?10,?sum:?function()?{?console.log(this?===?numbers);?//?=>?true
?function?calculate()?{?console.log(this?===?numbers);?//?=>?true
?return?this.numberA?+?this.numberB;
?}?//?使用?.call()?方法修改上下文
?return?calculate.call(this);
?}
};
numbers.sum();?//?=>?15復(fù)制代碼

call(this)像往常一樣執(zhí)行calculate函數(shù),但 call 會(huì)把上下文修改為指定為第一個(gè)參數(shù)的值。

現(xiàn)在this.numberA + this.numberB相當(dāng)于numbers.numberA + numbers.numberB。 該函數(shù)返回預(yù)期結(jié)果5 + 10 = 15。

另一種就是使用箭頭函數(shù)

const?numbers?=?{?numberA:?5,?numberB:?10,?sum:?function()?{?console.log(this?===?numbers);?//?=>?true
?const?calculate?=?()?=>?{?console.log(this?===?numbers);?//?=>?true
?return?this.numberA?+?this.numberB;
?}?return?calculate();
?}
};
numbers.sum();?//?=>?15復(fù)制代碼

3.方法調(diào)用

方法是存儲(chǔ)在對(duì)象屬性中的函數(shù)。例如

const?myObject?=?{?//?helloFunction?是一個(gè)方法
?helloFunction:?function()?{?return?'Hello?World!';
?}
};const?message?=?myObject.helloFunction();
復(fù)制代碼

helloFunction是myObject的一個(gè)方法,要調(diào)用該方法,可以這樣子調(diào)用 :myObject.helloFunction。

當(dāng)一個(gè)表達(dá)式以屬性訪問的形式執(zhí)行時(shí),執(zhí)行的是方法調(diào)用,它相當(dāng)于以個(gè)函數(shù)接著(,一組用逗號(hào)分隔的參數(shù)以及)。

利用前面的例子,myObject.helloFunction()是對(duì)象myObject上的一個(gè)helloFunction的方法調(diào)用。[1, 2].join(',') 或/\s/.test('beautiful world')也被認(rèn)為是方法調(diào)用。

區(qū)分函數(shù)調(diào)用和方法調(diào)用非常重要,因?yàn)樗鼈兪遣煌念愋?。主要區(qū)別在于方法調(diào)用需要一個(gè)屬性訪問器形式來調(diào)用函數(shù)(obj.myFunc()或obj['myFunc']()),而函數(shù)調(diào)用不需要(myFunc())。

['Hello',?'World'].join(',?');?//?方法調(diào)用({?ten:?function()?{?return?10;?}?}).ten();?//?方法調(diào)用const?obj?=?{};
obj.myFunction?=?function()?{?return?new?Date().toString();
};
obj.myFunction();?//?方法調(diào)用const?otherFunction?=?obj.myFunction;
otherFunction();?//?函數(shù)調(diào)用parseFloat('16.60');?//?函數(shù)調(diào)用isNaN(0);?//?函數(shù)調(diào)用復(fù)制代碼

理解函數(shù)調(diào)用和方法調(diào)用之間的區(qū)別有助于正確識(shí)別上下文。

3.1 方法調(diào)用中 this 是腫么樣

在方法調(diào)用中,this是擁有這個(gè)方法的對(duì)象

當(dāng)調(diào)用對(duì)象上的方法時(shí),this就變成了對(duì)象本身。

JS 中 this 在各個(gè)場(chǎng)景下的指向


創(chuàng)建一個(gè)對(duì)象,該對(duì)象有一個(gè)遞增數(shù)字的方法

const?calc?=?{?num:?0,?increment:?function()?{?console.log(this?===?calc);?//?=>?true
?this.num?+=?1;?return?this.num;
?}
};//?method?invocation.?this?is?calccalc.increment();?//?=>?1calc.increment();?//?=>?2復(fù)制代碼

調(diào)用calc.increment()使increment函數(shù)的上下文成為calc對(duì)象。所以使用this.num來增加num屬性是有效的。

再來看看另一個(gè)例子。JS對(duì)象從原型繼承一個(gè)方法,當(dāng)在對(duì)象上調(diào)用繼承的方法時(shí),調(diào)用的上下文仍然是對(duì)象本身

const?myDog?=?Object.create({?sayName:?function()?{?console.log(this?===?myDog);?//?=>?true
?return?this.name;
?}
});
myDog.name?=?'Milo';//?方法調(diào)用?this?指向?myDogmyDog.sayName();?//?=>?'Milo'復(fù)制代碼

Object.create()創(chuàng)建一個(gè)新對(duì)象myDog,并根據(jù)第一個(gè)參數(shù)設(shè)置其原型。myDog對(duì)象繼承sayName方法。

執(zhí)行myDog. sayname()時(shí),myDog是調(diào)用的上下文。

在EC6 class 語法中,方法調(diào)用上下文也是實(shí)例本身

class?Planet?{?constructor(name)?{?this.name?=?name;?
?}
?getName()?{
?console.log(this?===?earth);?//?=>?true
?return?this.name;
?}
}var?earth?=?new?Planet('Earth');//?method?invocation.?the?context?is?earthearth.getName();?//?=>?'Earth'復(fù)制代碼

3.2 陷阱:將方法與其對(duì)象分離

方法可以從對(duì)象中提取到一個(gè)單獨(dú)的變量const alone = myObj.myMethod。當(dāng)方法單獨(dú)調(diào)用時(shí),與原始對(duì)象alone()分離,你可能認(rèn)為當(dāng)前的this就是定義方法的對(duì)象myObject。

如果方法在沒有對(duì)象的情況下調(diào)用,那么函數(shù)調(diào)用就會(huì)發(fā)生,此時(shí)的this指向全局對(duì)象window嚴(yán)格模式下是undefined。

下面的示例定義了Animal構(gòu)造函數(shù)并創(chuàng)建了它的一個(gè)實(shí)例:myCat。然后setTimout()在1秒后打印myCat對(duì)象信息

function?Animal(type,?legs)?{?this.type?=?type;?this.legs?=?legs;?
?this.logInfo?=?function()?{?console.log(this?===?myCat);?//?=>?false
?console.log('The?'?+?this.type?+?'?has?'?+?this.legs?+?'?legs');
?}
}const?myCat?=?new?Animal('Cat',?4);//?The?undefined?has?undefined?legs?setTimeout(myCat.logInfo,?1000);?
復(fù)制代碼

你可能認(rèn)為setTimout調(diào)用myCat.loginfo()時(shí),它應(yīng)該打印關(guān)于myCat對(duì)象的信息。

不幸的是,方法在作為參數(shù)傳遞時(shí)與對(duì)象是分離,setTimout(myCat.logInfo)以下情況是等效的:

setTimout(myCat.logInfo);
//?等價(jià)于
const?extractedLogInfo?=?myCat.logInfo;setTimout(extractedLogInfo);
復(fù)制代碼

將分離的logInfo作為函數(shù)調(diào)用時(shí),this是全局 window,所以對(duì)象信息沒有正確地打印。

函數(shù)可以使用.bind()方法與對(duì)象綁定,就可以解決 this 指向的問題。

function?Animal(type,?legs)?{?this.type?=?type;?this.legs?=?legs;?
?this.logInfo?=?function()?{?console.log(this?===?myCat);?//?=>?true
?console.log('The?'?+?this.type?+?'?has?'?+?this.legs?+?'?legs');
?};
}const?myCat?=?new?Animal('Cat',?4);//?logs?"The?Cat?has?4?legs"setTimeout(myCat.logInfo.bind(myCat),?1000);
復(fù)制代碼

myCat.logInfo.bind(myCat)返回一個(gè)新函數(shù),它的執(zhí)行方式與logInfo完全相同,但是此時(shí)的 this 指向 myCat,即使在函數(shù)調(diào)用中也是如此。

另一種解決方案是將logInfo()方法定義為一個(gè)箭頭函數(shù):

function?Animal(type,?legs)?{?this.type?=?type;?this.legs?=?legs;?
?this.logInfo?=?()?=>?{?console.log(this?===?myCat);?//?=>?true
?console.log('The?'?+?this.type?+?'?has?'?+?this.legs?+?'?legs');
?};
}const?myCat?=?new?Animal('Cat',?4);//?logs?"The?Cat?has?4?legs"setTimeout(myCat.logInfo,?1000);
復(fù)制代碼

4. 構(gòu)造函數(shù)調(diào)用

當(dāng)new關(guān)鍵詞緊接著函數(shù)對(duì)象,(,一組逗號(hào)分隔的參數(shù)以及)時(shí)被調(diào)用,執(zhí)行的是構(gòu)造函數(shù)調(diào)用如new RegExp('\\d')。

聲明了一個(gè)Country函數(shù),并且將它作為一個(gè)構(gòu)造函數(shù)調(diào)用:

function?Country(name,?traveled)?{?this.name?=?name???name?:?'United?Kingdom';?this.traveled?=?Boolean(traveled);?
}
Country.prototype.travel?=?function()?{?this.traveled?=?true;
};//?構(gòu)造函數(shù)調(diào)用const?france?=?new?Country('France',?false);//?構(gòu)造函數(shù)調(diào)用const?unitedKingdom?=?new?Country;
france.travel();?//?Travel?to?France復(fù)制代碼

new Country('France', false)是Country函數(shù)的構(gòu)造函數(shù)調(diào)用。它的執(zhí)行結(jié)果是一個(gè)name屬性為'France'的新的對(duì)象。 如果這個(gè)構(gòu)造函數(shù)調(diào)用時(shí)不需要參數(shù),那么括號(hào)可以省略:new Country。

從ES6開始,JS 允許用class關(guān)鍵詞來定義構(gòu)造函數(shù)

class?City?{?constructor(name,?traveled)?{?this.name?=?name;?this.traveled?=?false;
?}
?travel()?{?this.traveled?=?true;
?}
}//?Constructor?invocationconst?paris?=?new?City('Paris',?false);
paris.travel();
復(fù)制代碼

new City('Paris')是構(gòu)造函數(shù)調(diào)用。這個(gè)對(duì)象的初始化由這個(gè)類中一個(gè)特殊的方法constructor來處理。其中,this指向新創(chuàng)建的對(duì)象。

構(gòu)造函數(shù)創(chuàng)建了一個(gè)新的空的對(duì)象,它從構(gòu)造函數(shù)的原型繼承了屬性。構(gòu)造函數(shù)的作用就是去初始化這個(gè)對(duì)象。 可能你已經(jīng)知道了,在這種類型的調(diào)用中,上下文指向新創(chuàng)建的實(shí)例。

當(dāng)屬性訪問myObject.myFunction前面有一個(gè)new關(guān)鍵詞時(shí),JS會(huì)執(zhí)行構(gòu)造函數(shù)調(diào)用而不是原來的方法調(diào)用。

例如new myObject.myFunction():它相當(dāng)于先用屬性訪問把方法提取出來extractedFunction = myObject.myFunction,然后利用把它作為構(gòu)造函數(shù)創(chuàng)建一個(gè)新的對(duì)象: new extractedFunction()。

4.1. 構(gòu)造函數(shù)中的 this

在構(gòu)造函數(shù)調(diào)用中 this 指向新創(chuàng)建的對(duì)象

構(gòu)造函數(shù)調(diào)用的上下文是新創(chuàng)建的對(duì)象。它利用構(gòu)造函數(shù)的參數(shù)初始化新的對(duì)象,設(shè)定屬性的初始值,添加事件處理函數(shù)等等。

JS 中 this 在各個(gè)場(chǎng)景下的指向


來看看下面示例中的上下文

function?Foo?()?{?console.log(this?instanceof?Foo);?//?=>?true
?this.property?=?'Default?Value';
}//?Constructor?invocationconst?fooInstance?=?new?Foo();
fooInstance.property;?//?=>?'Default?Value'復(fù)制代碼

new Foo() 正在進(jìn)行構(gòu)造函數(shù)調(diào)用,其中上下文是fooInstance。 在Foo內(nèi)部初始化對(duì)象:this.property被賦值為默認(rèn)值。

同樣的情況在用class語法(從ES6起)時(shí)也會(huì)發(fā)生,唯一的區(qū)別是初始化在constructor方法中進(jìn)行:

class?Bar?{?constructor()?{?console.log(this?instanceof?Bar);?//?=>?true
?this.property?=?'Default?Value';
?}
}//?Constructor?invocationconst?barInstance?=?new?Bar();
barInstance.property;?//?=>?'Default?Value'復(fù)制代碼

4.2. 陷阱: 忘了使用 new

有些JS函數(shù)不是只在作為構(gòu)造函數(shù)調(diào)用的時(shí)候才創(chuàng)建新的對(duì)象,作為函數(shù)調(diào)用時(shí)也會(huì),例如RegExp:

var?reg1?=?new?RegExp('\\w+');var?reg2?=?RegExp('\\w+');
reg1?instanceof?RegExp;?//?=>?truereg2?instanceof?RegExp;?//?=>?truereg1.source?===?reg2.source;?//?=>?true復(fù)制代碼

當(dāng)執(zhí)行的 new RegExp('\\w+')和RegExp('\\w+')時(shí),JS 會(huì)創(chuàng)建等價(jià)的正則表達(dá)式對(duì)象。

使用函數(shù)調(diào)用來創(chuàng)建對(duì)象存在一個(gè)潛在的問題(不包括工廠模式),因?yàn)橐恍?gòu)造函數(shù)可能會(huì)忽略在缺少new關(guān)鍵字時(shí)初始化對(duì)象的邏輯。

下面的例子說明了這個(gè)問題:

function?Vehicle(type,?wheelsCount)?{?this.type?=?type;?this.wheelsCount?=?wheelsCount;?return?this;
}//?忘記使用?new?const?car?=?Vehicle('Car',?4);
car.type;?//?=>?'Car'car.wheelsCount?//?=>?4car?===?window?//?=>?true復(fù)制代碼

Vehicle是一個(gè)在上下文對(duì)象上設(shè)置type和wheelsCount屬性的函數(shù)。

當(dāng)執(zhí)行Vehicle('Car', 4)時(shí),返回一個(gè)對(duì)象Car,它具有正確的屬性:Car.type 為 Car和Car.wheelsCount 為4,你可能認(rèn)為它很適合創(chuàng)建和初始化新對(duì)象。

然而,在函數(shù)調(diào)用中,this是window對(duì)象 ,因此 Vehicle('Car',4)在 window 對(duì)象上設(shè)置屬性。 顯然這是錯(cuò)誤,它并沒有創(chuàng)建新對(duì)象。

當(dāng)你希望調(diào)用構(gòu)造函數(shù)時(shí),確保你使用了new操作符:

function?Vehicle(type,?wheelsCount)?{?if?(!(this?instanceof?Vehicle))?{?throw?Error('Error:?Incorrect?invocation');
?}?this.type?=?type;?this.wheelsCount?=?wheelsCount;?return?this;
}//?Constructor?invocationconst?car?=?new?Vehicle('Car',?4);
car.type?//?=>?'Car'car.wheelsCount?//?=>?4car?instanceof?Vehicle?//?=>?true//?Function?invocation.?Throws?an?error.const?brokenCar?=?Vehicle('Broken?Car',?3);
復(fù)制代碼

new Vehicle('Car',4) 運(yùn)行正常:創(chuàng)建并初始化一個(gè)新對(duì)象,因?yàn)闃?gòu)造函數(shù)調(diào)用中時(shí)使用了new關(guān)鍵字。

在構(gòu)造函數(shù)里添加了一個(gè)驗(yàn)證this instanceof Vehicle來確保執(zhí)行的上下文是正確的對(duì)象類型。如果this不是Vehicle,那么就會(huì)報(bào)錯(cuò)。這樣,如果執(zhí)行Vehicle('Broken Car', 3)(沒有new),我們會(huì)得到一個(gè)異常:Error: Incorrect invocation。

5. 隱式調(diào)用

使用myFun.call()或myFun.apply()方法調(diào)用函數(shù)時(shí),執(zhí)行的是隱式調(diào)用。

JS中的函數(shù)是第一類對(duì)象,這意味著函數(shù)就是對(duì)象,對(duì)象的類型為Function。從函數(shù)對(duì)象的方法列表中,.call()和.apply()用于調(diào)用具有可配置上下文的函數(shù)。

  • 方法 .call(thisArg[, arg1[, arg2[, ...]]])將接受的第一個(gè)參數(shù)thisArg作為調(diào)用時(shí)的上下文,arg1, arg2, ...這些則作為參數(shù)傳入被調(diào)用的函數(shù)。

  • 方法.apply(thisArg, [args])將接受的第一個(gè)參數(shù)thisArg作為調(diào)用時(shí)的上下文,并且接受另一個(gè)類似數(shù)組的對(duì)象[arg1, arg2, ...]作為被調(diào)用函數(shù)的參數(shù)傳入。

下面是隱式調(diào)用的例子

function?increment(number)?{?return?++number;?
}
increment.call(undefined,?10);?//?=>?11increment.apply(undefined,?[10]);?//?=>?11復(fù)制代碼

increment.call()和increment.apply()都用參數(shù)10調(diào)用了這個(gè)自增函數(shù)。

兩者的區(qū)別是.call()接受一組參數(shù),例如myFunction.call(thisValue, 'value1', 'value2')。而.apply()接受的一組參數(shù)必須是一個(gè)類似數(shù)組的對(duì)象,例如myFunction.apply(thisValue, ['value1', 'value2'])。

5.1. 隱式調(diào)用中的this

在隱式調(diào)用.call()或.apply()中,this是第一個(gè)參數(shù)

很明顯,在隱式調(diào)用中,this作為第一個(gè)參數(shù)傳遞給.call()或.apply()。

var?rabbit?=?{?name:?'White?Rabbit'?};function?concatName(string)?{?console.log(this?===?rabbit);?//?=>?true
?return?string?+?this.name;
}
concatName.call(rabbit,?'Hello?');?//?=>?'Hello?White?Rabbit'concatName.apply(rabbit,?['Bye?']);?//?=>?'Bye?White?Rabbit'復(fù)制代碼

當(dāng)應(yīng)該使用特定上下文執(zhí)行函數(shù)時(shí),隱式調(diào)用非常有用。例如為了解決方法調(diào)用時(shí),this總是window或嚴(yán)格模式下的undefined的上下文問題。隱式調(diào)用可以用于模擬在一個(gè)對(duì)象上調(diào)用某個(gè)方法。

function?Runner(name)?{?console.log(this?instanceof?Rabbit);?//?=>?true
?this.name?=?name;?
}function?Rabbit(name,?countLegs)?{?console.log(this?instanceof?Rabbit);?//?=>?true
?Runner.call(this,?name);?this.countLegs?=?countLegs;
}const?myRabbit?=?new?Rabbit('White?Rabbit',?4);
myRabbit;?//?{?name:?'White?Rabbit',?countLegs:?4?}復(fù)制代碼

Rabbit中的Runner.call(this, name)隱式調(diào)用了父類的函數(shù)來初始化這個(gè)對(duì)象。

6. 綁定函數(shù)

綁定函數(shù)是與對(duì)象連接的函數(shù)。通常使用.bind()方法從原始函數(shù)創(chuàng)建。原始函數(shù)和綁定函數(shù)共享相同的代碼和作用域,但執(zhí)行時(shí)上下文不同。

方法 myFunc.bind(thisArg[, arg1[, arg2[, ...]]])接受第一個(gè)參數(shù)thisArg作為綁定函數(shù)執(zhí)行時(shí)的上下文,并且它接受一組可選的參數(shù) arg1, arg2, ...作為被調(diào)用函數(shù)的參數(shù)。它返回一個(gè)綁定了thisArg的新函數(shù)。

function?multiply(number)?{?'use?strict';?return?this?*?number;
}const?double?=?multiply.bind(2);
double(3);?//?=>?6double(10);?//?=>?20復(fù)制代碼

bind(2)返回一個(gè)新的函數(shù)對(duì)象double,double 綁定了數(shù)字2。multiply和double具有相同的代碼和作用域。

與.apply()和.call() 方法相反,它不會(huì)立即調(diào)用該函數(shù),.bind()方法只返回一個(gè)新函數(shù),在之后被調(diào)用,只是this已經(jīng)被提前設(shè)置好了。

6.1. 綁定函數(shù)中的this

在調(diào)用綁定函數(shù)時(shí),this是.bind()的第一個(gè)參數(shù)。

.bind()的作用是創(chuàng)建一個(gè)新函數(shù),調(diào)用該函數(shù)時(shí),將上下文作為傳遞給.bind()的第一個(gè)參數(shù)。它是一種強(qiáng)大的技術(shù),使咱們可以創(chuàng)建一個(gè)定義了this值的函數(shù)。

JS 中 this 在各個(gè)場(chǎng)景下的指向


來看看,如何在如何在綁定函數(shù)設(shè)置 this

const?numbers?=?{?array:?[3,?5,?10],
?getNumbers:?function()?{?return?this.array;?
?}
};const?boundGetNumbers?=?numbers.getNumbers.bind(numbers);
boundGetNumbers();?//?=>?[3,?5,?10]//?Extract?method?from?objectconst?simpleGetNumbers?=?numbers.getNumbers;
simpleGetNumbers();?//?=>?undefined?(嚴(yán)格模式下報(bào)錯(cuò))復(fù)制代碼

numbers.getNumbers.bind(numbers)返回綁定numbers對(duì)象boundGetNumbers函數(shù)。boundGetNumbers()調(diào)用時(shí)的this是number對(duì)象,并能夠返回正確的數(shù)組對(duì)象。

可以將函數(shù)numbers.getNumbers提取到變量simpleGetNumbers中而不進(jìn)行綁定。在之后的函數(shù)調(diào)用中simpleGetNumbers()的this是window(嚴(yán)格模式下為undefined),不是number對(duì)象。在這個(gè)情況下,simpleGetNumbers()不會(huì)正確返回?cái)?shù)組。

6.2 緊密的上下文綁定

.bind()創(chuàng)建一個(gè)永久的上下文鏈接,并始終保持它。 一個(gè)綁定函數(shù)不能通過.call()或者.apply()來改變它的上下文,甚至是再次綁定也不會(huì)有什么作用。

只有綁定函數(shù)的構(gòu)造函數(shù)調(diào)用才能更改已經(jīng)綁定的上下文,但是很不推薦的做法(構(gòu)造函數(shù)調(diào)用必須使用常規(guī)的非綁定函數(shù))。

下面示例創(chuàng)建一個(gè)綁定函數(shù),然后嘗試更改其已預(yù)先定義好的上下文

function?getThis()?{?'use?strict';?return?this;
}const?one?=?getThis.bind(1);//?綁定函數(shù)調(diào)用one();?//?=>?1//?使用帶有.apply()和.call()的綁定函數(shù)one.call(2);?//?=>?1one.apply(2);?//?=>?1//?再次綁定one.bind(2)();?//?=>?1//?以構(gòu)造函數(shù)的形式調(diào)用綁定函數(shù)new?one();?//?=>?Object復(fù)制代碼

只有new one()改變了綁定函數(shù)的上下文,其他方式的調(diào)用中this總是等于1。

7. 箭頭函數(shù)

箭頭函數(shù)用于以更短的形式聲明函數(shù),并在詞法上綁定上下文。它可以這樣使用

const?hello?=?(name)?=>?{?return?'Hello?'?+?name;
};
hello('World');?//?=>?'Hello?World'//?Keep?only?even?numbers[1,?2,?5,?6].filter(item?=>?item?%?2?===?0);?//?=>?[2,?6]復(fù)制代碼

箭頭函數(shù)語法簡(jiǎn)單,沒有冗長的function 關(guān)鍵字。當(dāng)箭頭函數(shù)只有一條語句時(shí),甚至可以省略return關(guān)鍵字。

箭頭函數(shù)是匿名的,這意味著name屬性是一個(gè)空字符串''。這樣它就沒有詞法上函數(shù)名(函數(shù)名對(duì)于遞歸、分離事件處理程序非常有用)

同時(shí),跟常規(guī)函數(shù)相反,它也不提供arguments對(duì)象。但是,這在ES6中通過rest parameters修復(fù)了:

const?sumArguments?=?(...args)?=>?{?console.log(typeof?arguments);?//?=>?'undefined'
?return?args.reduce((result,?item)?=>?result?+?item);
};
sumArguments.name?//?=>?''sumArguments(5,?5,?6);?//?=>?16復(fù)制代碼

7.1. 箭頭函數(shù)中的this

this 定義箭頭函數(shù)的封閉上下文

箭頭函數(shù)不會(huì)創(chuàng)建自己的執(zhí)行上下文,而是從定義它的外部函數(shù)中獲取 this。 換句話說,箭頭函數(shù)在詞匯上綁定 this。

JS 中 this 在各個(gè)場(chǎng)景下的指向


下面的例子說明了這個(gè)上下文透明的特性:

class?Point?{?constructor(x,?y)?{?this.x?=?x;?this.y?=?y;
?}
?log()?{?console.log(this?===?myPoint);?//?=>?true
?setTimeout(()=>?{?console.log(this?===?myPoint);?//?=>?true
?console.log(this.x?+?':'?+?this.y);?//?=>?'95:165'
?},?1000);
?}
}const?myPoint?=?new?Point(95,?165);
myPoint.log();
復(fù)制代碼

setTimeout使用與log()方法相同的上下文(myPoint對(duì)象)調(diào)用箭頭函數(shù)。正如所見,箭頭函數(shù)從定義它的函數(shù)繼承上下文。

如果在這個(gè)例子里嘗試用常規(guī)函數(shù),它創(chuàng)建自己的上下文(window或嚴(yán)格模式下的undefined)。因此,要使相同的代碼正確地使用函數(shù)表達(dá)式,需要手動(dòng)綁定上下文:setTimeout(function(){…}.bind(this))。這很冗長,使用箭頭函數(shù)是一種更簡(jiǎn)潔、更短的解決方案。

如果箭頭函數(shù)在最頂層的作用域中定義(在任何函數(shù)之外),則上下文始終是全局對(duì)象(瀏覽器中的 window):

onst?getContext?=?()?=>?{?console.log(this?===?window);?//?=>?true
?return?this;
};console.log(getContext()?===?window);?//?=>?true復(fù)制代碼

箭頭函數(shù)一勞永逸地與詞匯上下文綁定。 即使修改上下文,this也不能被改變:

const?numbers?=?[1,?2];
(function()?{?
?const?get?=?()?=>?{?console.log(this?===?numbers);?//?=>?true
?return?this;
?};?console.log(this?===?numbers);?//?=>?true
?get();?//?=>?[1,?2]
?//?Use?arrow?function?with?.apply()?and?.call()
?get.call([0]);?//?=>?[1,?2]
?get.apply([0]);?//?=>?[1,?2]
?//?Bind
?get.bind([0])();?//?=>?[1,?2]}).call(numbers);
復(fù)制代碼

無論如何調(diào)用箭頭函數(shù)get,它總是保留詞匯上下文numbers。 用其他上下文的隱式調(diào)用(通過 get.call([0])或get.apply([0]))或者重新綁定(通過.bind())都不會(huì)起作用。

箭頭函數(shù)不能用作構(gòu)造函數(shù)。 將它作為構(gòu)造函數(shù)調(diào)用(new get())會(huì)拋出一個(gè)錯(cuò)誤:TypeError: get is not a constructor。

7.2. 陷阱: 用箭頭函數(shù)定義方法

你可能希望使用箭頭函數(shù)來聲明一個(gè)對(duì)象上的方法。箭頭函數(shù)的定義相比于函數(shù)表達(dá)式短得多:(param) => {...} instead of function(param) {..}。

來看看例子,用箭頭函數(shù)在Period類上定義了format()方法:

function?Period?(hours,?minutes)?{?
?this.hours?=?hours;?this.minutes?=?minutes;
}
Period.prototype.format?=?()?=>?{?console.log(this?===?window);?//?=>?true
?return?this.hours?+?'?hours?and?'?+?this.minutes?+?'?minutes';
};const?walkPeriod?=?new?Period(2,?30);?
walkPeriod.format();?//?=>?'undefined?hours?and?undefined?minutes'復(fù)制代碼

由于format是一個(gè)箭頭函數(shù),并且在全局上下文(最頂層的作用域)中定義,因此 this 指向window對(duì)象。

即使format作為方法在一個(gè)對(duì)象上被調(diào)用如walkPeriod.format(),window仍然是這次調(diào)用的上下文。之所以會(huì)這樣是因?yàn)榧^函數(shù)有靜態(tài)的上下文,并不會(huì)隨著調(diào)用方式的改變而改變。

該方法返回'undefined hours和undefined minutes',這不是咱們想要的結(jié)果。

函數(shù)表達(dá)式解決了這個(gè)問題,因?yàn)槌R?guī)函數(shù)確實(shí)能根據(jù)實(shí)際調(diào)用改變它的上下文:

function?Period?(hours,?minutes)?{?
?this.hours?=?hours;?this.minutes?=?minutes;
}
Period.prototype.format?=?function()?{?console.log(this?===?walkPeriod);?//?=>?true
?return?this.hours?+?'?hours?and?'?+?this.minutes?+?'?minutes';
};const?walkPeriod?=?new?Period(2,?30);?
walkPeriod.format();?//?=>?'2?hours?and?30?minutes'復(fù)制代碼

walkPeriod.format()是一個(gè)對(duì)象上的方法調(diào)用,它的上下文是walkPeriod對(duì)象。this.hours等于2,this.minutes等于30,所以這個(gè)方法返回了正確的結(jié)果:'2 hours and 30 minutes'。

原文:dmitripavlutin.com/gentle-expl…

代碼部署后可能存在的BUG沒法實(shí)時(shí)知道,事后為了解決這些BUG,花了大量的時(shí)間進(jìn)行l(wèi)og 調(diào)試,這邊順便給大家推薦一個(gè)好用的BUG監(jiān)控工具 Fundebug。

總結(jié)

為函數(shù)調(diào)用對(duì)this影響最大,從現(xiàn)在開始不要問自己:

this 是從哪里來的?

而是要看看

函數(shù)是怎么被調(diào)用的?

對(duì)于箭頭函數(shù),需要想想

在這個(gè)箭頭函數(shù)被定義的地方,this是什么?

這是處理this時(shí)的正確想法,它們可以讓你免于頭痛。

JS 中 this 在各個(gè)場(chǎng)景下的指向

1. this 的奧秘

很多時(shí)候, JS 中的 this 對(duì)于咱們的初學(xué)者很容易產(chǎn)生困惑不解。 this 的功能很強(qiáng)大,但需要一定付出才能慢慢理解它。

對(duì)Java、PHP或其他標(biāo)準(zhǔn)語言來看,this 表示類方法中當(dāng)前對(duì)象的實(shí)例。大多數(shù)情況下,this 不能在方法之外使用,這樣就比較不會(huì)造成混淆。

在J要中情況就有所不同: this表示函數(shù)的當(dāng)前執(zhí)行上下文,JS 中函數(shù)調(diào)用主要有以下幾種方式:

  • 函數(shù)調(diào)用: alert('Hello World!')

  • 方法調(diào)用: console.log('Hello World!')

  • 構(gòu)造函數(shù): new RegExp('\\d')

  • 隱式調(diào)用: alert.call(undefined, 'Hello World!')

每種調(diào)用類型以自己的方式定義上下文,所以就很容易產(chǎn)生混淆。

此外,嚴(yán)格模式也會(huì)影響執(zhí)行上下文。

理解this關(guān)鍵是要清楚的知道函數(shù)調(diào)用及其如何影響上下文。

本文主要說明函數(shù)的調(diào)用方式及如何影響 this,并且說明執(zhí)行上下文的常見陷阱。

在開始之前,先知道幾個(gè)術(shù)語:

調(diào)用函數(shù)正在執(zhí)行創(chuàng)建函數(shù)體的代碼,或者只是調(diào)用函數(shù)。 例如,parseInt函數(shù)調(diào)用是parseInt('15')。

  • 函數(shù)調(diào)用:執(zhí)行構(gòu)成函數(shù)主體的代碼:例如,parseInt函數(shù)調(diào)用是parseInt('15')。

  • 調(diào)用的上下文:指 this 在函數(shù)體內(nèi)的值。 例如,map.set('key', 'value')的調(diào)用上下文是 map。

  • 函數(shù)的作用域:是在函數(shù)體中可訪問的變量、對(duì)象和函數(shù)的集合。

2.函數(shù)調(diào)用

當(dāng)一個(gè)表達(dá)式為函數(shù)接著一個(gè)(,一些用逗號(hào)分隔的參數(shù)以及一個(gè))時(shí),函數(shù)調(diào)用被執(zhí)行,例如parseInt('18')。

函數(shù)調(diào)用表達(dá)式不能是屬性方式的調(diào)用,如 obj.myFunc(),這種是創(chuàng)建一個(gè)方法調(diào)用。再如 [1,5].join(',')不是函數(shù)調(diào)用,而是方法調(diào)用,這種區(qū)別需要記住哈,很重要滴

函數(shù)調(diào)用的一個(gè)簡(jiǎn)單示例:

function?hello(name)?{?return?'Hello?'?+?name?+?'!';
}//?函數(shù)調(diào)用const?message?=?hello('World');console.log(message);?//?=>?'Hello?World!'復(fù)制代碼

hello('World')是函數(shù)調(diào)用: hello表達(dá)式等價(jià)于一個(gè)函數(shù),跟在它后面的是一對(duì)括號(hào)以及'World'參數(shù)。

一個(gè)更高級(jí)的例子是IIFE(立即調(diào)用的函數(shù)表達(dá)式)

const?message?=?(function(name)?{
?return?'Hello?'?+?name?+?'!';
})('World');console.log(message)?//?=>?'Hello?World!'復(fù)制代碼

IIFE也是一個(gè)函數(shù)調(diào)用:第一對(duì)圓括號(hào)(function(name) {...})是一個(gè)表達(dá)式,它的計(jì)算結(jié)果是一個(gè)函數(shù)對(duì)象,后面跟著一對(duì)圓括號(hào),圓括號(hào)的參數(shù)是“World”。

2.1. 在函數(shù)調(diào)用中的this

this 在函數(shù)調(diào)用中是一個(gè)全局對(duì)象

局對(duì)象由執(zhí)行環(huán)境決定。在瀏覽器中,this是 window 對(duì)象。

JS 中 this 在各個(gè)場(chǎng)景下的指向


在函數(shù)調(diào)用中,執(zhí)行上下文是全局對(duì)象。

再來看看下面函數(shù)中的上下文又是什么鬼:

function?sum(a,?b)?{?console.log(this?===?window);?//?=>?true
?this.myNumber?=?20;?//?將'myNumber'屬性添加到全局對(duì)象
?return?a?+?b;
}//?sum()?is?invoked?as?a?function//?sum()?中的?`this`?是一個(gè)全局對(duì)象(window)sum(15,?16);?//?=>?31window.myNumber;?//?=>?20復(fù)制代碼

在調(diào)用sum(15,16)時(shí),JS 自動(dòng)將this設(shè)置為全局對(duì)象,在瀏覽器中該對(duì)象是window。

當(dāng)this在任何函數(shù)作用域(最頂層作用域:全局執(zhí)行上下文)之外使用,this 表示 window 對(duì)象

console.log(this?===?window);?//?=>?true
this.myString?=?'Hello?World!';
console.log(window.myString);?//?=>?'Hello?World!'<!--?In?an?html?file?--><script?type="text/javascript">
?console.log(this?===?window);?//?=>?true</script>復(fù)制代碼

2.2 嚴(yán)格模式下的函數(shù)調(diào)用 this 又是什么樣的

this 在嚴(yán)格模式下的函數(shù)調(diào)用中為?undefined

嚴(yán)格模式是在 ECMAScript 5.1中引入的,它提供了更好的安全性和更強(qiáng)的錯(cuò)誤檢查。

要啟用嚴(yán)格模式,函數(shù)頭部寫入use strict 即可。

啟用后,嚴(yán)格模式會(huì)影響執(zhí)行上下文,this 在常規(guī)函數(shù)調(diào)用中值為undefined。 與上述情況2.1相反,執(zhí)行上下文不再是全局對(duì)象。

JS 中 this 在各個(gè)場(chǎng)景下的指向


嚴(yán)格模式函數(shù)調(diào)用示例:

function?multiply(a,?b)?{?'use?strict';?//?啟用嚴(yán)格模式
?console.log(this?===?undefined);?//?=>?true
?return?a?*?b;
}
multiply(2,?5);?//?=>?10復(fù)制代碼

當(dāng)multiply(2,5)作為函數(shù)調(diào)用時(shí),this是undefined。

嚴(yán)格模式不僅在當(dāng)前作用域中有效,在內(nèi)部作用域中也是有效的(對(duì)于在內(nèi)部聲明的所有函數(shù)):

function?execute()?{?'use?strict';?//?開啟嚴(yán)格模式?
?function?concat(str1,?str2)?{?//?嚴(yán)格模式仍然有效?
?console.log(this?===?undefined);?//?=>?true
?return?str1?+?str2;
?}?//?concat()?在嚴(yán)格模式下作為函數(shù)調(diào)用
?//?this?in?concat()?is?undefined
?concat('Hello',?'?World!');?//?=>?"Hello?World!"}
execute();
復(fù)制代碼

'use strict'被插入到執(zhí)行體的頂部,在其作用域內(nèi)啟用嚴(yán)格模式。 因?yàn)楹瘮?shù)concat是在執(zhí)行的作用域中聲明的,所以它繼承了嚴(yán)格模式。

單個(gè)JS文件可能包含嚴(yán)格和非嚴(yán)格模式。 因此,對(duì)于相同的調(diào)用類型,可以在單個(gè)腳本中具有不同的上下文行為:

function?nonStrictSum(a,?b)?{?//?非嚴(yán)格模式
?console.log(this?===?window);?//?=>?true
?return?a?+?b;
}function?strictSum(a,?b)?{?'use?strict';?//?啟用嚴(yán)格模式
?console.log(this?===?undefined);?//?=>?true
?return?a?+?b;
}
nonStrictSum(5,?6);?//?=>?11strictSum(8,?12);?//?=>?20復(fù)制代碼

2.3 陷阱:this 在內(nèi)部函數(shù)中的時(shí)候

函數(shù)調(diào)用的一個(gè)常見陷阱是,認(rèn)為this在內(nèi)部函數(shù)中的情況與外部函數(shù)中的情況相同。

正確地說,內(nèi)部函數(shù)的上下文只依賴于它的調(diào)用類型,而不依賴于外部函數(shù)的上下文。

要將 this 設(shè)置為所需的值,可以通過 .call()或.apply()修改內(nèi)部函數(shù)的上下文或使用.bind()創(chuàng)建綁定函數(shù)。

下面的例子是計(jì)算兩個(gè)數(shù)的和:

const?numbers?=?{?numberA:?5,?numberB:?10,?sum:?function()?{?console.log(this?===?numbers);?//?=>?true
?function?calculate()?{?console.log(this?===?numbers);?//?=>?false
?return?this.numberA?+?this.numberB;
?}?return?calculate();
?}
};
numbers.sum();?//?=>?NaN?復(fù)制代碼

sum()是對(duì)象上的方法調(diào)用,所以sum中的上下文是numbers對(duì)象。calculate函數(shù)是在sum中定義的,你可能希望在calculate()中this也表示number對(duì)象。

calculate()是一個(gè)函數(shù)調(diào)用(不是方法調(diào)用),它將this作為全局對(duì)象window(非嚴(yán)格模下)。即使外部函數(shù)sum將上下文作為number對(duì)象,它在calculate里面沒有影響。

sum()的調(diào)用結(jié)果是NaN,不是預(yù)期的結(jié)果5 + 10 = 15,這都是因?yàn)闆]有正確調(diào)用calculate。

為了解決這個(gè)問題,calculate函數(shù)中上下文應(yīng)該與 sum 中的一樣,以便可以訪問numberA和numberB屬性。

一種解決方案是通過調(diào)用calculator.call(this)手動(dòng)將calculate上下文更改為所需的上下文。

const?numbers?=?{?numberA:?5,?numberB:?10,?sum:?function()?{?console.log(this?===?numbers);?//?=>?true
?function?calculate()?{?console.log(this?===?numbers);?//?=>?true
?return?this.numberA?+?this.numberB;
?}?//?使用?.call()?方法修改上下文
?return?calculate.call(this);
?}
};
numbers.sum();?//?=>?15復(fù)制代碼

call(this)像往常一樣執(zhí)行calculate函數(shù),但 call 會(huì)把上下文修改為指定為第一個(gè)參數(shù)的值。

現(xiàn)在this.numberA + this.numberB相當(dāng)于numbers.numberA + numbers.numberB。 該函數(shù)返回預(yù)期結(jié)果5 + 10 = 15。

另一種就是使用箭頭函數(shù)

const?numbers?=?{?numberA:?5,?numberB:?10,?sum:?function()?{?console.log(this?===?numbers);?//?=>?true
?const?calculate?=?()?=>?{?console.log(this?===?numbers);?//?=>?true
?return?this.numberA?+?this.numberB;
?}?return?calculate();
?}
};
numbers.sum();?//?=>?15復(fù)制代碼

3.方法調(diào)用

方法是存儲(chǔ)在對(duì)象屬性中的函數(shù)。例如

const?myObject?=?{?//?helloFunction?是一個(gè)方法
?helloFunction:?function()?{?return?'Hello?World!';
?}
};const?message?=?myObject.helloFunction();
復(fù)制代碼

helloFunction是myObject的一個(gè)方法,要調(diào)用該方法,可以這樣子調(diào)用 :myObject.helloFunction。

當(dāng)一個(gè)表達(dá)式以屬性訪問的形式執(zhí)行時(shí),執(zhí)行的是方法調(diào)用,它相當(dāng)于以個(gè)函數(shù)接著(,一組用逗號(hào)分隔的參數(shù)以及)。

利用前面的例子,myObject.helloFunction()是對(duì)象myObject上的一個(gè)helloFunction的方法調(diào)用。[1, 2].join(',') 或/\s/.test('beautiful world')也被認(rèn)為是方法調(diào)用。

區(qū)分函數(shù)調(diào)用和方法調(diào)用非常重要,因?yàn)樗鼈兪遣煌念愋?。主要區(qū)別在于方法調(diào)用需要一個(gè)屬性訪問器形式來調(diào)用函數(shù)(obj.myFunc()或obj['myFunc']()),而函數(shù)調(diào)用不需要(myFunc())。

['Hello',?'World'].join(',?');?//?方法調(diào)用({?ten:?function()?{?return?10;?}?}).ten();?//?方法調(diào)用const?obj?=?{};
obj.myFunction?=?function()?{?return?new?Date().toString();
};
obj.myFunction();?//?方法調(diào)用const?otherFunction?=?obj.myFunction;
otherFunction();?//?函數(shù)調(diào)用parseFloat('16.60');?//?函數(shù)調(diào)用isNaN(0);?//?函數(shù)調(diào)用復(fù)制代碼

理解函數(shù)調(diào)用和方法調(diào)用之間的區(qū)別有助于正確識(shí)別上下文。

3.1 方法調(diào)用中 this 是腫么樣

在方法調(diào)用中,this是擁有這個(gè)方法的對(duì)象

當(dāng)調(diào)用對(duì)象上的方法時(shí),this就變成了對(duì)象本身。

JS 中 this 在各個(gè)場(chǎng)景下的指向


創(chuàng)建一個(gè)對(duì)象,該對(duì)象有一個(gè)遞增數(shù)字的方法

const?calc?=?{?num:?0,?increment:?function()?{?console.log(this?===?calc);?//?=>?true
?this.num?+=?1;?return?this.num;
?}
};//?method?invocation.?this?is?calccalc.increment();?//?=>?1calc.increment();?//?=>?2復(fù)制代碼

調(diào)用calc.increment()使increment函數(shù)的上下文成為calc對(duì)象。所以使用this.num來增加num屬性是有效的。

再來看看另一個(gè)例子。JS對(duì)象從原型繼承一個(gè)方法,當(dāng)在對(duì)象上調(diào)用繼承的方法時(shí),調(diào)用的上下文仍然是對(duì)象本身

const?myDog?=?Object.create({?sayName:?function()?{?console.log(this?===?myDog);?//?=>?true
?return?this.name;
?}
});
myDog.name?=?'Milo';//?方法調(diào)用?this?指向?myDogmyDog.sayName();?//?=>?'Milo'復(fù)制代碼

Object.create()創(chuàng)建一個(gè)新對(duì)象myDog,并根據(jù)第一個(gè)參數(shù)設(shè)置其原型。myDog對(duì)象繼承sayName方法。

執(zhí)行myDog. sayname()時(shí),myDog是調(diào)用的上下文。

在EC6 class 語法中,方法調(diào)用上下文也是實(shí)例本身

class?Planet?{?constructor(name)?{?this.name?=?name;?
?}
?getName()?{
?console.log(this?===?earth);?//?=>?true
?return?this.name;
?}
}var?earth?=?new?Planet('Earth');//?method?invocation.?the?context?is?earthearth.getName();?//?=>?'Earth'復(fù)制代碼

3.2 陷阱:將方法與其對(duì)象分離

方法可以從對(duì)象中提取到一個(gè)單獨(dú)的變量const alone = myObj.myMethod。當(dāng)方法單獨(dú)調(diào)用時(shí),與原始對(duì)象alone()分離,你可能認(rèn)為當(dāng)前的this就是定義方法的對(duì)象myObject。

如果方法在沒有對(duì)象的情況下調(diào)用,那么函數(shù)調(diào)用就會(huì)發(fā)生,此時(shí)的this指向全局對(duì)象window嚴(yán)格模式下是undefined。

下面的示例定義了Animal構(gòu)造函數(shù)并創(chuàng)建了它的一個(gè)實(shí)例:myCat。然后setTimout()在1秒后打印myCat對(duì)象信息

function?Animal(type,?legs)?{?this.type?=?type;?this.legs?=?legs;?
?this.logInfo?=?function()?{?console.log(this?===?myCat);?//?=>?false
?console.log('The?'?+?this.type?+?'?has?'?+?this.legs?+?'?legs');
?}
}const?myCat?=?new?Animal('Cat',?4);//?The?undefined?has?undefined?legs?setTimeout(myCat.logInfo,?1000);?
復(fù)制代碼

你可能認(rèn)為setTimout調(diào)用myCat.loginfo()時(shí),它應(yīng)該打印關(guān)于myCat對(duì)象的信息。

不幸的是,方法在作為參數(shù)傳遞時(shí)與對(duì)象是分離,setTimout(myCat.logInfo)以下情況是等效的:

setTimout(myCat.logInfo);
//?等價(jià)于
const?extractedLogInfo?=?myCat.logInfo;setTimout(extractedLogInfo);
復(fù)制代碼

將分離的logInfo作為函數(shù)調(diào)用時(shí),this是全局 window,所以對(duì)象信息沒有正確地打印。

函數(shù)可以使用.bind()方法與對(duì)象綁定,就可以解決 this 指向的問題。

function?Animal(type,?legs)?{?this.type?=?type;?this.legs?=?legs;?
?this.logInfo?=?function()?{?console.log(this?===?myCat);?//?=>?true
?console.log('The?'?+?this.type?+?'?has?'?+?this.legs?+?'?legs');
?};
}const?myCat?=?new?Animal('Cat',?4);//?logs?"The?Cat?has?4?legs"setTimeout(myCat.logInfo.bind(myCat),?1000);
復(fù)制代碼

myCat.logInfo.bind(myCat)返回一個(gè)新函數(shù),它的執(zhí)行方式與logInfo完全相同,但是此時(shí)的 this 指向 myCat,即使在函數(shù)調(diào)用中也是如此。

另一種解決方案是將logInfo()方法定義為一個(gè)箭頭函數(shù):

function?Animal(type,?legs)?{?this.type?=?type;?this.legs?=?legs;?
?this.logInfo?=?()?=>?{?console.log(this?===?myCat);?//?=>?true
?console.log('The?'?+?this.type?+?'?has?'?+?this.legs?+?'?legs');
?};
}const?myCat?=?new?Animal('Cat',?4);//?logs?"The?Cat?has?4?legs"setTimeout(myCat.logInfo,?1000);
復(fù)制代碼

4. 構(gòu)造函數(shù)調(diào)用

當(dāng)new關(guān)鍵詞緊接著函數(shù)對(duì)象,(,一組逗號(hào)分隔的參數(shù)以及)時(shí)被調(diào)用,執(zhí)行的是構(gòu)造函數(shù)調(diào)用如new RegExp('\\d')。

聲明了一個(gè)Country函數(shù),并且將它作為一個(gè)構(gòu)造函數(shù)調(diào)用:

function?Country(name,?traveled)?{?this.name?=?name???name?:?'United?Kingdom';?this.traveled?=?Boolean(traveled);?
}
Country.prototype.travel?=?function()?{?this.traveled?=?true;
};//?構(gòu)造函數(shù)調(diào)用const?france?=?new?Country('France',?false);//?構(gòu)造函數(shù)調(diào)用const?unitedKingdom?=?new?Country;
france.travel();?//?Travel?to?France復(fù)制代碼

new Country('France', false)是Country函數(shù)的構(gòu)造函數(shù)調(diào)用。它的執(zhí)行結(jié)果是一個(gè)name屬性為'France'的新的對(duì)象。 如果這個(gè)構(gòu)造函數(shù)調(diào)用時(shí)不需要參數(shù),那么括號(hào)可以省略:new Country。

從ES6開始,JS 允許用class關(guān)鍵詞來定義構(gòu)造函數(shù)

class?City?{?constructor(name,?traveled)?{?this.name?=?name;?this.traveled?=?false;
?}
?travel()?{?this.traveled?=?true;
?}
}//?Constructor?invocationconst?paris?=?new?City('Paris',?false);
paris.travel();
復(fù)制代碼

new City('Paris')是構(gòu)造函數(shù)調(diào)用。這個(gè)對(duì)象的初始化由這個(gè)類中一個(gè)特殊的方法constructor來處理。其中,this指向新創(chuàng)建的對(duì)象。

構(gòu)造函數(shù)創(chuàng)建了一個(gè)新的空的對(duì)象,它從構(gòu)造函數(shù)的原型繼承了屬性。構(gòu)造函數(shù)的作用就是去初始化這個(gè)對(duì)象。 可能你已經(jīng)知道了,在這種類型的調(diào)用中,上下文指向新創(chuàng)建的實(shí)例。

當(dāng)屬性訪問myObject.myFunction前面有一個(gè)new關(guān)鍵詞時(shí),JS會(huì)執(zhí)行構(gòu)造函數(shù)調(diào)用而不是原來的方法調(diào)用。

例如new myObject.myFunction():它相當(dāng)于先用屬性訪問把方法提取出來extractedFunction = myObject.myFunction,然后利用把它作為構(gòu)造函數(shù)創(chuàng)建一個(gè)新的對(duì)象: new extractedFunction()。

4.1. 構(gòu)造函數(shù)中的 this

在構(gòu)造函數(shù)調(diào)用中 this 指向新創(chuàng)建的對(duì)象

構(gòu)造函數(shù)調(diào)用的上下文是新創(chuàng)建的對(duì)象。它利用構(gòu)造函數(shù)的參數(shù)初始化新的對(duì)象,設(shè)定屬性的初始值,添加事件處理函數(shù)等等。

JS 中 this 在各個(gè)場(chǎng)景下的指向


來看看下面示例中的上下文

function?Foo?()?{?console.log(this?instanceof?Foo);?//?=>?true
?this.property?=?'Default?Value';
}//?Constructor?invocationconst?fooInstance?=?new?Foo();
fooInstance.property;?//?=>?'Default?Value'復(fù)制代碼

new Foo() 正在進(jìn)行構(gòu)造函數(shù)調(diào)用,其中上下文是fooInstance。 在Foo內(nèi)部初始化對(duì)象:this.property被賦值為默認(rèn)值。

同樣的情況在用class語法(從ES6起)時(shí)也會(huì)發(fā)生,唯一的區(qū)別是初始化在constructor方法中進(jìn)行:

class?Bar?{?constructor()?{?console.log(this?instanceof?Bar);?//?=>?true
?this.property?=?'Default?Value';
?}
}//?Constructor?invocationconst?barInstance?=?new?Bar();
barInstance.property;?//?=>?'Default?Value'復(fù)制代碼

4.2. 陷阱: 忘了使用 new

有些JS函數(shù)不是只在作為構(gòu)造函數(shù)調(diào)用的時(shí)候才創(chuàng)建新的對(duì)象,作為函數(shù)調(diào)用時(shí)也會(huì),例如RegExp:

var?reg1?=?new?RegExp('\\w+');var?reg2?=?RegExp('\\w+');
reg1?instanceof?RegExp;?//?=>?truereg2?instanceof?RegExp;?//?=>?truereg1.source?===?reg2.source;?//?=>?true復(fù)制代碼

當(dāng)執(zhí)行的 new RegExp('\\w+')和RegExp('\\w+')時(shí),JS 會(huì)創(chuàng)建等價(jià)的正則表達(dá)式對(duì)象。

使用函數(shù)調(diào)用來創(chuàng)建對(duì)象存在一個(gè)潛在的問題(不包括工廠模式),因?yàn)橐恍?gòu)造函數(shù)可能會(huì)忽略在缺少new關(guān)鍵字時(shí)初始化對(duì)象的邏輯。

下面的例子說明了這個(gè)問題:

function?Vehicle(type,?wheelsCount)?{?this.type?=?type;?this.wheelsCount?=?wheelsCount;?return?this;
}//?忘記使用?new?const?car?=?Vehicle('Car',?4);
car.type;?//?=>?'Car'car.wheelsCount?//?=>?4car?===?window?//?=>?true復(fù)制代碼

Vehicle是一個(gè)在上下文對(duì)象上設(shè)置type和wheelsCount屬性的函數(shù)。

當(dāng)執(zhí)行Vehicle('Car', 4)時(shí),返回一個(gè)對(duì)象Car,它具有正確的屬性:Car.type 為 Car和Car.wheelsCount 為4,你可能認(rèn)為它很適合創(chuàng)建和初始化新對(duì)象。

然而,在函數(shù)調(diào)用中,this是window對(duì)象 ,因此 Vehicle('Car',4)在 window 對(duì)象上設(shè)置屬性。 顯然這是錯(cuò)誤,它并沒有創(chuàng)建新對(duì)象。

當(dāng)你希望調(diào)用構(gòu)造函數(shù)時(shí),確保你使用了new操作符:

function?Vehicle(type,?wheelsCount)?{?if?(!(this?instanceof?Vehicle))?{?throw?Error('Error:?Incorrect?invocation');
?}?this.type?=?type;?this.wheelsCount?=?wheelsCount;?return?this;
}//?Constructor?invocationconst?car?=?new?Vehicle('Car',?4);
car.type?//?=>?'Car'car.wheelsCount?//?=>?4car?instanceof?Vehicle?//?=>?true//?Function?invocation.?Throws?an?error.const?brokenCar?=?Vehicle('Broken?Car',?3);
復(fù)制代碼

new Vehicle('Car',4) 運(yùn)行正常:創(chuàng)建并初始化一個(gè)新對(duì)象,因?yàn)闃?gòu)造函數(shù)調(diào)用中時(shí)使用了new關(guān)鍵字。

在構(gòu)造函數(shù)里添加了一個(gè)驗(yàn)證this instanceof Vehicle來確保執(zhí)行的上下文是正確的對(duì)象類型。如果this不是Vehicle,那么就會(huì)報(bào)錯(cuò)。這樣,如果執(zhí)行Vehicle('Broken Car', 3)(沒有new),我們會(huì)得到一個(gè)異常:Error: Incorrect invocation。

5. 隱式調(diào)用

使用myFun.call()或myFun.apply()方法調(diào)用函數(shù)時(shí),執(zhí)行的是隱式調(diào)用。

JS中的函數(shù)是第一類對(duì)象,這意味著函數(shù)就是對(duì)象,對(duì)象的類型為Function。從函數(shù)對(duì)象的方法列表中,.call()和.apply()用于調(diào)用具有可配置上下文的函數(shù)。

  • 方法 .call(thisArg[, arg1[, arg2[, ...]]])將接受的第一個(gè)參數(shù)thisArg作為調(diào)用時(shí)的上下文,arg1, arg2, ...這些則作為參數(shù)傳入被調(diào)用的函數(shù)。

  • 方法.apply(thisArg, [args])將接受的第一個(gè)參數(shù)thisArg作為調(diào)用時(shí)的上下文,并且接受另一個(gè)類似數(shù)組的對(duì)象[arg1, arg2, ...]作為被調(diào)用函數(shù)的參數(shù)傳入。

下面是隱式調(diào)用的例子

function?increment(number)?{?return?++number;?
}
increment.call(undefined,?10);?//?=>?11increment.apply(undefined,?[10]);?//?=>?11復(fù)制代碼

increment.call()和increment.apply()都用參數(shù)10調(diào)用了這個(gè)自增函數(shù)。

兩者的區(qū)別是.call()接受一組參數(shù),例如myFunction.call(thisValue, 'value1', 'value2')。而.apply()接受的一組參數(shù)必須是一個(gè)類似數(shù)組的對(duì)象,例如myFunction.apply(thisValue, ['value1', 'value2'])。

5.1. 隱式調(diào)用中的this

在隱式調(diào)用.call()或.apply()中,this是第一個(gè)參數(shù)

很明顯,在隱式調(diào)用中,this作為第一個(gè)參數(shù)傳遞給.call()或.apply()。

var?rabbit?=?{?name:?'White?Rabbit'?};function?concatName(string)?{?console.log(this?===?rabbit);?//?=>?true
?return?string?+?this.name;
}
concatName.call(rabbit,?'Hello?');?//?=>?'Hello?White?Rabbit'concatName.apply(rabbit,?['Bye?']);?//?=>?'Bye?White?Rabbit'復(fù)制代碼

當(dāng)應(yīng)該使用特定上下文執(zhí)行函數(shù)時(shí),隱式調(diào)用非常有用。例如為了解決方法調(diào)用時(shí),this總是window或嚴(yán)格模式下的undefined的上下文問題。隱式調(diào)用可以用于模擬在一個(gè)對(duì)象上調(diào)用某個(gè)方法。

function?Runner(name)?{?console.log(this?instanceof?Rabbit);?//?=>?true
?this.name?=?name;?
}function?Rabbit(name,?countLegs)?{?console.log(this?instanceof?Rabbit);?//?=>?true
?Runner.call(this,?name);?this.countLegs?=?countLegs;
}const?myRabbit?=?new?Rabbit('White?Rabbit',?4);
myRabbit;?//?{?name:?'White?Rabbit',?countLegs:?4?}復(fù)制代碼

Rabbit中的Runner.call(this, name)隱式調(diào)用了父類的函數(shù)來初始化這個(gè)對(duì)象。

6. 綁定函數(shù)

綁定函數(shù)是與對(duì)象連接的函數(shù)。通常使用.bind()方法從原始函數(shù)創(chuàng)建。原始函數(shù)和綁定函數(shù)共享相同的代碼和作用域,但執(zhí)行時(shí)上下文不同。

方法 myFunc.bind(thisArg[, arg1[, arg2[, ...]]])接受第一個(gè)參數(shù)thisArg作為綁定函數(shù)執(zhí)行時(shí)的上下文,并且它接受一組可選的參數(shù) arg1, arg2, ...作為被調(diào)用函數(shù)的參數(shù)。它返回一個(gè)綁定了thisArg的新函數(shù)。

function?multiply(number)?{?'use?strict';?return?this?*?number;
}const?double?=?multiply.bind(2);
double(3);?//?=>?6double(10);?//?=>?20復(fù)制代碼

bind(2)返回一個(gè)新的函數(shù)對(duì)象double,double 綁定了數(shù)字2。multiply和double具有相同的代碼和作用域。

與.apply()和.call() 方法相反,它不會(huì)立即調(diào)用該函數(shù),.bind()方法只返回一個(gè)新函數(shù),在之后被調(diào)用,只是this已經(jīng)被提前設(shè)置好了。

6.1. 綁定函數(shù)中的this

在調(diào)用綁定函數(shù)時(shí),this是.bind()的第一個(gè)參數(shù)。

.bind()的作用是創(chuàng)建一個(gè)新函數(shù),調(diào)用該函數(shù)時(shí),將上下文作為傳遞給.bind()的第一個(gè)參數(shù)。它是一種強(qiáng)大的技術(shù),使咱們可以創(chuàng)建一個(gè)定義了this值的函數(shù)。

JS 中 this 在各個(gè)場(chǎng)景下的指向


來看看,如何在如何在綁定函數(shù)設(shè)置 this

const?numbers?=?{?array:?[3,?5,?10],
?getNumbers:?function()?{?return?this.array;?
?}
};const?boundGetNumbers?=?numbers.getNumbers.bind(numbers);
boundGetNumbers();?//?=>?[3,?5,?10]//?Extract?method?from?objectconst?simpleGetNumbers?=?numbers.getNumbers;
simpleGetNumbers();?//?=>?undefined?(嚴(yán)格模式下報(bào)錯(cuò))復(fù)制代碼

numbers.getNumbers.bind(numbers)返回綁定numbers對(duì)象boundGetNumbers函數(shù)。boundGetNumbers()調(diào)用時(shí)的this是number對(duì)象,并能夠返回正確的數(shù)組對(duì)象。

可以將函數(shù)numbers.getNumbers提取到變量simpleGetNumbers中而不進(jìn)行綁定。在之后的函數(shù)調(diào)用中simpleGetNumbers()的this是window(嚴(yán)格模式下為undefined),不是number對(duì)象。在這個(gè)情況下,simpleGetNumbers()不會(huì)正確返回?cái)?shù)組。

6.2 緊密的上下文綁定

.bind()創(chuàng)建一個(gè)永久的上下文鏈接,并始終保持它。 一個(gè)綁定函數(shù)不能通過.call()或者.apply()來改變它的上下文,甚至是再次綁定也不會(huì)有什么作用。

只有綁定函數(shù)的構(gòu)造函數(shù)調(diào)用才能更改已經(jīng)綁定的上下文,但是很不推薦的做法(構(gòu)造函數(shù)調(diào)用必須使用常規(guī)的非綁定函數(shù))。

下面示例創(chuàng)建一個(gè)綁定函數(shù),然后嘗試更改其已預(yù)先定義好的上下文

function?getThis()?{?'use?strict';?return?this;
}const?one?=?getThis.bind(1);//?綁定函數(shù)調(diào)用one();?//?=>?1//?使用帶有.apply()和.call()的綁定函數(shù)one.call(2);?//?=>?1one.apply(2);?//?=>?1//?再次綁定one.bind(2)();?//?=>?1//?以構(gòu)造函數(shù)的形式調(diào)用綁定函數(shù)new?one();?//?=>?Object復(fù)制代碼

只有new one()改變了綁定函數(shù)的上下文,其他方式的調(diào)用中this總是等于1。

7. 箭頭函數(shù)

箭頭函數(shù)用于以更短的形式聲明函數(shù),并在詞法上綁定上下文。它可以這樣使用

const?hello?=?(name)?=>?{?return?'Hello?'?+?name;
};
hello('World');?//?=>?'Hello?World'//?Keep?only?even?numbers[1,?2,?5,?6].filter(item?=>?item?%?2?===?0);?//?=>?[2,?6]復(fù)制代碼

箭頭函數(shù)語法簡(jiǎn)單,沒有冗長的function 關(guān)鍵字。當(dāng)箭頭函數(shù)只有一條語句時(shí),甚至可以省略return關(guān)鍵字。

箭頭函數(shù)是匿名的,這意味著name屬性是一個(gè)空字符串''。這樣它就沒有詞法上函數(shù)名(函數(shù)名對(duì)于遞歸、分離事件處理程序非常有用)

同時(shí),跟常規(guī)函數(shù)相反,它也不提供arguments對(duì)象。但是,這在ES6中通過rest parameters修復(fù)了:

const?sumArguments?=?(...args)?=>?{?console.log(typeof?arguments);?//?=>?'undefined'
?return?args.reduce((result,?item)?=>?result?+?item);
};
sumArguments.name?//?=>?''sumArguments(5,?5,?6);?//?=>?16復(fù)制代碼

7.1. 箭頭函數(shù)中的this

this 定義箭頭函數(shù)的封閉上下文

箭頭函數(shù)不會(huì)創(chuàng)建自己的執(zhí)行上下文,而是從定義它的外部函數(shù)中獲取 this。 換句話說,箭頭函數(shù)在詞匯上綁定 this。

JS 中 this 在各個(gè)場(chǎng)景下的指向


下面的例子說明了這個(gè)上下文透明的特性:

class?Point?{?constructor(x,?y)?{?this.x?=?x;?this.y?=?y;
?}
?log()?{?console.log(this?===?myPoint);?//?=>?true
?setTimeout(()=>?{?console.log(this?===?myPoint);?//?=>?true
?console.log(this.x?+?':'?+?this.y);?//?=>?'95:165'
?},?1000);
?}
}const?myPoint?=?new?Point(95,?165);
myPoint.log();
復(fù)制代碼

setTimeout使用與log()方法相同的上下文(myPoint對(duì)象)調(diào)用箭頭函數(shù)。正如所見,箭頭函數(shù)從定義它的函數(shù)繼承上下文。

如果在這個(gè)例子里嘗試用常規(guī)函數(shù),它創(chuàng)建自己的上下文(window或嚴(yán)格模式下的undefined)。因此,要使相同的代碼正確地使用函數(shù)表達(dá)式,需要手動(dòng)綁定上下文:setTimeout(function(){…}.bind(this))。這很冗長,使用箭頭函數(shù)是一種更簡(jiǎn)潔、更短的解決方案。

如果箭頭函數(shù)在最頂層的作用域中定義(在任何函數(shù)之外),則上下文始終是全局對(duì)象(瀏覽器中的 window):

onst?getContext?=?()?=>?{?console.log(this?===?window);?//?=>?true
?return?this;
};console.log(getContext()?===?window);?//?=>?true復(fù)制代碼

箭頭函數(shù)一勞永逸地與詞匯上下文綁定。 即使修改上下文,this也不能被改變:

const?numbers?=?[1,?2];
(function()?{?
?const?get?=?()?=>?{?console.log(this?===?numbers);?//?=>?true
?return?this;
?};?console.log(this?===?numbers);?//?=>?true
?get();?//?=>?[1,?2]
?//?Use?arrow?function?with?.apply()?and?.call()
?get.call([0]);?//?=>?[1,?2]
?get.apply([0]);?//?=>?[1,?2]
?//?Bind
?get.bind([0])();?//?=>?[1,?2]}).call(numbers);
復(fù)制代碼

無論如何調(diào)用箭頭函數(shù)get,它總是保留詞匯上下文numbers。 用其他上下文的隱式調(diào)用(通過 get.call([0])或get.apply([0]))或者重新綁定(通過.bind())都不會(huì)起作用。

箭頭函數(shù)不能用作構(gòu)造函數(shù)。 將它作為構(gòu)造函數(shù)調(diào)用(new get())會(huì)拋出一個(gè)錯(cuò)誤:TypeError: get is not a constructor。

7.2. 陷阱: 用箭頭函數(shù)定義方法

你可能希望使用箭頭函數(shù)來聲明一個(gè)對(duì)象上的方法。箭頭函數(shù)的定義相比于函數(shù)表達(dá)式短得多:(param) => {...} instead of function(param) {..}。

來看看例子,用箭頭函數(shù)在Period類上定義了format()方法:

function?Period?(hours,?minutes)?{?
?this.hours?=?hours;?this.minutes?=?minutes;
}
Period.prototype.format?=?()?=>?{?console.log(this?===?window);?//?=>?true
?return?this.hours?+?'?hours?and?'?+?this.minutes?+?'?minutes';
};const?walkPeriod?=?new?Period(2,?30);?
walkPeriod.format();?//?=>?'undefined?hours?and?undefined?minutes'復(fù)制代碼

由于format是一個(gè)箭頭函數(shù),并且在全局上下文(最頂層的作用域)中定義,因此 this 指向window對(duì)象。

即使format作為方法在一個(gè)對(duì)象上被調(diào)用如walkPeriod.format(),window仍然是這次調(diào)用的上下文。之所以會(huì)這樣是因?yàn)榧^函數(shù)有靜態(tài)的上下文,并不會(huì)隨著調(diào)用方式的改變而改變。

該方法返回'undefined hours和undefined minutes',這不是咱們想要的結(jié)果。

函數(shù)表達(dá)式解決了這個(gè)問題,因?yàn)槌R?guī)函數(shù)確實(shí)能根據(jù)實(shí)際調(diào)用改變它的上下文:

function?Period?(hours,?minutes)?{?
?this.hours?=?hours;?this.minutes?=?minutes;
}
Period.prototype.format?=?function()?{?console.log(this?===?walkPeriod);?//?=>?true
?return?this.hours?+?'?hours?and?'?+?this.minutes?+?'?minutes';
};const?walkPeriod?=?new?Period(2,?30);?
walkPeriod.format();?//?=>?'2?hours?and?30?minutes'復(fù)制代碼

walkPeriod.format()是一個(gè)對(duì)象上的方法調(diào)用,它的上下文是walkPeriod對(duì)象。this.hours等于2,this.minutes等于30,所以這個(gè)方法返回了正確的結(jié)果:'2 hours and 30 minutes'。

原文:dmitripavlutin.com/gentle-expl…

代碼部署后可能存在的BUG沒法實(shí)時(shí)知道,事后為了解決這些BUG,花了大量的時(shí)間進(jìn)行l(wèi)og 調(diào)試,這邊順便給大家推薦一個(gè)好用的BUG監(jiān)控工具 Fundebug。

總結(jié)

為函數(shù)調(diào)用對(duì)this影響最大,從現(xiàn)在開始不要問自己:

this 是從哪里來的?

而是要看看

函數(shù)是怎么被調(diào)用的?

對(duì)于箭頭函數(shù),需要想想

在這個(gè)箭頭函數(shù)被定義的地方,this是什么?

這是處理this時(shí)的正確想法,它們可以讓你免于頭痛。

JS 中 this 在各個(gè)場(chǎng)景下的指向


向AI問一下細(xì)節(jié)

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

AI