this

this


跟別的語言截然不同的是,JavaScript的this老是指向一個對象,而具體指向哪一個對象是在運行時基於函數的執行環境動態綁定的,而非函數被聲明時的環境。html


this的指向


除去不經常使用的with和eval,具體到實際應用中,this的指向大體能夠分爲如下4種:設計模式

1. 做爲對象的方法調用
2. 做爲普通函數調用
3. 構造器調用
4. Function.prototype.call或Function.prototype.apply調用瀏覽器


1. 做爲對象的方法調用

看成爲對象的方法被調用時,this指向該對象app

var obj = {
    a: 1,
    getA: function(){
        alert ( this === obj ); // 輸出: true
        alert ( this.a ); // 輸出: 1
    }
};
obj.getA();

2. 做爲普通函數調用

當函數不做爲對象的方法被調用時,也就是咱們一般說的普通函數,此時的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();

3. 構造器調用

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

4. Function.prototype.call 或 Function.prototype.apply 調用

跟普通的函數調用相比,用 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。

丟失的this

這是一個常常遇到的問題,咱們先看下面的代碼:

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
相關文章
相關標籤/搜索