this指向原則透析

這是我參與8月更文挑戰的第5天,活動詳情查看:8月更文挑戰markdown

smalld9fcb449fa428b1cc001b40527b990761622906649.jpg

1.爲何要用this?

  • this提供了一種更優雅的方式來隱式傳遞 一個對象引用。
function identify() {
    return this.name.toUpperCase();
}
function speak() {
    var greeting = "Hello, I'm " + identify.call( this );
    console.log( greeting );
}
var me = {
    name: "Kyle"
};
var you = {
    name: "Reader"
};
identify.call( me ); // KYLE
identify.call( you ); // READER
speak.call( me ); // Hello, 我是 KYLE
speak.call( you ); // Hello, 我是 READE
複製代碼

這段代碼能夠在不一樣的上下文對象(me 和 you)中重複使用函數 identify() 和 speak(), 不用針對每一個對象編寫不一樣版本的函數。app

若是不使用 this,那就須要給 identify() 和 speak() 顯式傳入一個上下文對象。ide

function identify(context) {
    return context.name.toUpperCase();
}
function speak(context) {
    var greeting = "Hello, I'm " + identify( context );
    console.log( greeting );
}
identify( you ); // READER
speak( me ); //hello, 我是 KYLE
複製代碼

2. this四條綁定規則

this 關鍵字, 被自動定義在全部函數的做用域中。函數

注意:this的指向在函數建立時肯定不了,被調用時才能肯定。post

咱們在找this指向時,要先找到調用位置,而後判斷符合哪一條綁定規則,同時注意綁定規則的優先級。ui

2.1 默認綁定

在找不到函數的調用對象時,this

  • 非嚴格模式:函數中this會指向全局對象window。
  • 嚴格模式下,函數中this會指向 undefined。

注意: 對於默認綁定來講,決定 this 綁定對象的並非調用位置是否處於嚴格模式,而是函數體是否處於嚴格模式編碼

例1:函數調用前面沒有對象spa

function sayHi(){
    console.log('Hello,', this.name);
}
    var name = 'YvetteLau';
    sayHi();
    
// 'Hello,' YvetteLau
複製代碼

2.2 隱式綁定

經過對象調用函數,那麼函數中this會指向最後調用它的對象。code

隱式丟失

1.將obj.fn()賦值給某個變量

雖然 bar 是 obj.foo 的一個引用,可是實際上,它引用的是 foo 函數自己,所以此時的 bar() 實際上是一個不帶任何修飾的函數調用,所以應用了默認綁定。

以下示例, o.foothis 隱式綁定在了 o 對象上, 而 bar 引用了 o.foo 函數自己, 因此此時的 bar() 實際上是一個不帶任何修飾的函數調用, 所以使用了 默認綁定 規則:

var o = {
    a: 1,
    foo() {
        console.log(this.a)
    }
}
​
var bar = o.foo
​
o.foo() // 1
bar()   // undefined
複製代碼

2.將obj.fn()賦值給函數的形參

參數傳遞其實就是一種隱式賦值,所以咱們傳入函數時也會被隱式賦值。示例中, bar(o.foo) 實際上採用了隱式賦值: callback = o.foo, 事實上跟上面的例子同樣, 都是直接引用了 o.foo 函數自己, 因此形成了 隱式丟失:

function bar(callback) {
    callback()
}
​
var o = {
    a: 1,
    foo() {
        console.log(this.a)
    }
}
​
bar(o.foo) // undefined
複製代碼

3.setTimeOut函數中的this

例2:setTimeOut中包含的函數,在後面執行時,並無對象去調用這個函數,因此也是默認綁定。

btn.onclick = function(){
    setTimeout(function(){
        console.log(this);
    },0)
}
btn.onclick();

// window
複製代碼

雖然onclick函數是btn對象調用的,但setTimeout函數中的內容並沒有在btn對象調用onclick函數時當即執行,等到setTimeout函數執行時,setTimeout函數找不到調用對象,這時變爲默認綁定,非嚴格模式下,setTimeout函數內的this指向window

(setTimeOut任務是宏任務,會在下一輪事件循環執行。具體參見:juejin.cn/post/693532…)

2.3 顯示綁定

咱們有時想要強制爲某個函數綁定this,能夠經過使用 call()apply() 方法來實現;

函數.call(要綁定this的對象, 參數1, 參數2, ...)

函數.apply(要綁定this的對象, [參數1, 參數2, ...])

硬綁定

function foo() {
    console.log( this.a );
}
var obj = {
    a:2
};
var bar = function() {
    foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬綁定的 bar 不可能再修改它的 this
bar.call( window ); // 2
​
複製代碼

咱們建立了函數 bar(),並在它的內部手動調用 了 foo.call(obj) ,所以強制把 foo 的 this 綁定到了 obj。不管以後如何調用函數 bar,它 總會手動在 obj 上調用 foo。這種綁定是一種顯式的強制綁定,所以咱們稱之爲硬綁定。

硬綁定ES5API

bind(..) 會返回一個硬編碼的新函數,它會把參數設置爲 this 的上下文並調用原始函數。

硬綁定時傳入null/undefined,則會忽略本次硬綁定,採用默認綁定規則。

2.4 new綁定

當使用 new 操做符調用某個函數時,這個函數被構造調用

new 操做符在調用函數時作的工做:

  • 建立或構造了一個全新的對象
  • 這個新對象會被執行[[原型]]鏈接
  • 函數中的 this 會指向這個新對象
  • 若是被調用的函數沒有返回(return), 則 new 表達式中的函數調用會自動返回這個新對象

3. 綁定規則的優先級

new綁定 > 顯式綁定 > 隱式綁定 > 默認綁定

判斷this:

  1. 函數是否在 new 中調用(new 綁定)?若是是的話 this 綁定的是新建立的對象。
  2. 函數是否經過 call、apply(顯式綁定)或者硬綁定調用?若是是的話,this 綁定的是 指定的對象。
  3. 函數是否在某個上下文對象中調用(隱式綁定)?若是是的話,this 綁定的是那個上 下文對象。
  4. 若是都不是的話,使用默認綁定。若是在嚴格模式下,就綁定到 undefined,不然綁定到全局對象。

4. 箭頭函數

箭頭函數沒法使用上述規則。根據根據外層(函數或者全局)做用域來決定 this。

箭頭函數的this指向它外層做用域調用時的this。

foo() 內部建立的箭頭函數會捕獲調用時 foo() 的 this。因爲 foo() 的 this 綁定到 obj1, bar(引用箭頭函數)的 this 也會綁定到 obj1,箭頭函數的綁定沒法被修改。(new 也不行!)

function foo() {
    // 返回一個箭頭函數
    return (a) => {
        //this 繼承自 foo()
        console.log( this.a );
    };
}
var obj1 = {
    a:2
};
var obj2 = {
    a:3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3 
複製代碼

箭頭函數會繼承外層函數調用的 this 綁定(不管 this 綁定到什麼)。

參考:

你不知道的JavaScript

相關文章
相關標籤/搜索