JavaScript中的範圍以及this變數

來源:互聯網
上載者:User

 

今天我想簡單討論下關於Javascript的範圍和this變數。“範圍”的概念就是說,我們的代碼能夠從哪裡去訪問某些函數或者變數,也就是它們所存在的上下文,或者說就是它們被執行的地方。


你可能已經見過有的人寫類似這樣的代碼:

 

function someFunc() {var _this = this;something.on(click, function() {console.log(_this);});};

可是你卻搞不懂其中“var _this=this;”這一句到底是要幹嘛。希望本篇文章能澄清這種疑惑。

 

 

第一種範圍是全域範圍(Global Scope),它的定義很簡單。如果一個函數或者變數是全域的,那麼在任何地方都能夠訪問它們。在瀏覽器裡,全域範圍其實就是window對象。所以如果你在代碼裡這樣定義一個變數:

var x = 9;

那麼實際上你是在window對象上建立一個叫x的屬性,並給它賦值為9。當然如果你願意也可以這樣寫:

window.x = 9;

不過既然它是全域變數,一般人都不會這樣做。window對象是全域的,它上面的所有屬性都可以在代碼裡的任何地方直接存取到。

 

 

另外一種,也是最後一種範圍,就是本地範圍(Local Scope)。JavaScript在函數水平上建立本地範圍,比如:

function myFunc() {var x = 5;};console.log(x); //undefined

因為x是在myFunc()之內聲明的,所以它僅僅在myFunc()內是可以訪問到的。

 

 

一點小提醒:

 

如果你沒有使用var關鍵字來聲明一個變數,那麼它會自動變成全域的。所以這樣寫也是可行的:

function myFunc() {x = 5;});console.log(x); //5

但這並不是一個好主意。這樣做會把全域範圍搞亂,所以這種做法很不推薦。你應該盡量少地在全域範圍裡聲明變數。之所以像jQuery一類的函數庫會這樣做,也是出於這個原因:

(function() {var jQuery = { /* all my methods go here */ };window.jQuery = jQuery.})();

先把所有的東西都放在一個匿名函數的函數體裡,然後再立刻執行這個函數,這樣一來,在這個函數裡聲明的所有變數就都只存在於本地範圍了。而在最後,你再把jQuery對象綁定到window對象上,這樣jQuery就是全域的了,而你也就進而把所有的東西變成是全域可訪問到的了(譯者註:這些變數函數什麼的仍然存在於本地範圍,所以不能從外面直接存取,當然,要訪問的話,只能通過這個唯一的全域的介面:jQuery,這就是所謂的閉包的最大功德)。儘管在這段範例程式碼裡我把jQuery簡化到了不能再簡化的地步,可是本質上說,它的源碼的工作原理就是這樣的。如果你想知道更多細節,強烈推薦你讀一下Paul Irish的文章:10 Things I learned from the jQuery Source。

 

因為本地範圍以函數為單位,所以在一個函數內定義的函數,是可以訪問外面這個包含它的函數的本地變數的:

