this
關於this
,其實this
是提供了一種更優雅的方式來隱式「傳遞」一個對象引用。靈活的應用this
,可讓咱們在寫內容較多的代碼時,不會顯得愈來愈混亂。bash
this
根據英語的語法角度來推斷的話,this
能夠理解爲指向函數自身。可是在實際中並非如此。app
function foo(num) {
console.log( "foo: " + num );
// 記錄 foo 被調用的次數
this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
foo( i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log( foo.count );// 0
console.log(count); //NaN
複製代碼
上述代碼中,咱們企圖用this.count
指向foo
函數自己,以此在其做用域建立一個count
屬性。可是是代碼的實際結果告訴咱們,這樣寫並不能作到咱們所想要的結果。函數
這種寫法,最後會使代碼在無心中建立了一個全局變量count
,其值爲NaN
。oop
若是要從函數對象內部引用其自身,只使用
this
是不夠的。通常而言,須要一個指向函數對象的詞法標識符來引用它。ui
對於上述狀況,其實有許多解決方式。可是既然咱們的主題是this
,那就用this
的方式來解決。this
function foo(num) {
console.log( "foo: " + num );
// 記錄 foo 被調用的次數
// 注意,在當前的調用方式下(參見下方代碼),this 確實指向 foo
this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
// 使用 call(..) 能夠確保 this 指向函數對象 foo 自己
foo.call( foo, i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log( foo.count ); // 4
複製代碼
這個方式,是強制this
指向foo
函數對象。編碼
this
的做用域對於this
,還有一種誤解。就是this
指向函數的做用域。spa
可是實際上,this
在任何狀況下都不指向函數的詞法做用域。prototype
舉個例子:code
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); // ReferenceError: a is not defined
複製代碼
上述方法,其實在有必定代碼基礎的人眼裏,會以爲很奇怪。由於咱們要調用bar()
方法的話,這個this
其實是多餘的。
而且,this
這個操做並不能跨做用域引用變量。
切記,不要將
this
和詞法做用域混合使用。
What is 'this'
在標題上玩了一下英語的諧音梗,哈哈哈,但願不要介意。
上面講述了那些以後,咱們須要瞭解到的是,this
究竟是一個什麼樣的機制。
其實,this
的綁定和函數聲明的位置,沒有半毛錢關係,半分錢也沒有。它只取決於函數的調用方式。
當一個函數被調用時,會建立一個執行上下文。它會包含函數在哪裏被調用、調用方法和傳參等信息。this
就是其中的一個屬性,在函數執行過程當中會用到。
this
的全面解析在瞭解this
的綁定規則以前,咱們須要知道了解調用位置。
調用位置實際上就是指咱們調用對象的詞法標識符的位置。
this
的綁定規則有四條。
最經常使用的函數調用類型:獨立函數調用。這種方法就可使用默認綁定。
function foo(){
console.log(this.a);
}
var a = 2;
foo(); //2
複製代碼
咱們能夠發現,咱們在調用foo()
時,其內部的this
指向了全局對象,這是由於應用了this
的默認綁定。
代碼中,
foo()
是直接使用不帶任何修飾的函數引用進行調用,所以只能使用默認綁定,沒法應用其餘規則。
須要注意的是,上述狀況只有在非嚴格模式下 使用。
function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo(); // TypeError: this is undefined
複製代碼
隱式的使用,須要考慮調用位置是否有上下文對象。
function foo(){
console.log(this.a);
}
var obj = {
a:2,
foo:foo
};
obj.foo();
複製代碼
咱們在使用了obj.foo()
的方式來調用函數時,函數foo()
其實不屬於obj
對象。可是這種方式,讓函數的落腳點在obj
對象內,this
指向的是obj
對象內,所以能夠調用到obj
內部的變量。
隱式丟失 在使用this
綁定的時候,有個常見的問題,就是被隱式綁定的函數會丟失綁定對象。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函數別名!
var a = "oops, global"; // a 是全局對象的屬性
bar(); // "oops, global 複製代碼
上述代碼,在使用過程當中,使用bar
引用了obj.foo
,可是實際上,引用的仍是函數自己。因此,此時 bar()
實際上是一個不帶任何修飾的函數調用 ,所以應用了默認綁定。
還有另外一種狀況:
function foo() {
console.log( this.a );
}
function doFoo(fn) {
// fn 其實引用的是 foo
fn(); // <-- 調用位置!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a 是全局對象的屬性
doFoo( obj.foo ); // "oops, global"
複製代碼
咱們在使用
this
的時候,應該嚴格注意避開相似狀況。由於咱們沒法控制回調函數的執行方式,從而致使沒法正確的使用咱們想要使用的綁定方式。
JavaScript
中的「全部」函數都有一些有用的特性,能夠用來解決這個問題。具體點說,可使用函數的 call(..)
和apply(..)
方法。嚴格來講,JavaScript
的宿主環境有時會提供一些很是特殊的函數,它們並無這兩個方法。可是這樣的函數很是罕見,JavaScript
提供的絕大多數函數以及你本身建立的全部函數均可以使用 call(..)
和 apply(..)
方法。
顯示綁定主要分爲兩種。
硬綁定 硬綁定其實就是使用 call(..)
和 apply(..)
方法。
call
:
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
複製代碼
apply
:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a:2
};
var bar = function() {
return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5
複製代碼
因爲硬綁定是一種很是經常使用的模式,因此在
ES5
中提供了內置的方法Function.prototype. bind
,它的用法以下:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a:2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
複製代碼
bind(..)
會返回一個硬編碼的新函數,它會把參數設置爲 this
的上下文並調用原始函數。
API
調用的上下文第三方庫的許多函數,以及JavaScript
語言和宿主環境中許多新的內置函數,都提供了一個可選的參數,一般被稱爲「上下文」(context
),其做用和 bind(..)
同樣,確保你的回調函數使用指定的 this
。
實際上該種方法也是經過
call()
或者apply()
來實現的顯式綁定,只不過不須要咱們本身來寫。
在傳統的面向類的語言中,「構造函數」是類中的一些特殊方法,使用 new
初始化類時會 調用類中的構造函數。一般的形式是這樣的:
something = new MyClass(..);
複製代碼
JavaScript
也有一個 new
操做符,使用方法看起來也和那些面向類的語言同樣,絕大多數開發者都認爲 JavaScript
中 new
的機制也和那些語言同樣。然而,JavaScript
中 new
的機制實際上和麪向類的語言徹底不一樣。
實際上, 在
JavaScript
中並不存在所謂的「構造函數」,只有對於函數的「構造調用」。
使用 new
來調用函數,或者說發生構造函數調用時,會自動執行下面的操做。
this
。new
表達式中的函數調用會自動返回這個新對象。function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
複製代碼
使用 new
來調用 foo(..)
時,咱們會構造一個新對象並把它綁定到 foo(..)
調用中的 this
上。 new
是最後一種能夠影響函數調用時 this
綁定行爲的方法,咱們稱之爲 new
綁定。
上述說了四種綁定方式,在實際開發中,確定會遇到多種綁定交叉的狀況。那咱們就須要知道,綁定的優先級如何,才能夠預知到代碼結果。
判斷this
如今咱們能夠根據優先級來判斷函數在某個調用位置應用的是哪條規則。能夠按照下面的 順序來進行判斷:
new
中調用( new
綁定)?若是是的話 this
綁定的是新建立的對象。var bar = new foo()
複製代碼
call
、 apply
(顯式綁定)或者硬綁定調用?若是是的話, this
綁定的是指定的對象。var bar = foo.call(obj2)
複製代碼
this
綁定的是那個上 下文對象。var bar = obj1.foo()
複製代碼
undefined
,不然綁定到全局對象。var bar = foo()
複製代碼
就是這樣。對於正常的函數調用來講,理解了這些知識你就能夠明白 this
的綁定原理了。
凡是規則,都會有出現意外的狀況。this
綁定也不例外。
function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2
複製代碼
當咱們把null
或者undefined
傳入call
、apply
或者bind
時,這些值在調用時會被忽略,從而去應用默認綁定規則。
當咱們建立一個函數的「間接引用」時,調用這個函數就會應用默認綁定規則。
簡介引用最容易在賦值時發生。
function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2
複製代碼
賦值表達式 p.foo = o.foo
的返回值是目標函數的引用,所以調用位置是 foo()
而不是 p.foo()
或者 o.foo()
。因此這裏會應用默認綁定。
對於默認綁定來講,決定
this
綁定對象的並非調用位置是否處於嚴格模式,而是 函數體是否處於嚴格模式。若是函數體處於嚴格模式,this
會被綁定到undefined
,不然this
會被綁定到全局對象。
前面有提到一種綁定方式,叫作硬綁定,因此通常相對的,其實也有一種叫作軟綁定的方式。
使用硬綁定會下降函數的靈活性,以後沒法再使用隱式綁定或者顯式綁定來修改
this
。可是軟綁定 ,能夠保留隱式綁定和顯示綁定修改this
的能力。
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
// 捕獲全部 curried 參數
var curried = [].slice.call( arguments, 1 );
var bound = function() {
return fn.apply(
(!this || this === (window || global)) ?
obj : this
curried.concat.apply( curried, arguments )
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
複製代碼
this
詞法在ES6
中,介紹了一種特殊的函數類型:箭頭函數。
箭頭函數並非使用 function
關鍵字定義的,而是使用被稱爲「胖箭頭」的操做符 => 定 義的。箭頭函數不使用 this
的四種標準規則,而是根據外層(函數或者全局)做用域來決 定 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
複製代碼
foo()
內部建立的箭頭函數會捕獲調用時 foo()
的 this
。因爲 foo()
的 this
綁定到 obj1
,bar
(引用箭頭函數)的 this
也會綁定到 obj1
,箭頭函數的綁定沒法被修改。
箭頭函數能夠像
bind(..)
同樣確保函數的this
被綁定到指定對象,此外,其重要性還體 如今它用更常見的詞法做用域取代了傳統的this
機制。
要判斷this
綁定,就須要先找到函數的直接調用位置。
以後根據規則來判斷this
的綁定對象:
1. `new`調用
複製代碼
2.call
、apply
或者bind
3.上下文對象調用 4.默認綁定
ES6
中的箭頭函數並不會使用四條標準的綁定規則。而是根據當前的詞法做用域來決定this
。箭頭函數會繼承外層函數調用的this
綁定。