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