function outer() {var x = 5;function inner() {console.log(x); //5 }inner();}

不過反過來卻不行,outer()函數並不能訪問inner()裡面的任何變數:

function outer() {var x = 5;function inner() {console.log(x); //5 var y = 10;}inner();console.log(y); //undefined}

目前來看,這都是些很簡單很基本的東西。不過,如果我們要來審視一番this關鍵字,情況就變複雜很多了,我想咱們都遇見過這種情況(譯者註:關於這裡有爭議,見後面註解【1】。):

$(myLink).on(click, function() {console.log(this); //points to myLink (as expected)$.ajax({//ajax set upsuccess: function() {console.log(this); //points to the global object. Huh?}});});

 

每次在你的函數被執行的時候,this變數都是被自動賦值的,至於它的具體值到底是什麼,這取決於該函數被呼叫的方式。JavaScript裡面有幾種主要的呼叫函數的方式,我並不打算現在在這裡一一敘述,不過常用的就那麼三種:作為一個對象的方法被呼叫;或者作為函數獨自被呼叫;或者作為一個事件的處理器(event handler)被呼叫。不同的來電者式將導致this的值是不同的:

function foo() {console.log(this); //global object 譯者註:其實就是window};myapp = {};myapp.foo = function() {console.log(this); //points to myapp object}var link = document.getElementById(myId);link.addEventListener(click, function() {console.log(this); //points to link}, false);

情況一目瞭然。MDN對於第三種情況有詳細的解釋:


通常來說,在一個事件處理器被執行過程中,我們都希望能追蹤到觸發這個事件的對象,尤其是有時候可能會在若干個相似的對象上綁定同一個事件處理器(譯者註:比如說你在一系列連結化物件上綁定了同一個處理click事件的處理器)。當我們用addEventListener()來綁定一個函數的時候,this的值會被改變,注意:this的值實際上是由呼叫者傳遞給函數的。


所以,現在,再回過頭來看一開始的關於“var _this = this;”這一句的用意的疑問,我們才猛然發現已經離答案不遠了。


$(#myLink).on(click, function() {})這句的意圖是當這個DOM元素被點擊時,這個函數便被執行。可是由於這個函數是作為一個事件處理器被呼叫的,所以this變數會指向ID為myLink的DOM元素。而你在Ajax請求裡指定的success方法只是一個常規的函數,所以當它被執行的時候,this被賦值為全域對象。(譯者註:關於這裡有爭議,見後面註解【1】。)


上述原因就是為什麼你總會見到有人寫:var _this = this或者var that = this,或者類似的東西,這樣做的目的是把當前this的值備份留作以後不時之需。關於下面這段代碼裡第二個console.log()究竟應該輸出什麼值,很多人提出了異議,這個問題我以後再討論(譯者註:看來,作者本人沒迴避這個問題,我也是在後面提出異議者之一)。

$(myLink).on(click, function() {console.log(this); //points to myLink (as expected)var _this = this;  //store reference$.ajax({//ajax set upsuccess: function() {console.log(this); //points to the global object. Huh?console.log(_this); //better!}});});

另外也有一些呼叫函數的方法是可以主動明確地指定this的值的,不過因為這篇文章現在已經夠長了,所以不如等改天再詳細討論吧。如有問題請留言,我會一一回複。

 

註解【1】:
這個問題已經有人在原文下面的評論中提到,就是
success: function() {
console.log(this); //points to the global object. Huh?
}
輸出的並不是window,而是另外一個對象,看起來是jQuery用來記錄Ajax設定參數的一個對象。


假設說我在一個載入了jQuery的網頁裡插入了一個id為vince的標籤,然後我執行:

$(#vince).on(click, function() {    console.log(this);// options :var my_city=Washington,USA;var my_key=xxxxxxxxxxxxxxxxxxxxxx;var no_of_days=2;// build URI:var uri=http://free.worldweatheronline.com/feed/weather.ashx?q=+my_city+&key=+my_key+&format=json&no_of_days=+no_of_days+&includeLocation=yes;// uri-encode it to prevent errors :uri=encodeURI(uri);    $.ajax({        type: POST,url: uri,complete: function() {            console.log(this);        }    });});

我本想類比用Ajax訪問一個公用的並且仍然活躍的web service,用來做這個示範,而不是用我本地的server,但是找了半天也找不到,最後找到一個提供天氣情況的,可是需要註冊才有API碼,我沒有時間去完成註冊,所以上面的my_key是“xxxxx”,因此,這個Ajax請求將不會成功返回。所以我沒有像原文那樣使用success事件處理函數,而是使用了complete事件,這樣不論如何保證它都會被調用。那麼看到的結果其實是:

 

實際上關於這個被輸出的東西到底是怎麼來的,我們可以在Chrome裡面用單步調試的方法來追蹤,不過前提是,要載入非min版本的jQuery源碼,不然沒有人看的懂,所以這需要時間,我想還是等我比較閑的時候再來搞吧。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.