JavaScript中this綁定詳解
this可以說是 javascript 中最耐人尋味的一個(gè)特性,就像高中英語里各種時(shí)態(tài),比如被動(dòng)時(shí)態(tài),過去時(shí),現(xiàn)在時(shí),過去進(jìn)行時(shí)一樣,無論弄錯(cuò)過多少次,下一次依然可能弄錯(cuò)。本文啟發(fā)于《你不知道的JavaScript上卷》,對(duì) javasript 中的this進(jìn)行一個(gè)總結(jié)。
學(xué)習(xí)this的第一步就是明白this既不是指向函數(shù)自身也不指向函數(shù)的作用域。this實(shí)際上是在函數(shù)被調(diào)用時(shí)發(fā)生的綁定,它指向什么地方完全取決于函數(shù)在哪里被調(diào)用。
默認(rèn)綁定在 javascript 中 ,最常用的函數(shù)調(diào)用類型就是 獨(dú)立函數(shù)調(diào)用 ,因此可以把這條規(guī)則看作是無法應(yīng)用其他規(guī)則時(shí)的默認(rèn)規(guī)則。如果在調(diào)用函數(shù)的時(shí)候,函數(shù)不帶任何修飾,也就是“光禿禿”的調(diào)用,那就會(huì)應(yīng)用 默認(rèn)綁定規(guī)則
function sayLocation() { console.log(this.atWhere)}var atWhere = 'I am in global'sayLocation() // 默認(rèn)綁定,this綁定在全局對(duì)象,輸出 “I am in global”
再看一個(gè)例子
var name = 'global'function person() { console.log(this.name) // (1) 'global' person.name = ’inside’ function sayName() {console.log(this.name) // (2) 'global' 不是 'inside' } sayName() // 在person函數(shù)內(nèi)部執(zhí)行sayName函數(shù),this指向的同樣是全局的對(duì)象}person()
在這個(gè)例子中,person函數(shù)在全局作用域中被調(diào)用,因此第(1)句中的this就綁定在了全局對(duì)象上(在瀏覽器中是是window,在node中就是global),因此第(1)句自然輸出的是一個(gè)全局對(duì)象的name屬性,當(dāng)然就是'global'了。sayName函數(shù)在person函數(shù)內(nèi)調(diào)用,即使這樣第(2)句中的this指代的仍然是全局對(duì)象,即使person函數(shù)設(shè)置了 name 屬性。
這就是 默認(rèn)綁定規(guī)則 ,它是 javascript 中最常見的一種函數(shù)調(diào)用模式,this的綁定規(guī)則也是四種綁定規(guī)則中最簡(jiǎn)單的一種,就是 函數(shù)在哪個(gè)作用域被調(diào)用,this 就綁定在這個(gè)作用域上 。
默認(rèn)綁定里的嚴(yán)格模式在 javascript 中,如果使用了嚴(yán)格模式,則this不能綁定到全局對(duì)象。還是以第一個(gè)例子,只不過這次加上了嚴(yán)格模式聲明
’use strict’function sayLocation() { console.log(this.atWhere)}var atWhere = 'I am in global'sayLocation()// Uncaught TypeError: Cannot read property ’atWhere’ of undefined
可以看出,在嚴(yán)格模式下,把this綁定到全局對(duì)象上時(shí),實(shí)際上綁定的是undefined,因此上面這段代碼會(huì)報(bào)錯(cuò)。
隱式綁定當(dāng)函數(shù)在調(diào)用時(shí),如果函數(shù)有所謂的“落腳點(diǎn)”,即有上下文對(duì)象時(shí),隱式綁定規(guī)則會(huì)把函數(shù)中的this綁定到這個(gè)上下文對(duì)象。如果覺得上面這段話不夠直白的話,還是來看代碼。
function say() { console.log(this.name)}var obj1 = { name: 'zxt', say: say}var obj2 = { name: 'zxt1', say: say}obj1.say() // zxtobj2.say() // zxt1
很簡(jiǎn)單是不是。在上面這段代碼中,obj1,obj2就是所謂的say函數(shù)的落腳點(diǎn),專業(yè)一點(diǎn)的說法就是上下文對(duì)象,當(dāng)給函數(shù)指定了這個(gè)上下文對(duì)象時(shí),函數(shù)內(nèi)部的this自然指向了這個(gè)上下文對(duì)象。這也是很常見的一種函數(shù)調(diào)用模式。
隱式綁定時(shí)丟失上下文function say() { console.log(this.name)}var name = 'global'var obj = { name: 'inside', say: say}var alias = obj.say // 設(shè)置一個(gè)簡(jiǎn)寫 (1) alias() // 函數(shù)調(diào)用 輸出'global' (2)
可以看到這里輸出的是”global“,為什么就和上例中不一樣,我們明明只是給obj.say換了個(gè)名字而已?
首先我們來看上面第(1)句代碼,由于在 javascript 中,函數(shù)是對(duì)象,對(duì)象之間是引用傳遞,而不是值傳遞。因此,第(1)句代碼只是alias = obj.say = say,也就是alias = say,obj.say只是起了一個(gè)橋梁的作用,alias最終引用的是say函數(shù)的地址,而與 obj 這個(gè)對(duì)象無關(guān)了。這就是所謂的”丟失上下文“。最終執(zhí)行alias函數(shù),只不過簡(jiǎn)單的執(zhí)行了say函數(shù),輸出'global'。
顯式綁定顯式綁定,顧名思義,顯示地將this綁定到一個(gè)上下文,javascript中,提供了三種顯式綁定的方法,apply,call,bind。apply和call的用法基本相似,它們之間的區(qū)別是:
apply(obj,[arg1,arg2,arg3,...]被調(diào)用函數(shù)的參數(shù)以數(shù)組的形式給出
call(obj,arg1,arg2,arg3,...)被調(diào)用函數(shù)的參數(shù)依次給出
而bind函數(shù)執(zhí)行后,返回的是一個(gè)新函數(shù)。下面以代碼說明。
// 不帶參數(shù)function speak() { console.log(this.name)}var name = 'global'var obj1 = { name: ’obj1’}var obj2 = { name: ’obj2’}speak() // global 等價(jià)于speak.call(window)speak.call(window)speak.call(obj1) // obj1speak.call(obj2) // obj2
因此可以看出,apply,call的作用就是給函數(shù)綁定一個(gè)執(zhí)行上下文,且是顯式綁定的。因此,函數(shù)內(nèi)的this自然而然的綁定在了call或者apply所調(diào)用的對(duì)象上面。
// 帶參數(shù)function count(num1, num2) { console.log(this.a * num1 + num2)}var obj1 = { a: 2}var obj2 = { a: 3}count.call(obj1, 1, 2) // 4count.apply(obj1, [1, 2]) // 4count.call(obj2, 1, 2) // 5count.apply(obj2, [1, 2]) // 5
上面這個(gè)例子則說明了apply和call用法上的差異。
而bind函數(shù),則返回一個(gè)綁定了指定的執(zhí)行上下文的新函數(shù)。還是以上面這段代碼為例
// 帶參數(shù)function count(num1, num2) { console.log(this.a * num1 + num2)}var obj1 = { a: 2}var bound1 = count.bind(obj1) // 未指定參數(shù)bound1(1, 2) // 4var bound2 = count.bind(obj1, 1) // 指定了一個(gè)參數(shù)bound2(2) // 4 var bound3 = count.bind(obj1, 1, 2) // 指定了兩個(gè)參數(shù)bound3() //4var bound4 = count.bind(obj1, 1, 2, 3) // 指定了多余的參數(shù),多余的參數(shù)會(huì)被忽略bound4() // 4
所以,bind方法只是返回了一個(gè)新的函數(shù),這個(gè)函數(shù)內(nèi)的this指定了執(zhí)行上下文,而返回這個(gè)新函數(shù)可以接受參數(shù)。
new 綁定最后要講的一種this綁定規(guī)則,是指通過new操作符調(diào)用構(gòu)造函數(shù)時(shí)發(fā)生的this綁定。首先要明確一點(diǎn)的是,在 javascript 中并沒有其他語言那樣的類的概念。構(gòu)造函數(shù)也僅僅是普通的函數(shù)而已,只不過構(gòu)造函數(shù)的函數(shù)名以大寫字母開頭,也只不過它可以通過new操作符調(diào)用而已.
function Person(name,age) { this.name = name this.age = age console.log('我也只不過是個(gè)普通函數(shù)')}Person('zxt',22) // '我也只不過是個(gè)普通函數(shù)'console.log(name) // 'zxt'console.log(age) // 22var zxt = new Person('zxt',22) // '我也只不過是個(gè)普通函數(shù)'console.log(zxt.name) // 'zxt'console.log(zxt.age) // 22
上面這個(gè)例子中,首先定義了一個(gè)Person函數(shù),既可以普通調(diào)用,也可以以構(gòu)造函數(shù)的形式的調(diào)用。當(dāng)普通調(diào)用時(shí),則按照正常的函數(shù)執(zhí)行,輸出一個(gè)字符串。 如果是通過一個(gè)new操作符,則構(gòu)造了一個(gè)新的對(duì)象。那么,接下來我們?cè)倏纯磧煞N調(diào)用方式,this分別綁定在了何處首先普通調(diào)用時(shí),前面已經(jīng)介紹過,此時(shí)應(yīng)用默認(rèn)綁定規(guī)則,this綁定在了全局對(duì)象上,此時(shí)全局對(duì)象上會(huì)分別增加name和age兩個(gè)屬性。當(dāng)通過new操作符調(diào)用時(shí),函數(shù)會(huì)返回一個(gè)對(duì)象,從輸出結(jié)果上來看this對(duì)象綁定在了這個(gè)返回的對(duì)象上。
因此,所謂的new綁定是指通過new操作符來調(diào)用函數(shù)時(shí),會(huì)產(chǎn)生一個(gè)新對(duì)象,并且會(huì)把構(gòu)造函數(shù)內(nèi)的this綁定到這個(gè)對(duì)象上。
事實(shí)上,在javascript中,使用new來調(diào)用函數(shù),會(huì)自動(dòng)執(zhí)行下面的操作。
創(chuàng)建一個(gè)全新的對(duì)象
這個(gè)新對(duì)象會(huì)被執(zhí)行原型連接
這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的this
如果函數(shù)沒有返回其他對(duì)象,那么new表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象
四種綁定的優(yōu)先級(jí)上面講述了javascript中四種this綁定規(guī)則,這四種綁定規(guī)則基本上涵蓋了所有函數(shù)調(diào)用情況。但是如果同時(shí)應(yīng)用了這四種規(guī)則中的兩種甚至更多,又該是怎么樣的一個(gè)情況,或者說這四種綁定的優(yōu)先級(jí)順序又是怎么樣的。
首先,很容易理解, 默認(rèn)綁定的優(yōu)先級(jí)是最低的 。這是因?yàn)橹挥性跓o法應(yīng)用其他this綁定規(guī)則的情況下,才會(huì)調(diào)用默認(rèn)綁定。那隱式綁定和顯式綁定呢?還是上代碼吧,代碼可從來不會(huì)說謊。
function speak() { console.log(this.name)}var obj1 = { name: ’obj1’, speak: speak}var obj2 = { name: ’obj2’}obj1.speak() // obj1 (1)obj1.speak.call(obj2) // obj2 (2)
所以在上面代碼中,執(zhí)行了obj1.speak(),speak函數(shù)內(nèi)部的this指向了obj1,因此(1)處代碼輸出的當(dāng)然就是obj1,但是當(dāng)顯式綁定了speak函數(shù)內(nèi)的this到obj2上,輸出結(jié)果就變成了obj2,所有從這個(gè)結(jié)果可以看出 顯式綁定 的優(yōu)先級(jí)是要高于 隱式綁定 的。事實(shí)上我們可以這么理解obj1.speak.call(obj2)這行代碼,obj1.speak只是間接獲得了speak函數(shù)的引用,這就有點(diǎn)像前面所說的 隱式綁定丟失了上下文 。好,既然 顯式綁定 的優(yōu)先級(jí)要高于 隱式綁定 ,那么接下來再來比較一下new 綁定和顯式綁定。
function foo(something) { this.a = something}var obj1 = {}var bar = foo.bind(obj1) // 返回一個(gè)新函數(shù)bar,這個(gè)新函數(shù)內(nèi)的this指向了obj1 (1)bar(2) // this綁定在了Obj1上,所以obj1.a === 2console.log(obj1.a)var baz = new bar(3) // 調(diào)用new 操作符后,bar函數(shù)的this指向了返回的新實(shí)例baz (2)console.log(obj1.a)console.log(baz.a)
我們可以看到,在(1)處,bar函數(shù)內(nèi)部的this原本指向的是obj1,但是在(2)處,由于經(jīng)過了new操作符調(diào)用,bar函數(shù)內(nèi)部的this卻重新指向了返回的實(shí)例,這就可以說明 new 綁定 的優(yōu)先級(jí)是要高于 顯式綁定 的。
至此,四種綁定規(guī)則的優(yōu)先級(jí)排序就已經(jīng)得出了,分別是
new 綁定 > 顯式綁定 > 隱式綁定 > 默認(rèn)綁定
箭頭函數(shù)中的this綁定箭頭函數(shù)是ES6里一個(gè)重要的特性。
箭頭函數(shù)的this是根據(jù)外層的(函數(shù)或者全局)作用域來決定的。函數(shù)體內(nèi)的this對(duì)象指的是定義時(shí)所在的對(duì)象,而不是之前介紹的調(diào)用時(shí)綁定的對(duì)象。舉一個(gè)例子
var a = 1var foo = () => { console.log(this.a) // 定義在全局對(duì)象中,因此this綁定在全局作用域}var obj = { a: 2}foo() // 1 ,在全局對(duì)象中調(diào)用foo.call(obj) // 1,顯示綁定,由obj對(duì)象來調(diào)用,但根本不影響結(jié)果
從上面這個(gè)例子看出,箭頭函數(shù)的 this 強(qiáng)制性的綁定在了箭頭函數(shù)定義時(shí)所在的作用域,而且無法通過顯示綁定,如apply,call方法來修改。在來看下面這個(gè)例子
// 定義一個(gè)構(gòu)造函數(shù)function Person(name,age) { this.name = name this.age = age this.speak = function (){console.log(this.name)// 普通函數(shù)(非箭頭函數(shù)),this綁定在調(diào)用時(shí)的作用域 } this.bornYear = () => {// 本文寫于2016年,因此new Date().getFullYear()得到的是2016// 箭頭函數(shù),this綁定在實(shí)例內(nèi)部console.log(new Date().getFullYear() - this.age)} }}var zxt = new Person('zxt',22)zxt.speak() // 'zxt'zxt.bornYear() // 1994// 到這里應(yīng)該大家應(yīng)該都沒什么問題var xiaoMing = { name: 'xiaoming', age: 18 // 小明永遠(yuǎn)18歲}zxt.speak.call(xiaoMing)// 'xiaoming' this綁定的是xiaoMing這個(gè)對(duì)象zxt.bornYear.call(xiaoMing)// 1994 而不是 1998,這是因?yàn)閠his永遠(yuǎn)綁定的是zxt這個(gè)實(shí)例
因此 ES6 的箭頭函數(shù)并不會(huì)使用四條標(biāo)準(zhǔn)的綁定規(guī)則,而是根據(jù)當(dāng)前的詞法作用域來決定this,具體來說就是,箭頭函數(shù)會(huì)繼承 外層函數(shù)調(diào)用的this綁定 ,而無論外層函數(shù)的this綁定到哪里。
小結(jié)以上就是javascript中所有this綁定的情況,在es6之前,前面所說的四種綁定規(guī)則可以涵蓋任何的函數(shù)調(diào)用情況,es6標(biāo)準(zhǔn)實(shí)施以后,對(duì)于函數(shù)的擴(kuò)展新增了箭頭函數(shù),與之前不同的是, 箭頭函數(shù)的作用域位于箭頭函數(shù)定義時(shí)所在的作用域 。
而對(duì)于之前的四種綁定規(guī)則來說,掌握每種規(guī)則的調(diào)用條件就能很好的理解this到底是綁定在了哪個(gè)作用域。
全文完
相關(guān)文章:
1. 詳解Android studio 動(dòng)態(tài)fragment的用法2. 解決Android studio xml界面無法預(yù)覽問題3. 圖文詳解vue中proto文件的函數(shù)調(diào)用4. Spring Boot和Thymeleaf整合結(jié)合JPA實(shí)現(xiàn)分頁效果(實(shí)例代碼)5. php模擬實(shí)現(xiàn)斗地主發(fā)牌6. 什么是python的自省7. Vue封裝一個(gè)TodoList的案例與瀏覽器本地緩存的應(yīng)用實(shí)現(xiàn)8. vue 使用localstorage實(shí)現(xiàn)面包屑的操作9. .Net Core使用Coravel實(shí)現(xiàn)任務(wù)調(diào)度的完整步驟10. Vuex localStorage的具體使用

網(wǎng)公網(wǎng)安備