摘要編程
相信有C++、C#或Java等編程經驗的各位,對於this關鍵字再熟悉不過了。因爲Javascript是一種面向對象的編程語言,它和C++、C#或Java同樣都包含this關鍵字,接下來咱們將向你們介紹Javascript中的this關鍵字。瀏覽器
正文編程語言
因爲許多面向對象的編程語言都包含this關鍵字,咱們會很天然地把this和麪向對象的編程方式聯繫在一塊兒,this一般指向利用構造器新建立出來的對象。而在ECMAScript中,this不只僅只用來表示建立出來的對象,也是執行上下文的一個屬性:函數
activeExecutionContext = { // Variable object. VO: {...}, this: thisValue };
全局代碼中的thisthis
// Global scope // The implicit property of // the global object foo1 = "abc"; alert(foo1); // abc // The explicit property of // the global object this.foo2 = "def"; alert(foo2); // def // The implicit property of // the global object var foo3 = "ijk"; alert(foo3); // ijk
前面咱們經過顯式和隱式定義了全局屬性foo一、foo2和foo3,因爲this在全局上下文中,因此它的值是全局對象自己(在瀏覽器中是window object);接下來咱們將介紹函數中的this。code
函數中的this對象
當this在函數代碼中,狀況就複雜多了,而且會引起不少的問題。ip
函數代碼中this值的第一個特性(同時也是最主要的特性)就是:它並不是靜態的綁定在函數上。ci
正如此前提到的,this的值是在進入執行上下文(Excution context)的階段肯定的,而且在函數代碼中的話,其值每次都不盡相同。it
然而,一旦進入執行代碼階段,其值就不能改變了。若是要想給this賦一個新的值是不可能的,由於在那時this根本就不是變量了。
接下來,咱們經過具體的例子說明函數中的this。
首先咱們定義兩個對象foo和person,foo包含一個屬性name,而person包含屬性name和方法say(),具體的定義以下:
// Defines foo object. var foo = { name: "Foo" }; // Defines person object. var person = { name: "JK_Rush", say: function() { alert(this === person); alert("My name is " + this.name); } }; person.say(); // My name is JK_Rush // foo and person object refer to // the same function say foo.say = person.say; foo.say(); // My name is Foo.
經過上面的代碼,咱們發現調用person的say()方法時,this指向person對象,當經過賦值方式使得foo的say()方法指向peson中的say()方法時。咱們調用foo的say()方法,發現this不是指向person對象,而不是指向foo對象,這到底是什麼緣由呢?
首先,咱們必須知道this的值在函數中是非靜態的,它的值肯定在函數調用時,具體代碼執行前,this的值是由激活上下文代碼的調用者決定的,好比說,調用函數的外層上下文;更重要的是,this的值是由調用表達式的形式決定的,因此說this並不是靜態的綁定在函數上。
因爲this並不是靜態地綁定在函數上,那麼咱們是否能夠在函數中動態地修改this的值呢?
// Defines foo object. var foo = { name: "Foo" }; // Defines person object. var person = { name: "JK_Rush", say: function() { alert(this === person); this = foo; // ReferenceError alert("My name is " + this.name); } }; person.say(); // My name is JK_Rush
如今咱們在方法say()中,動態地修改this的值,當咱們從新執行以上代碼,發現this的值引用錯誤。這是因爲一旦進入執行代碼階段(函數調用時,具體代碼執行前),this的值就肯定了,因此不能改變了。
引用類型
前面咱們提到this的值是由激活上下文代碼的調用者決定的,更重要的是,this的值是由調用表達式的形式決定的;那麼表達式的形式是如何影響this的值呢?
首先,讓咱們介紹一個內部類型——引用類型,它的值能夠用僞代碼表示爲一個擁有兩個屬性的對象分別是:base屬性(屬性所屬的對象)以及該base對象中的propertyName屬性:
// Reference type. var valueOfReferenceType = { base: mybase, propertyName : 'mybasepropertyName' };
引用類型的值只有多是如下兩種狀況:
當處理一個標識符的時候
或者進行屬性訪問的時候
標識符其實就是變量名、函數名、函數參數名以及全局對象的未受限的屬性。
// Declares varible. var foo = 23; // Declares a function function say() { // Your code. }
中間過程當中,對應的引用類型以下:
// Reference type. var fooReference = { base: global, propertyName: 'foo' }; var sayReference = { base: global, propertyName: 'say' };
咱們知道Javascript中屬性訪問有兩種方式:點符號和中括號符號:
// Invokes the say method. foo.say(); foo['say']();
因爲say()方法是標識符,因此它對應於foo對象引用類型以下:
// Reference type. var fooSayReference = { base: foo, propertyName: 'say' };
咱們發現say()方法的base屬性值爲foo對象,那麼它對應的this屬性也將指向foo對象。
假設,咱們直接調用say()方法,它對應的引用類型以下:
// Reference type. var sayReference = { base: global, propertyName: 'say' };
因爲say()方法的base屬性值爲global(一般來講是window object),那麼它對應的this屬性也將指向global。
函數上下文中this的值是函數調用者提供而且由當前調用表達式的形式而定的。若是在調用括號()的左邊有引用類型的值,那麼this的值就會設置爲該引用類型值的base對象。 全部其餘狀況下(非引用類型),this的值老是null。然而,因爲null對於this來講沒有任何意義,所以會隱式轉換爲全局對象。
函數調用以及非引用類型
前面咱們提到,當調用括號左側爲非引用類型的時,this的值會設置爲null,並最終隱式轉換爲全局對象。
如今咱們定義了一個匿名自執行函數,具體實現以下:
// Declares anonymous function (function () { alert(this); // null => global })();
因爲括號()左邊的匿名函數是非引用類型對象(它既不是標識符也不屬於屬性訪問),所以,this的值設置爲全局對象。
// Declares object. var foo = { bar: function () { alert(this); } }; (foo.bar)(); // foo. (foo.bar = foo.bar)(); // global? (false || foo.bar)(); // global? (foo.bar, foo.bar)(); // global
這裏注意到四個表達式中,只有第一個表達式this是指向foo對象的,而其餘三個表達式則執行global。
如今咱們又有疑問了:爲何屬性訪問,可是最終this的值不是引用類型對象而是全局對象呢?
咱們注意到表達式二是賦值(assignment operator),與表達式一組操做符不一樣的是,它會觸發調用GetValue方法(參見11.13.1中的第三步)。 最後返回的時候就是一個函數對象了(而不是引用類型的值了),這就意味着this的值會設置爲null,最終會變成全局對象。
第三和第四種狀況也是相似的——逗號操做符和OR邏輯表達式都會觸發調用GetValue方法,因而相應地就會丟失原先的引用類型值,變成了函數類型,this的值就變成了全局對象了。