JavaScript中的this


前面兩篇文章介紹了JavaScript執行上下文中兩個重要屬性:VO/AO和scope chain。本文就來看看執行上下文中的this。

首先看看下面兩個對this的概括:

  • this是執行上下文(Execution Context)的一個重要屬性,是一個與執行上下文相關的特殊對象。因此,它可以叫作上下文對象(也就是用來指明執行上下文是在哪個上下文中被觸發的對象)。
  • this不是變量對象(Variable Object)的一個屬性,所以跟變量不同,this從不會參與到標識符解析過程。也就是說,在代碼中當訪問this的時候,它的值是直接從執行上下文中獲取的,並不需要任何作用域鏈查找。this的值只在進入上下文的時候進行一次確定。

關於this最困惑的應該是,同一個函數,當在不同的上下文進行調用的時候,this的值就可能會不同。也就是說,this的值是通過函數調用表達式(也就是函數被調用的方式)的caller所提供的。

下面就看看在不同場景中,this的值。

全局上下文

在全局上下文(Global Context)中,this總是global object,在瀏覽器中就是window對象。

console.log(this === window);

this.name = "Will";
this.age = 28;
this.getInfo = function(){
    console.log(this.name + " is " + this.age + " years old");
};
window.getInfo();
// true
// Will is 28 years old

函數上下文

在一個函數中,this的情況就比較多了,this的值直接受函數調用方式的影響。

Invoke function as Function

當通過正常的方式調用一個函數的時候,this的值就會被設置為global object(瀏覽器中的window對象)。

但是,當使用"strict mode"執行下面代碼的時候,this就會被設置為"undefined"。

function gFunc(){
    return this;
}

console.log(gFunc());
console.log(this === window.gFunc());
// window
// true

Invoke function as Method

當函數作為一個對象方法來執行的時候,this的值就是該方法所屬的對象。

在下面的例子中,創建了一個obj對象,並設置name屬性的值為"obj"。所以但調用該對象的func方法的時候,方法中的this就表示obj這個對象。

var obj = {
    name: "obj",
    func: function () {
        console.log(this + ":" + this.name);
    }
};

obj.func();
// [object Object]:obj

為了驗證"方法中的this代表方法所屬的對象"這句話,再看下面一個例子。

在對象obj中,創建了一個內嵌對象nestedObj,當調用內嵌對象的方法的時候,方法中的this就代表nestedObj。

var obj = {
    name: "obj",
    nestedObj: {
        name:"nestedObj",
        func: function () {
            console.log(this + ":" + this.name);
        }
    }            
}

obj.nestedObj.func();
// [object Object]:nestedObj

對於上面例子中的方法,通常稱為綁定方法,也就是說這些方法都是個特定的對象關聯的。

但是,當我們進行下面操作的時候,temp將是一個全局作用里面的函數,並沒有綁定到obj對象上。所以,temp中的this表示的是window對象。

var name = "Will";
var obj = {
    name: "obj",
    func: function () {
        console.log(this + ":" + this.name);
    }
};

temp = obj.func;
temp();
//  [object Window]:Will

Invoke function as Constructor

在JavaScript中,函數可以作為構造器來直接創建對象,在這種情況下,this就代表了新建的對象。

function Staff(name, age){
    this.name = name;
    this.age = age;
    this.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    };
}

var will = new Staff("Will", 28);
will.getInfo();
// Will is 28 years old

Invoke context-less function

對於有些沒有上下文的函數,也就是說這些函數沒有綁定到特定的對象上,那么這些上下文無關的函數將會被默認的綁定到global object上。

在這個例子中,函數f和匿名函數表達式在被調用的過程中並沒有被關聯到任何對象,所以他們的this都代表global object。

var context = "global";

var obj = {  
    context: "object",
    method: function () {  
        console.log(this + ":" +this.context);
        
        function f() {
            var context = "function";
            console.log(this + ":" +this.context); 
        };
        f(); 
        
        (function(){
            var context = "function";
            console.log(this + ":" +this.context); 
        })();
    }
};

obj.method();
// [object Object]:object
// [object Window]:global
// [object Window]:global

call/apply/bind改變this

