this 在任何狀況下都不指向函數的詞法做用域。做用域「對象」沒法經過 JavaScript代碼訪問,它存在於 JavaScript 引擎內部。數組
this 是在運行時進行綁定的,並非在編寫時綁定,它的上下文取決於函數調用時的各類條件。this 的綁定和函數聲明的位置沒有任何關係,只取決於函數的調用方式。app
當一個函數被調用時,會建立一個活動記錄(有時候也稱爲執行上下文)。這個記錄會包含函數在哪裏被調用(調用棧)、函數的調用方法、傳入的參數等信息。this 就是記錄的其中一個屬性,會在函數執行的過程當中用到。函數
函數的執行過程當中調用位置如何決定 this 的綁定對象,綁定規則:oop
1 默認綁定this
function foo(){ console.log(a); } var a=2; foo();
foo() 是直接使用不帶任何修飾的函數引用進行調用的,所以只能使用默認綁定,沒法應用其餘規則。若是使用嚴格模式(strict mode),那麼全局對象將沒法使用默認綁定,所以 this 會綁定
到 undefined。prototype
2 隱式綁定
調用位置是否有上下文對象,或者說是否被某個對象擁有或者包含。當 foo() 被調用時,它的落腳點確實指向 obj 對象。當函數引用有上下文對象時,隱式綁定規則會把函數調用中的 this 綁定到這個上下文對象。由於調用 foo() 時 this 被綁定到 obj,所以 this.a 和 obj.a 是同樣的。對象屬性引用鏈中只有最頂層或者說最後一層會影響調用位置。code
function fooo(){ conosle.log(this.a); } var obj={ a:2, foo:foo }; var obj2={ a:22, obj: obj } obj.foo(); // 2 obj2.obj.foo(); // 2
隱式丟失
雖然 bar 是 obj.foo 的一個引用,可是實際上,它引用的是 foo 函數自己,所以此時的bar() 實際上是一個不帶任何修飾的函數調用,所以應用了默認綁定。對象
當函數被做爲參數傳遞時(如:回調函數),會丟失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"
3 顯式綁定
JavaScript 提供的絕大多數函數以及你本身建立的全部函數均可以使用 call(..) 和 apply(..) 方法。這兩個方法是如何工做的呢?它們的第一個參數是一個對象,它們會把這個對象綁定到this,接着在調用函數時指定這個 this。由於你能夠直接指定 this 的綁定對象,所以咱們稱之爲顯式綁定。ip
function foo(){ console.log(this.a); } var obj={a:2}; foo.call(obj); // 2
若是你傳入了一個原始值(字符串類型、布爾類型或者數字類型)來看成 this 的綁定對象,這個原始值會被轉換成它的對象形式(也就是new String(..)、new Boolean(..)或者new Number(..))。這一般被稱爲「裝箱」。
4 new 綁定
使用 new 來調用函數,或者說發生構造函數調用時,會自動執行下面的操做。
function foo(a) { this.a = a; } var bar = new foo(2); console.log( bar.a ); // 2
使用 new 來調用 foo(..) 時,咱們會構造一個新對象並把它綁定到 foo(..) 調用中的 this上。new 是最後一種能夠影響函數調用時 this 綁定行爲的方法,咱們稱之爲 new 綁定。
注意:若是你把 null 或者 undefined 做爲 this 的綁定對象傳入 call、apply 或者 bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。
那麼什麼狀況下你會傳入 null 呢?一種很是常見的作法是使用 apply(..) 來「展開」一個數組,並看成參數傳入一個函數。相似地,bind(..) 能夠對參數進行柯里化(預先設置一些參數),這種方法有時很是有用:
function foo(a,b) { console.log( "a:" + a + ", b:" + b ); } // 把數組「展開」成參數 foo.apply( null, [2, 3] ); // a:2, b:3 // 使用 bind(..) 進行柯里化 var bar = foo.bind( null, 2 ); bar( 3 ); // a:2, b:3
軟綁定會對指定的函數進行封裝,首先檢查調用時的 this,若是 this 綁定到全局對象或者 undefined,那就把指定的默認對象 obj 綁定到 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; }; }
箭頭函數並非使用 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,箭頭函數的綁定沒法被修改。(new 也不行!)