我的JavaScript之旅——this到底是啥?

作者: 菜阿彬  來源: 博客園  發布時間: 2010-10-01 00:12  閱讀: 976 次  推薦: 0   原文鏈接   [收藏]  
摘要:this, scope chain, prototype chain, constructor, closure,這些都是學習JavaScript必須懂的東西,每本JS的書都會對這些概念進行描述,我看過幾篇,都是講what,就是要你死記硬背,或者多寫JS,熟能生巧才行。沒有看到一篇講why的。而我把這5篇寫why的博文寫完,再看那些書籍上的描述,真有一覽眾山小的感覺,哈哈。

  下圖是在ASP.NET中為button掛上客戶端onclick事件的兩種辦法:圖中的2和3/1。 結果發現兩種方式調用同樣一個函數clickMe,this卻不一樣。  

  如果采用3或1的做法,那么點擊button1后將alert出[object DOMWindow];而采用2的做法,將alert出 [object HTMLInputElement](在chrome下測試。)

  顯然,在1的做法中,this指向DOMWindow,也就是全局的object——global;而2的做法中,this指向Button1這個元素。

  為什么呢?

  (注:對于3這種通過Attribute來聲明處理程序的方式,button的onclick不是指向clickMe函數,而是指向一個被自動創建的匿名函數,該匿名函數以Attribute的值(也就是"clickMe();")作為函數體——也就是等價于1。)

  簡單的答案

  基于類的OO語言(比如C#)中,函數總是聲明在一個類中,函數內部的this指向該類的當前實例。而JS中,函數是第一等公民(它不會聲明為別的東西的一部分),所以this跟函數是如何聲明的無關,跟函數是怎么被調用的有關。

  方式1和2對clickMe的調用,不同之處在于:1中clickMe是被button1.onclick所指向的函數調用的,2中clickMe是作為button1.onclick屬性直接調用的。因此對于clickMe函數,1中的this指向“擁有”clickMe函數的對象——global(DOMWindow),而2中的this指向“擁有”onclick屬性的對象——button1。

  可惜對像我這種寫JS寫的不多的人來說,總是記不住這個簡單答案,因為它只告訴我what,沒有告訴我why。本文試圖從ECMAScript官方文檔出發,從原理上說明:在不同的場合中,函數的this到底是什么?

  call和apply

  首先明確一點,在最正常的情況下,我們這樣調用:Func(),這時this是由JavaScript來確定的,這也是本篇要研究的主題。而如果用Func.call(thisArg,  arg1, arg2, ...)或者Func.apply(thisArg, [arg1, arg2, ...])來調用時,this是我們自己傳進去的(作為call或apply的第一個參數)。如果我們不傳this進去,或者傳null進去,會怎樣?這時this將會是global object。  

  函數作為構造器時的this

  先從簡單的說起。所謂構造器,就是用new關鍵字來調用函數,在這篇關于new關鍵字的玄機的文章中有說到。看下面的注釋,可以知道,A函數里的this這時就是a。

function A() {this.x = 1;}
var a = new A(); //這行代碼大概等價于下面兩行
var a = {}; // a指向一個新創建的對象。
A.call(a); //再把a作為第一個參數傳給A.call函數。

  普通調用時的this

  看下圖,對于在全局定義的函數A,this就是global。  

  這個好理解,A作為頂級函數,是頂級對象global(也就是window)的屬性,所以this === global。但看下圖: 

  Outer()返回inner函數,Outer()()就是是對inner的調用,在inner內部,this同樣等于global。可是inner函數是執行Outer時,在Outer的context下創建起來的,inner并不是global的屬性(見下圖)。為什么inner里的this仍舊是global?一個函數被調用時,this究竟是怎么確定的?  

  官方文檔對this的解釋

  上面第1個例子,當執行A()時,這里說明了函數被調用時發生的事情,簡述如下:

  1,  得到A這個引用。

  2, 求A的值,就是A這個函數對象。

  3, A的值必須是object,并且必須有[[Call]]方法。(當然,它是函數型object,函數被創建時都有[[Call]]方法。)

  4, 如果A是個引用(是),那么通過GetBase()得到A所在的對象,就是包含有A屬性的對象(在我們例子中就是global)

  5, 檢查上面步驟中A所在的對象。如果是個activation object(就是variable object,scope chain里的對象),則把null作為thisArg傳給A.call();否則就把該對象作為thisArg。

  重點就是這第5步(需要對variable object熟悉,否則請先閱讀這篇文章),執行A()時,global里有A屬性,而global是global context下的activation object,所以將會把null作為thisArg參數傳給A.call()。而上文說了,如果傳的thisArg參數為null,則會把global作為this。所以gloabl === this還不是天生就這樣,還轉了下彎。

  對于第2個例子,Outer()(),也就好解釋了:因為Outer()返回的inner函數是在OuterContext中創建為OuterVariableObject的屬性,所以第4步得到的對象是OuterVariableObject;那么根據第5步邏輯,仍舊把null作為thisArg傳給inner.call()。因此inner里的this也同樣是global。

  把函數作為對象的屬性值

  所以,如果直接調用函數,無論這個函數是在哪個執行上下文被創建,this都指向global。除非“擁有”這個函數的對象不是一個variable object,它才會被當做this。那么很簡單,我們把函數作為我們自己創建的對象(非variable object)的屬性值,然后再調用,那么this就應該是這個我們創建的對象了。驗證如下:  

  回過頭看文章開頭的問題:

  方式1是這樣調用的:Button1.onclick(clickMe();),clickMe是global的屬性,因此clickMe里的this指向global。

  方式2是這樣調用的:Button1.onclick(),onclick是Button1的屬性,因此里面的this指向Button1。

  簡言之,onclick和clickMe都是對真正的函數object的引用,只不過通過GetBase()得到的“所屬對象”不同,this自然也就不同了。

  階段性結語

  我的JavaScript之旅先告一段落了(快寫吐了),5篇文章寫下來,至少自己收獲不少,下次面人的時候,終于可以問點JS的東西了。

  this, scope chain, prototype chain, constructor, closure,這些都是學習JavaScript必須懂的東西,每本JS的書都會對這些概念進行描述,我看過幾篇,都是講what,就是要你死記硬背,或者多寫JS,熟能生巧才行。沒有看到一篇講why的。而我把這5篇寫why的博文寫完,再看那些書籍上的描述,真有一覽眾山小的感覺,哈哈。

  至于對這些概念的運用,就需要多寫多看JS代碼了,不想寫那樣的文章了。JS告一段落,要研究別的東西去了。FubuMVC、Ruby on Rails、CQRS……

0
0
 
標簽:JavaScript this
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()