與其餘語言相比,js中的this有所不一樣,也是比較頭疼的問題。在參考了一些資料後,今天,就來深刻解析一下this指向問題,有不對的地方望你們指出。前端
對於前端開發者來講,this是比較複雜的機制,那麼爲何要花大量時間來學習呢,先來看一段代碼。
若是不使用this,要給identify( )和speak( )顯式傳入一個對象:數組
function identify(context) { return context.name.toUpperCase(); } function speak(context) { var greeting = "Hello, I'm" + identify(context); console.log(greeting); } identify(you); speak(me);
能夠看到,speak( )裏面直接寫了identify( )的函數名,然而,隨着使用模式愈來愈複雜,顯式傳遞的上下文會讓代碼變得混亂,尤爲體如今面向對象中。
顯然,this提供了一種方式來隱式「傳遞」一個對象的引用,更加簡潔,易於複用。瀏覽器
記錄函數foo被調用的次數:app
function foo(num) { console.log("foo:" + num); // 記錄次數 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 //foo被調用了多少次? console.log(foo.count); // 0
從前兩次的console.log( )能夠看出,foo確實被調用了4次,可是foo.count仍然爲0,顯然this指向函數自己的理解是錯誤的。ide
要明確的是,this在任何狀況下都不指向函數的詞法做用域。由於,做用域「對象」沒法經過JavaScript代碼訪問,它存在於JavaScript引擎內部。
下面的代碼試圖使用this來隱式引用函數的詞法做用域,沒有成功:函數
function foo() { var a = 2; this.bar(); } function bar() { console.log(this.a); } foo(); // ReferenceError: a is not defined
直接報出了訪問不到foo( )中的a。ReferenceError和做用域判別失敗相關,而TypeError表明做用域判別成功,可是對結果的操做是非法的、不合理的。學習
排除了以上兩個誤解以後,來看一下this究竟是什麼。
this是運行時綁定的,它和函數聲明的位置沒有任何關係,只取決於函數的調用方式。當一個函數被調用時,會建立一個活動記錄(執行上下文),這個記錄包含函數在哪裏被調用,函數的調用方式、傳入的參數等,this就是這個記錄的一個屬性,在函數執行的過程當中用到。即,this老是指向調用它所在方法的對象this
function foo() { console.log(this.a); } var a = 2; foo(); // 2
在全局中聲明變量a = 2,而後在全局中直接調用foo( ),this指向了全局對象,獲得a的值。
要注意的是,在嚴格模式(strict mood)下,若是this沒有被執行環境定義,那它將綁定爲undefined。code
function foo() { "use strict"; console.log(this.a); } var a = 2; foo(); // TypeError: this is undefined
在嚴格模式下,調用foo( )不影響this綁定。對象
function foo() { console.log(this.a); } var a = 2; (function() { "use strict"; foo(); // 2 })();
function foo() { console.log(this.a); } var obj = { a: 2, foo: foo }; var a = "global"; setTimeout(obj.foo, 100); // "global"
JavaScript中的setTimeout( )的實現和下面僞代碼類似:
function setTimeout(fn, delay) { // 等待delay毫秒 fn(); // 調用函數 }
function foo() { console.log(this.a); } var obj = { a: 2 }; foo.call(obj); // 2 foo.apply(obj); // 2 foo.bind(obj); // 2
call和apply的區別在於第二個參數,call是把args所有列出來,用「,」分隔,而apply是一個類數組。call、apply是硬綁定,經過硬綁定的函數不能再修改它的this。
function foo() { console.log(this.a); } var obj = { a: 2 }; var bar = function() { foo.call(obj); } bar(); // 2 setTimeout(bar, 100); // 2 bar.call(window); // 2
函數foo( )內部手動調用了foo.call(obj),把foo的this強制綁定到了obj,因此後面即便又把bar( )綁定到了window,仍是沒法改變this指向。
在傳統的面嚮對象語言中,會使用new初始化類,然而在JavaScript中new的機制和麪向對象語言徹底不一樣。在js中,構造函數只是使用new操做符時被調用的函數,它們並不屬於一個類,也不會實例化一個類。也就是說,js中,不存在所謂的「構造函數」,只有對函數的「構造調用」。
function foo(a) { this.a = a; } var bar = new foo(2); console.log(bar.a); // 2
使用new調用foo( ),會構造一個新對象並把它綁定到foo( )調用中的this上。
既然有那麼多能夠改變this的指向,那麼它們的優先級是怎麼樣的呢,記住這句話:範圍越小,優先級越高。能夠按照下面的順序來判斷:
判斷函數是否在new中調用過:
var bar = new foo();
判斷函數是否經過call、apply、bind綁定過:
var bar = foo.call(obj);
判斷函數是否在某個上下文對象中調用過:
var bar = obj.foo();
若是以上狀況均不存在,那麼在嚴格模式下,綁定到undefined,不然綁定到全局對象:
var bar = foo();