this本身是不可變的,但是 JavaScript中提供了call/apply/bind三個函數來在函數調用時設置this的值。

這三個函數的原型如下:

  • fun.apply(obj1 [, argsArray])
    • Sets obj1 as the value of this inside fun() and calls fun() passing elements of argsArray as its arguments.
  • fun.call(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])
    • Sets obj1 as the value of this inside fun() and calls fun() passing arg1, arg2, arg3, ... as its arguments.
  • fun.bind(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])
    • Returns the reference to the function fun with this inside fun() bound to obj1 and parameters of fun bound to the parameters specified arg1, arg2, arg3, ....

下面看一個簡單的例子:

function add(numA, numB){
    console.log( this.original + numA + numB);
}

add(1, 2);

var obj = {original: 10};
add.apply(obj, [1, 2]);
add.call(obj, 1, 2);

var f1 = add.bind(obj);
f1(2, 3);

var f2 = add.bind(obj, 2);
f2(3);
// NaN
// 13
// 13
// 15
// 15

當直接調用add函數的時候,this將代表window,當執行"this.original"的時候,由於window對象並沒有"original"屬性,所以會得到"undefined"。

通過call/apply/bind,達到的效果就是把add函數綁定到了obj對象上,當調用add的時候,this就代表了obj這個對象。

DOM event handler

當一個函數被當作event handler的時候,this會被設置為觸發事件的頁面元素(element)。

var body = document.getElementsByTagName("body")[0];
body.addEventListener("click", function(){
    console.log(this);
});
// <body>…</body>

In-line event handler

當代碼通過in-line handler執行的時候,this同樣指向擁有該handler的頁面元素。

看下面的代碼:

document.write('<button onclick="console.log(this)">Show this</button>');
// <button onclick="console.log(this)">Show this</button>
document.write('<button onclick="(function(){console.log(this);})()">Show this</button>');
// window

在第一行代碼中,正如上面in-line handler所描述的,this將指向"button"這個element。但是,對於第二行代碼中的匿名函數,是一個上下文無關(context-less)的函數,所以this會被默認的設置為window。

前面我們已經介紹過了bind函數,所以,通過下面的修改就能改變上面例子中第二行代碼的行為:

document.write('<button onclick="((function(){console.log(this);}).bind(this))()">Show this</button>');
// <button onclick="((function(){console.log(this);}).bind(this))()">Show this</button>

保存this

在JavaScript代碼中,同一個上下文中可能會出現多個this,為了使用外層的this,就需要對this進行暫存了。

看下面的例子,根據前面的介紹,在body元素的click handler中,this肯定是指向body這個元素,所以為了使用"greeting"這個方法,就是要對指向bar對象的this進行暫存,這里用了一個self變量。

有了self,我們就可以在click handler中使用bar對象的"greeting"方法了。

當閱讀一些JavaScript庫代碼的時候,如果遇到類似self,me,that的時候,他們可能就是對this的暫存。

var bar = {
    name: "bar",
    body: document.getElementsByTagName("body")[0],
    
    greeting: function(){
        console.log("Hi there, I'm " + this + ":" + this.name);
    },
    
    anotherMethod: function () {
        var self = this;
        this.body.addEventListener("click", function(){
            self.greeting();
        });
    }
};
  
bar.anotherMethod();
// Hi there, I'm [object Object]:bar

同樣,對於上面的例子,也可以使用bind來設置this達到相同的效果。

var bar = {
    name: "bar",
    body: document.getElementsByTagName("body")[0],
    
    greeting: function(){
        console.log("Hi there, I'm " + this + ":" + this.name);
    },
    
    anotherMethod: function () {
        this.body.addEventListener("click", (function(){
            this.greeting();
        }).bind(this));
    }
};
  
bar.anotherMethod();
// Hi there, I'm [object Object]:bar

總結

本文介紹了執行上下文中的this屬性,this的值直接影響着代碼的運行結果。

在函數調用中,this是由激活上下文代碼的調用者(caller)來提供的,即調用函數的父上下文(parent context ),也就是說this取決於調用函數的方式,指向調用時所在函數所綁定的對象。

通過上面的介紹,相信對this有了一定的認識。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM