您好,登錄后才能下訂單哦!
javascript中的this該怎么用?針對(duì)這個(gè)問(wèn)題,這篇文章給出了相對(duì)應(yīng)的分析和解答,希望能幫助更多想解決這個(gè)問(wèn)題的朋友找到更加簡(jiǎn)單易行的辦法。
1、this 的奧秘
this 關(guān)鍵字是 Java 常用的關(guān)鍵字,可用于任何實(shí)例方法內(nèi)指向當(dāng)前對(duì)象,也可指向?qū)ζ湔{(diào)用當(dāng)前方法的對(duì)象,或者在需要當(dāng)前類型對(duì)象引用時(shí)使用。
對(duì)Java、PHP或其他標(biāo)準(zhǔn)語(yǔ)言來(lái)看,this
表示類方法中當(dāng)前對(duì)象的實(shí)例。大多數(shù)情況下,this
不能在方法之外使用,這樣就比較不會(huì)造成混淆。
在J要中情況就有所不同: this
表示函數(shù)的當(dāng)前執(zhí)行上下文,JS 中函數(shù)調(diào)用主要有以下幾種方式:
alert('Hello World!')
console.log('Hello World!')
new RegExp('\\d')
alert.call(undefined, 'Hello World!')
每種調(diào)用類型以自己的方式定義上下文,所以就很容易產(chǎn)生混淆。
此外,嚴(yán)格模式也會(huì)影響執(zhí)行上下文。
理解this
關(guān)鍵是要清楚的知道函數(shù)調(diào)用及其如何影響上下文。
本文主要說(shuō)明函數(shù)的調(diào)用方式及如何影響 this
,并且說(shuō)明執(zhí)行上下文的常見(jiàn)陷阱。
在開(kāi)始之前,先知道幾個(gè)術(shù)語(yǔ):
調(diào)用函數(shù)正在執(zhí)行創(chuàng)建函數(shù)體的代碼,或者只是調(diào)用函數(shù)。 例如,parseInt函數(shù)調(diào)用是parseInt('15')。
parseInt
函數(shù)調(diào)用是parseInt('15')
。this
在函數(shù)體內(nèi)的值。 例如,map.set('key', 'value')
的調(diào)用上下文是 map
。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!'
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!'
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ì)象。
在函數(shù)調(diào)用中,執(zhí)行上下文是全局對(duì)象。
再來(lái)看看下面函數(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); // => 31 window.myNumber; // => 20
在調(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>
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ì)象。
嚴(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
當(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'; // 開(kāi)啟嚴(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();
'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); // => 11 strictSum(8, 12); // => 20
2.3、陷阱:this
在內(nèi)部函數(shù)中的時(shí)候
函數(shù)調(diào)用的一個(gè)常見(jiàn)陷阱是,認(rèn)為this
在內(nèi)部函數(shù)中的情況與外部函數(shù)中的情況相同。
正確地說(shuō),內(nèi)部函數(shù)的上下文只依賴于它的調(diào)用類型,而不依賴于外部函數(shù)的上下文。
要將 this
設(shè)置為所需的值,可以通過(guò) .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
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
里面沒(méi)有影響。
sum()
的調(diào)用結(jié)果是NaN
,不是預(yù)期的結(jié)果5 + 10 = 15
,這都是因?yàn)闆](méi)有正確調(diào)用calculate
。
為了解決這個(gè)問(wèn)題,calculate
函數(shù)中上下文應(yīng)該與 sum
中的一樣,以便可以訪問(wèn)numberA
和numberB
屬性。
一種解決方案是通過(guò)調(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
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
3、方法調(diào)用
方法是存儲(chǔ)在對(duì)象屬性中的函數(shù)。例如
const myObject = { // helloFunction 是一個(gè)方法 helloFunction: function() { return 'Hello World!'; } }; const message = myObject.helloFunction();
helloFunction
是myObject
的一個(gè)方法,要調(diào)用該方法,可以這樣子調(diào)用 :myObject.helloFunction
。
當(dāng)一個(gè)表達(dá)式以屬性訪問(wèn)的形式執(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è)屬性訪問(wèn)器形式來(lái)調(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)用
理解函數(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ì)象本身。
創(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 calc calc.increment(); // => 1 calc.increment(); // => 2
調(diào)用calc.increment()
使increment
函數(shù)的上下文成為calc
對(duì)象。所以使用this.num
來(lái)增加num
屬性是有效的。
再來(lái)看看另一個(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 指向 myDog myDog.sayName(); // => 'Milo'
Object.create()
創(chuàng)建一個(gè)新對(duì)象myDog
,并根據(jù)第一個(gè)參數(shù)設(shè)置其原型。myDog
對(duì)象繼承sayName
方法。
執(zhí)行myDog. sayname()
時(shí),myDog
是調(diào)用的上下文。
在EC6 class
語(yǔ)法中,方法調(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 earth earth.getName(); // => 'Earth'
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
。
如果方法在沒(méi)有對(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);
你可能認(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);
將分離的logInfo
作為函數(shù)調(diào)用時(shí),this
是全局 window
,所以對(duì)象信息沒(méi)有正確地打印。
函數(shù)可以使用.bind()
方法與對(duì)象綁定,就可以解決 this
指向的問(wèn)題。
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);
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);
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
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開(kāi)始,JS 允許用class
關(guān)鍵詞來(lái)定義構(gòu)造函數(shù)
class City { constructor(name, traveled) { this.name = name; this.traveled = false; } travel() { this.traveled = true; } } // Constructor invocation const paris = new City('Paris', false); paris.travel();
new City('Paris')
是構(gòu)造函數(shù)調(diào)用。這個(gè)對(duì)象的初始化由這個(gè)類中一個(gè)特殊的方法constructor
來(lái)處理。其中,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)屬性訪問(wèn)myObject.myFunction
前面有一個(gè)new
關(guān)鍵詞時(shí),JS會(huì)執(zhí)行構(gòu)造函數(shù)調(diào)用而不是原來(lái)的方法調(diào)用。
例如new myObject.myFunction()
:它相當(dāng)于先用屬性訪問(wèn)把方法提取出來(lái)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ù)等等。
來(lái)看看下面示例中的上下文
function Foo () { console.log(this instanceof Foo); // => true this.property = 'Default Value'; } // Constructor invocation const fooInstance = new Foo(); fooInstance.property; // => 'Default Value'
new Foo()
正在進(jìn)行構(gòu)造函數(shù)調(diào)用,其中上下文是fooInstance
。 在Foo
內(nèi)部初始化對(duì)象:this.property
被賦值為默認(rèn)值。
同樣的情況在用class
語(yǔ)法(從ES6起)時(shí)也會(huì)發(fā)生,唯一的區(qū)別是初始化在constructor
方法中進(jìn)行:
class Bar { constructor() { console.log(this instanceof Bar); // => true this.property = 'Default Value'; } } // Constructor invocation const barInstance = new Bar(); barInstance.property; // => 'Default Value'
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; // => true reg2 instanceof RegExp; // => true reg1.source === reg2.source; // => true
當(dāng)執(zhí)行的 new RegExp('\\w+')
和RegExp('\\w+')
時(shí),JS 會(huì)創(chuàng)建等價(jià)的正則表達(dá)式對(duì)象。
使用函數(shù)調(diào)用來(lái)創(chuàng)建對(duì)象存在一個(gè)潛在的問(wèn)題(不包括工廠模式),因?yàn)橐恍?gòu)造函數(shù)可能會(huì)忽略在缺少new
關(guān)鍵字時(shí)初始化對(duì)象的邏輯。
下面的例子說(shuō)明了這個(gè)問(wèn)題:
function Vehicle(type, wheelsCount) { this.type = type; this.wheelsCount = wheelsCount; return this; } // 忘記使用 new const car = Vehicle('Car', 4); car.type; // => 'Car' car.wheelsCount // => 4 car === window // => true
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ò)誤,它并沒(méi)有創(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 invocation const car = new Vehicle('Car', 4); car.type // => 'Car' car.wheelsCount // => 4 car instanceof Vehicle // => true // Function invocation. Throws an error. const brokenCar = Vehicle('Broken Car', 3);
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
來(lái)確保執(zhí)行的上下文是正確的對(duì)象類型。如果this
不是Vehicle
,那么就會(huì)報(bào)錯(cuò)。這樣,如果執(zhí)行Vehicle('Broken Car', 3)
(沒(méi)有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); // => 11 increment.apply(undefined, [10]); // => 11
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'
當(dāng)應(yīng)該使用特定上下文執(zhí)行函數(shù)時(shí),隱式調(diào)用非常有用。例如為了解決方法調(diào)用時(shí),this
總是window
或嚴(yán)格模式下的undefined
的上下文問(wèn)題。隱式調(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 }
Rabbit
中的Runner.call(this, name)
隱式調(diào)用了父類的函數(shù)來(lái)初始化這個(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); // => 6 double(10); // => 20
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ù)。
來(lái)看看,如何在如何在綁定函數(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 object const simpleGetNumbers = numbers.getNumbers; simpleGetNumbers(); // => undefined (嚴(yán)格模式下報(bào)錯(cuò))
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ù)不能通過(guò).call()
或者.apply()
來(lái)改變它的上下文,甚至是再次綁定也不會(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); // => 1 one.apply(2); // => 1 // 再次綁定 one.bind(2)(); // => 1 // 以構(gòu)造函數(shù)的形式調(diào)用綁定函數(shù) new one(); // => Object
只有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]
箭頭函數(shù)語(yǔ)法簡(jiǎn)單,沒(méi)有冗長(zhǎng)的function
關(guān)鍵字。當(dāng)箭頭函數(shù)只有一條語(yǔ)句時(shí),甚至可以省略return
關(guān)鍵字。
箭頭函數(shù)是匿名的,這意味著name
屬性是一個(gè)空字符串''
。這樣它就沒(méi)有詞法上函數(shù)名(函數(shù)名對(duì)于遞歸、分離事件處理程序非常有用)
同時(shí),跟常規(guī)函數(shù)相反,它也不提供arguments
對(duì)象。但是,這在ES6中通過(guò)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
7.1、箭頭函數(shù)中的this
this 定義箭頭函數(shù)的封閉上下文
箭頭函數(shù)不會(huì)創(chuàng)建自己的執(zhí)行上下文,而是從定義它的外部函數(shù)中獲取 this
。 換句話說(shuō),箭頭函數(shù)在詞匯上綁定 this
。
下面的例子說(shuō)明了這個(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();
setTimeout
使用與log()
方法相同的上下文(myPoint
對(duì)象)調(diào)用箭頭函數(shù)。正如所見(jiàn),箭頭函數(shù)從定義它的函數(shù)繼承上下文。
如果在這個(gè)例子里嘗試用常規(guī)函數(shù),它創(chuàng)建自己的上下文(window
或嚴(yán)格模式下的undefined
)。因此,要使相同的代碼正確地使用函數(shù)表達(dá)式,需要手動(dòng)綁定上下文:setTimeout(function(){…}.bind(this))
。這很冗長(zhǎng),使用箭頭函數(shù)是一種更簡(jiǎn)潔、更短的解決方案。
如果箭頭函數(shù)在最頂層的作用域中定義(在任何函數(shù)之外),則上下文始終是全局對(duì)象(瀏覽器中的 window):
onst getContext = () => { console.log(this === window); // => true return this; }; console.log(getContext() === window); // => true
箭頭函數(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);
無(wú)論如何調(diào)用箭頭函數(shù)get
,它總是保留詞匯上下文numbers
。 用其他上下文的隱式調(diào)用(通過(guò) get.call([0])
或get.apply([0])
)或者重新綁定(通過(guò).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ù)來(lái)聲明一個(gè)對(duì)象上的方法。箭頭函數(shù)的定義相比于函數(shù)表達(dá)式短得多:(param) => {...} instead of function(param) {..}
。
來(lái)看看例子,用箭頭函數(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'
由于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è)問(wèn)題,因?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'
walkPeriod.format()
是一個(gè)對(duì)象上的方法調(diào)用,它的上下文是walkPeriod
對(duì)象。this.hours
等于2
,this.minutes
等于30
,所以這個(gè)方法返回了正確的結(jié)果:'2 hours and 30 minutes'
。
關(guān)于javascript中的this關(guān)鍵字就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。