跟別的語言截然不同的是,JavaScript的this老是指向一個對象,而具體指向哪一個對象是在運行時基於函數的執行環境動態綁定的,而非函數被聲明時的環境。html
除去不經常使用的with和eval,具體到實際應用中,this的指向大體能夠分爲如下4種:設計模式
1. 做爲對象的方法調用
2. 做爲普通函數調用
3. 構造器調用
4. Function.prototype.call或Function.prototype.apply調用瀏覽器
看成爲對象的方法被調用時,this指向該對象app
var obj = { a: 1, getA: function(){ alert ( this === obj ); // 輸出: true alert ( this.a ); // 輸出: 1 } }; obj.getA();
當函數不做爲對象的方法被調用時,也就是咱們一般說的普通函數,此時的this老是指向全局對象,在瀏覽器的JavaScript中,這個全局對象是window對象。框架
window.name = 'globalName'; var getName = function(){ return this.name; }; console.log( getName() ); // 輸出: globalName // 或者: window.name = 'globalName'; var myObject = { name: 'sven', getName: function(){ return this.name; } }; myObject.getName(); // sven var getName = myObject.getName; console.log( getName() ); // globalName
有時候咱們會遇到一些困擾,好比在 div 節點的事件函數內部,有一個局部的 callback 方法,callback 被做爲普通函數調用時, callback 內部的 this 指向了 window,但咱們每每是想讓它指向該 div 節點,見以下代碼:函數
<html> <body> <div id="div1">我是一個 div</div> </body> <script> window.id = 'window'; document.getElementById( 'div1' ).onclick = function(){ alert ( this.id ); // 輸出: 'div1' var callback = function(){ alert ( this.id ); // 輸出: 'window' } callback(); }; </script> </html>
此時有一種簡單的解決方案,能夠用一個變量保存 div 節點的引用:this
document.getElementById( 'div1' ).onclick = function(){ var that = this; // 保存 div 的引用 var callback = function(){ alert ( that.id ); // 輸出: 'div1' } callback(); };
在 ECMAScript 5 的 strict 模式下,這種狀況下的 this 已經被規定爲不會指向全局對象,而是 undefined:prototype
function func(){ "use strict" alert ( this ); // 輸出: undefined } func();
JavaScript 中沒有類,可是能夠從構造器中建立對象,同時也提供了new運算符,使得構造器看起來更像一個類。除了宿主提供的一些內置函數,大部分JavaScript函數均可以看成構造器使用。構造器的外表跟普通函數如出一轍,它們的區別在於被調用的方式。當用new運算符調用該函數時,該函數總會返回一個實例對象,一般狀況下,構造器裏的 this 就指向返回的這個實例對象,見以下代碼:設計
var MyClass = function(){ this.name = 'sven'; }; var obj = new MyClass(); alert ( obj.name ); // 輸出: sven
但用 new 調用構造器時,還要注意一個問題,若是構造器顯式地返回了一個object類型的對象,那麼這次運算結果最終會返回這個對象,而不是咱們以前期待的 this:code
var MyClass = function(){ this.name = 'sven'; this.age = 23; return { // 顯式地返回一個對象 name: 'anne' } }; var obj = new MyClass(); alert ( obj.name ); // 輸出: anne alert ( obj.age ); // 輸出: undefined
若是構造器不顯式地返回任何數據,或者是返回一個非對象類型的數據,就不會形成上述問題:
var MyClass = function(){ this.name = 'sven' return 'anne'; // 返回 string 類型 }; var obj = new MyClass(); alert ( obj.name ); // 輸出: sven
跟普通的函數調用相比,用 Function.prototype.call或Function.prototype.apply能夠動態地改變傳入函數的 this:
var obj1 = { name: 'sven', getName: function(){ return this.name; } }; var obj2 = { name: 'anne' }; console.log( obj1.getName() ); // 輸出: sven console.log( obj1.getName.call( obj2 ) ); // 輸出: anne 注意此時的this指向obj2 console.log( obj1.getName.apply( obj2 ) ); // 輸出: anne 注意此時的this指向obj2
call 和 apply 方法能很好地體現JavaScript的函數式語言特性,在JavaScript中,幾乎每一次編寫函數式語言風格的代碼,都離不開 call 和 apply。在 JavaScript 諸多版本的設計模式中,也用到了 call 和 apply。
這是一個常常遇到的問題,咱們先看下面的代碼:
var obj = { myName: 'sven', getName: function(){ return this.myName; } }; console.log( obj.getName() ); // 輸出: 'sven' var getName2 = obj.getName; console.log( getName2() ); // 輸出: undefined
當調用 obj.getName 時, getName方法是做爲obj對象的屬性被調用的,此時的this指向obj對象,因此obj.getName()輸出'sven'。當用另一個變量getName2來引用obj.getName,而且調用getName2時,此時是普通函數調用方式, this 是指向全局 window 的,window對象上沒有myName,因此程序的執行結果是 undefined。
再看另外一個例子, document.getElementById這個方法名實在有點過長,咱們大概嘗試過用一個短的函數來代替它,如同 prototype.js 等一些框架所作過的事情:
var getId = function( id ){ return document.getElementById( id ); }; getId( 'div1' );
咱們也許思考過爲何不能用下面這種更簡單的方式:
var getId = document.getElementById; getId( 'div1' );
如今不妨花 1 分鐘時間,讓這段代碼在瀏覽器中運行一次:
<html> <body> <div id="div1">我是一個 div</div> </body> <script> var getId = document.getElementById; getId( 'div1' ); </script> </html>
在 Chrome、 Firefox、 IE10中執行事後就會發現,這段代碼拋出了一個異常。
這是由於許多引擎的document.getElementById 方法的內部實現中須要用到 this。
這個 this 原本被指望指向document,當 getElementById 方法做爲 document 對象的屬性被調用時,方法內部的this確實是指向document的。
但當用getId來引用document.getElementById以後,再調用getId,此時就成了普通函數調用,函數內部的this指向了window,而不是原來的 document。
咱們能夠嘗試利用 apply 把 document 看成 this 傳入 getId 函數,幫助「修正」 this:
document.getElementById = (function( func ){ return function(){ return func.apply( document, arguments ); } })( document.getElementById ); var getId = document.getElementById; var div = getId( 'div1' ); alert (div.id); // 輸出: div1