javascript
JavaScript中this的五种绑定方式详解
1 this的五種綁定方式
1.1 默認綁定
默認綁定是指當函數調用時,沒有為其指定對象上下文,此時會將該函數的this綁定到全局對象(window對象)。自ES5有了嚴格模式之后,默認綁定方式又分為非嚴格模式的默認綁定和嚴格模式的默認綁定。
1.1.1 非嚴格模式
在非嚴格模式下,函數的默認綁定只能綁定到全局對象window,見下面例子:
var myName = 'syzdev' // 相當于 window.myName = 'syzdev' function sayName() {// 這里的 this === windowconsole.log(this === window); // trueconsole.log(this.myName); // syzdev } sayName()在這個例子中,首先聲明了一個全局變量myName:
var聲明的變量為全局變量,并且會將該變量添加為全局對象window的屬性,需要注意的是,例子中的var不可修改為ES6中的let或const,不然結果大有不同,有興趣的讀者可以查閱資料“var、let、const的區別”。
再定義了一個sayName()函數,在函數中輸出this.myName,最后直接調用sayName()函數,再調用時,由于沒有指定其對象上下文,所以觸發了默認綁定,即將函數中的this綁定到全局對象window上,因此在函數內this === window,最后輸出的this.myName為 syzdev。
1.1.2 嚴格模式
關于嚴格模式,這里不做介紹,若不了解的讀者可以閱讀MDN-嚴格模式。
在嚴格模式下,有一項規定為“禁止this關鍵字指向全局對象window”,此時this會綁定到undefined,見下面例子:
// 開啟嚴格模式 'use strict' var myName = 'syzdev' function sayName() {// 這里的 this !== windowconsole.log(this === window); // falseconsole.log(this); // undefinedconsole.log(this.myName); // 報錯:Uncaught TypeError: Cannot read properties of undefined (reading 'myName') } sayName()在這個例子中,在代碼頂部使用'use strict'聲明了嚴格模式,由于在嚴格模式下禁止this綁定到全局對象window,所以函數中的this !== window,輸出this為undefined,因此輸出this.myName時會直接拋出錯誤“Uncaught TypeError: Cannot read properties of undefined (reading ‘name’)”。
由于嚴格模式可以聲明為全局或個別函數內,上面的例子中就是聲明為全局嚴格模式,若聲明為函數內嚴格模式,偽代碼如下:
function sayName() {'use strict'... }需要注意的是,嚴格模式并不會影響到調用函數內的默認綁定,見下面例子:
var myName = 'syzdev' function sayName() {// 這里的 this === windowconsole.log(this === window); // trueconsole.log(this.myName); // syzdev }function strictSayName() {// 函數內的嚴格模式'use strict'sayName() } strictSayName()在這個例子中,定義函數strictSayName()并在函數內部聲明嚴格模式,再調用sayName()函數,此時sayName()函數內部依然能夠將this綁定到全局對象window上。
1.2 隱式綁定
隱式綁定是日常開發中最為常見的綁定方式,即調用函數時為函數指明其對象上下文,此時函數中的this就為對象本身,見下面例子:
var personObj = {myName: 'syzdev',sayName: function() {console.log(this === personObj) // trueconsole.log(this.myName); // syzdev} } personObj.sayName()在這個例子中,定義一個personObj對象,包含myName屬性和sayName()方法,通過對象personObj.sayName()調用函數,此時就會觸發this隱式綁定,函數中的this就是personObj本身。
但隱式綁定還可能會出現綁定丟失的情況,此時隱式綁定就會變成了默認綁定,見下面例子:
var personObj = {myName: '123',sayName: function() {console.log(this === window); // trueconsole.log(this.myName); // undefined} } var anotherSayName = personObj.sayName anotherSayName()在這個例子中,把personObj.sayName()函數賦值給了變量anotherSayName,再直接使用anotherSayName()調用,此時雖然調用的還是sayName()函數,但是卻丟失了對象上下文,此時隱式綁定變成了默認綁定,anotherSayName()函數內的this綁定到全局對象window上,由于window對象上沒有myName屬性,所以輸出undefined。
1.3 顯式綁定
顯示綁定顧名思義,通過調用call/apply/bind方法,強制將函數中的this綁定到指定的對象,三者區別如下:
- call()接受的是多個對象作為參數。
- apply()接受的是多個對象組成的數組作為參數。
- bind()返回的是一個函數,除此之外使用方法與call()一樣。
使用call/apply/bind實現顯示綁定的方法如下:
在這個例子中,直接調用sayName()函數會觸發默認綁定,函數內的this綁定到全局對象window,所以輸出window myName。由于該例子中函數不需要傳參,所以在使用call/apply/bind方法時,不需要為其傳遞除this對象外的其他參數,其效果都是一致的,都是將sayNname()函數內的this綁定到personObj對象,最后輸出的myName為personObj對象中的personObj myName。
代碼詳解見注釋。
1.4 new綁定
在ES6以前,生成一個實例對象的方法是使用構造函數,構造函數只是一個普通的函數,當使用new操作符調用構造函數時,JavaScript解釋器便會在底層創建一個新對象,構造函數內的this綁定的就是這個新對象,見下面例子:
function Person(name, age) {this.name = namethis.age = age } var person = new Person('syzdev', 18)在這個例子中,使用new操作符執行一個構造函數,創建一個person對象,在構造函數內會創建一個新的對象,將函數內的this綁定到這個新對象上,如果函數內部沒有返回其他對象,則構造函數會默認返回這個新對象,流程如下圖所示:
1.5 箭頭函數綁定
箭頭函數是ES6中的一個重要特性,在箭頭函數中沒有自己的this,其this是根據外層的作用域來決定的,箭頭函數內的this指的是定義時所在的對象,是在函數定義時已經決定了,而不是像普通函數一樣在調用時綁定this對象,見下面例子:
var foo = () => {console.log(this.myName); }var myName = 'window myName' var personObj = {myName: 'personObj myName' }foo() // window myName foo.call(personObj) // window myName foo.apply(personObj) // window myName foo.bind(personObj)() // window myName在這個例子中,函數foo定義為箭頭函數,由于箭頭函數沒有自己的this,其this值在定義時已經決定,所以無法被call/apply/bind方法所改變,在該例子中其this值為全局對象window,所以四種方法調用foo()函數的結果都為window myName。
2 this綁定的優先級
所謂this綁定的優先級,指的是當函數執行時若同時指定了多個this對象時的綁定順序。
在第2章的例子中已經證明了這個結論:
var personObj = {myName: 'syzdev',sayName: function() {console.log(this === personObj) // trueconsole.log(this.myName); // syzdev} } // 相當于 window.personObj.sayName() personObj.sayName()在這個例子中,personObj.sayName()為隱式綁定,而personObj.sayName()相當于window.personObj.sayName()可見window對象的默認綁定失效了,最終執行的this依舊是personObj,所以隱式綁定 > 默認綁定。
修改第3章的例子如下:
function foo() {console.log(this.myName); }var myName = 'window myName' var personObj = {myName: 'personObj myName',foo: foo // 修改部分 }personObj.foo.call(window) // window myName在上面例子中的personObj.foo為隱式綁定,再通過call將foo()函數中的this顯示綁定到全局對象window上,最終輸出的結果還是window myName,可見顯示綁定 > 隱式綁定。
在這個例子中,使用bind方法將obj綁定到foo()函數中并返回一個新的函數bindFoo,在使用new操作符創建一個新的對象bindObj時,函數bindFoo()的this指向發生了改變,原本函數bindFoo()中的this指向的是obj,在使用new操作符后指向了一個新的對象并返回賦值給了bindObj。
2.2 總結
在討論this綁定優先級時之所以不討論箭頭函數,是因為箭頭函數的this在其函數定義時就已經確定,不存在優先級一說。其他四種綁定方式的優先級如下,由高到低:
總結
以上是生活随笔為你收集整理的JavaScript中this的五种绑定方式详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSS层叠上下文、层叠顺序和层叠等级
- 下一篇: JavaScript数组方法大全解