javascript
javascript中令人迷惑的this
JS中的this很多時(shí)候會(huì)讓人捉摸不透,不知道這個(gè)this到底指向的是什么。現(xiàn)在根據(jù)自己的理解寫下這篇文章做一個(gè)總結(jié)。
我們知道this指向誰(shuí)一般情況下是在運(yùn)行時(shí)決定的,并且運(yùn)行時(shí)決定this指向的因素又有很多,例如是不是被bind了,或者調(diào)用的時(shí)候使用了apply和call這類方法,還有是不是通過new來(lái)調(diào)用這個(gè)函數(shù),如果沒有以上顯示綁定,那么是obj.fn()這樣調(diào)用的嗎?或者直接fn()?如果直接fn()調(diào)用,那么fn的函數(shù)體是嚴(yán)格模式嗎?最后這個(gè)函數(shù)是ES6中的箭頭函數(shù)嗎?
默認(rèn)綁定和隱式綁定
首先看最常見的調(diào)用方式,通過對(duì)象調(diào)用這個(gè)函數(shù)或者叫方法(this隱式綁定到了調(diào)用函數(shù)的對(duì)象上)。
var a = 2 var obj = {a: 1,fn: function () {console.log(this.a)} }obj.fn()我們通過運(yùn)行知道打印了1,也就是說(shuō)這時(shí)fn中的this指向了調(diào)用他的obj,但是是否表示任何情況下fn中的this都是指向fn定義時(shí)的對(duì)象obj呢?顯然不是的。在某些情況下這種隱式綁定的this會(huì)丟失,如下:
var fn1 = obj.fn fn1()上面打印了2,是的出乎意料并沒有打印1,所以關(guān)于this的指向和函數(shù)的定義沒有什么關(guān)系,看似函數(shù)fn屬于對(duì)象obj,其實(shí)并不是。這時(shí)fn中this默認(rèn)指向的是window對(duì)象。上面通過賦值就弄丟了原本的隱式綁定,沒有了隱式綁定,只能使用默認(rèn)綁定。
現(xiàn)在我們切換到嚴(yán)格模式:
'use strict' var a = 2 var obj = {a: 1,fn: function () {console.log(this.a)} }var fn1 = obj.fn fn1()我們發(fā)現(xiàn)執(zhí)行fn1的時(shí)候報(bào)了一個(gè)錯(cuò)誤Uncaught TypeError: Cannot read property 'a' of undefined,這是因?yàn)樵趪?yán)格模式下fn1中的this指向的undefined,獲取undefined的屬性當(dāng)然會(huì)報(bào)錯(cuò),因?yàn)閡ndefined不是一個(gè)對(duì)象也不能隱式轉(zhuǎn)換成一個(gè)對(duì)象。
注:上面通過將對(duì)象的方法賦值給一個(gè)變量導(dǎo)致函數(shù)的方法中默認(rèn)綁定this丟失,這種情況會(huì)出現(xiàn)在很多其他意想不到的地方,例如函數(shù)的傳參(這也是一種隱式的賦值)。
顯示調(diào)用call和apply的綁定
上面無(wú)論是通過對(duì)象還是直接通過函數(shù)名調(diào)用函數(shù),其中的this指向誰(shuí)好像編譯器心里有數(shù)是一種默契。那么我們能不能不要這個(gè)默契,我們自己來(lái)指定函數(shù)調(diào)用的時(shí)候this指向誰(shuí)。我們通過call方法和apply方法就可以輕易做到。
var a = 2 var obj = {a: 1,fn: function () {console.log(this.a)} } var fn1 = obj.fn fn1.call(obj)我們可以看到又打印出了1,不負(fù)所望。調(diào)用fn1的時(shí)候我們通過結(jié)果可以知道函數(shù)體內(nèi)的this被綁定到了obj上。apply做的事情和call是一樣的,區(qū)別就在于傳入函數(shù)中參數(shù)的形式,call必須要和調(diào)用函數(shù)一樣一個(gè)一個(gè)傳入?yún)?shù),但是apply允許我們通過一個(gè)數(shù)組將需要的參數(shù)一起傳入函數(shù)中。這個(gè)神奇的功能就像是ES6中的 … 操作符。
bind的綁定
bind的也可以綁定函數(shù)中的this,但是和上面的call和apply有明顯的不同,call和apply是直接就執(zhí)行了函數(shù),但是bind不是,bind會(huì)返回一個(gè)函數(shù),這個(gè)特性就讓這個(gè)bind不僅僅可以綁定this,還可以進(jìn)行函數(shù)柯里化。
var a = 2 var obj = {a: 1,fn: function () {console.log(this.a)} } var fn1 = obj.fn var fn2 = fn1.bind(obj) fn2()不出意料的也打印了1。
bind的簡(jiǎn)單Polyfill
if (!Function.prototype.bind) {Function.prototype.bind = function (obj) {// 這一句檢測(cè)this是不是函數(shù)我本以為是多余,但是bind是可以被call的,這時(shí)候this就很有可能不指向functionif (typeof this !== 'function') {throw new TypeError('不是函數(shù)的數(shù)據(jù)嘗試調(diào)用bind方法!')}// obj 就是函數(shù)要綁定的this,而函數(shù)就是現(xiàn)在函數(shù)體中的this,因?yàn)閎ind函數(shù)是在Function.prototype上的// 這是在獲取在bind的時(shí)候就傳過來(lái)的參數(shù)var args = [].slice.call(arguments, 1)// 存一下需要bind的函數(shù)var fn = this // 處理fn函數(shù)的prototype屬性var _fn = function () {}// 這個(gè)函數(shù)將被返回var bindFn = function () {// 處理一下被bind的函數(shù)使用new調(diào)用的時(shí)候return fn.apply(this instanceof bindFn ? this : obj, args.concat([].slice.call(arguments)))} // 處理fn.prototypeif (fn.prototype) {_fn.prototype = fn.prototype}bindFn.prototype = new _fn()return bindFn} }上面的兼容是類似mozilla的寫法,不僅僅綁定了this還考慮到了參數(shù)的拼接,還有函數(shù)的prototype屬性的處理,還包括被bind的函數(shù)作為構(gòu)造函數(shù)調(diào)用的時(shí)候其中this的指向。
new的this綁定
可以綁定this的不僅僅是上面的call,apply和bind,new也可以的。我們知道通過new調(diào)用一個(gè)函數(shù)的時(shí)候會(huì)有下面幾個(gè)步驟:
看上面第二步就將函數(shù)中的this關(guān)聯(lián)到了新建的對(duì)象上了。那么對(duì)于一個(gè)bind的函數(shù)我們使用new來(lái)調(diào)用函數(shù)中的this到底是指向了new新建出來(lái)的對(duì)象還是bind時(shí)候的對(duì)象呢?
其實(shí)上面bind方法的Polyfill已經(jīng)給出了答案,是會(huì)指向new新建出來(lái)的對(duì)象。fn.apply(this instanceof bindFn ? this : obj, args.concat([].slice.call(arguments))),這里通過this instanceof bindFn判斷是不是通過new調(diào)用的該方法,如果是那么就指向當(dāng)前的this也就是新建的對(duì)象,如果不是才指向傳進(jìn)來(lái)的obj。
##綁定的優(yōu)先級(jí)
通過上面bind的Polyfill我們知道new綁定的this優(yōu)先級(jí)要大于顯示綁定的bind,并且bind的綁定優(yōu)先級(jí)要高于call和apply方法。
隱藏是綁定優(yōu)先級(jí)要高于默認(rèn)綁定并且低于顯示綁定的call和apply方法。
所以整理出來(lái)的優(yōu)先級(jí)如下:
new > bind > (apply == call) > 隱式綁定 > 默認(rèn)綁定
關(guān)于箭頭函數(shù)
ES6的箭頭函數(shù)和上面說(shuō)的情況都不一樣,箭頭函數(shù)中的this指向并不是在調(diào)用的時(shí)候確定的,而是在定義的時(shí)候,和定義的時(shí)候的詞法作用域有關(guān),并且后期并不能通過上面顯示綁定的方法修改this的指向。也就是說(shuō)箭頭函數(shù)定義的時(shí)候拿到當(dāng)前上下文的this,然后就不會(huì)再改變了。
var a = 2 var obj = {a: 1,fn: function () {console.log(this.a)var b = () => {console.log(this)}b()} } obj.fn()上面打印出了1 和 obj 對(duì)象
var a = 2 var obj = {a: 1,fn: function () {console.log(this.a)var b = () => {console.log(this)}b.call(window)} } obj.fn()雖然使用了call指定this綁定,但是還是打印了1和obj對(duì)象,而不是window。call方法并沒能修改箭頭函數(shù)的this指向。
var a = 2 var obj = {a: 1,fn: function () {console.log(this.a)var b = () => {console.log(this)}var c = b.bind(window)c()} } obj.fn()結(jié)果和call的綁定一致沒有改變箭頭函數(shù)中的this。
那么能使用new呢?箭頭函數(shù)不能使用new調(diào)用,會(huì)報(bào)錯(cuò)的。
參考
你不知道的javascript上卷
總結(jié)
以上是生活随笔為你收集整理的javascript中令人迷惑的this的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mongoose中的populate之多
- 下一篇: JS相关知识总结(一)