上一篇文章中講了下this的作用和部分綁定規則JavaScript中this關鍵字(上) - 簡書,這篇文章將上篇中剩下的部分說完。
this綁定規則:
3 .顯示綁定:
在靜態綁定中可以看到,必須在一個對象內部包含一個指向函數的屬性,并通過這個屬性間接的去引用函數,從而把this隱式的綁定到這個對象上。
如果不想在對象內部包含函數的引用,而想在某個對象上強制調用函數,這就是顯示綁定,怎么做才能做到顯示綁定呢?js中所有的函數都有一些公有的方法,比如call(),apply(),bind()這三種方法。那這三種方法該怎么用?首先,這三個方法的第一個參數都可以接受一個對象,它們會把對象綁定到this上,接著在調用函數時指定this,這種方法稱為顯示綁定。這三者的區別是:call()的第二個參數開始接受的是單獨的參數,例如:xxx.call(obj,argument1,argument2);apply()的第二個參數開始則接受一個參數數組,例如:xxx.apply(obj,[args1,args2]);bind的第二個參數以及以后的參數加上綁定函數運行時本身的參數按照順序作為原函數的參數來調用原函數。
4.new綁定
用new的話一般是用于初始化構造函數(類)的時候用的多一些,比如我最近在寫svg的時候就用到構造函數(類)。使用方法如下:
在實例1中可以看到有一個svg的類,使用的時候用new就可以了。
new做了什么樣的操作呢?
1. 創建(或者說構造)一個全新的對象。
2. 這個新對象會被執行 [[ 原型 ]] 連接。
3. 這個新對象會綁定到函數調用的 this 。
4. 如果函數沒有返回其他對象,那么 new 表達式中的函數調用會自動返回這個新對象。
如上面兩張圖,在使用new來調用Svg(...)時,會構造一個新對象并把它綁定到Svg()調用中的this上。
現在我們已經大概了解了函數中調用this綁定的四條規則,我們需要做的就是找到函數的調用位置并判斷使用了那條規則。但如果某個調用位置可以應用多條規則該怎么辦?接下來我們將探索一下綁定規則的優先級。
毫無疑問,默認綁定的優先級是四條規則中最低的,我們先不考慮它
隱式綁定和顯示綁定哪個優先級更高?上代碼
可以看到,顯示綁定的優先級更高,也就是說在判斷時應當先考慮是否優先應用顯示綁定
那隱式綁定和new綁定哪個高呢?
可以看到new綁定要比隱式綁定優先級高,那new綁定和顯示綁定誰的優先級更高呢?
先回憶一下bind()是如何工作的,bind()會創建一個新的包裝函數,這個函數會忽略它當前的this綁定(無論綁定的對象是什么),并把提供的對象綁定到this上。這樣看起來要比new綁定的優先級更高,無法使用new來控制this的綁定。
從實例5中可以看到,bar被綁定到了obj1上,但new bar(3)并沒有像預計的那樣把obj1.a修改為3,相反,new修改了硬綁定調用bar()的this,因為使用new的來進行綁定,會得到一個名字為baz的新對象,并且baz.a的值是3。
所以綁定規則的優先級是:
new綁定 > 顯示綁定 >隱式綁定 >默認綁定
不過規則總有例外,在某些特定的場景中this的綁定行為會出乎意料。
1.忽略this
不知道大家有沒有遇到過這種情況:
function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2
如果把undefined或者null傳入到call,apply或者bind中,這些值在調用時會被忽略,this會使用到默認規則。
什么情況下會傳入null呢?
一種常見的做法就是使用apply來"展開"一個數組,并當做參數傳入一個函數
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
foo.apply( null, [2, 3] ); // a:2, b:3
如果函數并不關心this的話,仍然需要傳入一個站位值,比如null.
但是,如果函數確實使用了this,那默認綁定規則會把this綁定到全局對象(window)
2.間接引用
比如在賦值時發生的間接引用:
function foo() {
console.log(this.a);
}
vara=2;
varo={a:3,foo:foo};
varp={a:4};
o.foo();// 3
(p.foo=o.foo)();// 2
p.foo=o.foo的返回值是目標函數的引用,因此調用位置是foo()而不是p.foo()或者o.foo(),間接引用時,this也會采取默認綁定的規則。
3.箭頭函數
es6中提供了一個特殊函數類型:箭頭函數,它不適用于上面介紹的四種規則,實際上它是根據外層(函數或者全局)的作用域來決定this的。
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的綁定對象
1.new調用會綁定到新創建的對象
2.call或者apply或者bind則綁定到指定的對象
3.上下文調用則綁定到對應的上下文對象
4.默認規則:嚴格模式下綁定到undefined,否則綁定到全局對象
箭頭函數并不會使用到以上四種規則,而是根據當前的詞法作用域來決定this,也就是說,箭頭函數會繼承外層函數調用的this綁定。