javascript必知必會之this關鍵字及scope
Javascript this關鍵字一般來說大家并不陌生,但是如何更好的用好Javascript this關鍵字卻需要我們仔細思考,希望本文對廣大程序員有所幫助。
摘要
本系列博文主要談一些在 Javascript 使用中經常會混淆的高級應用,包括: prototype, closure, scope, this關鍵字. 對于一個需要提高自己Javascript水平的程序員,這些都是必須要掌握的.
本節主要介紹this關鍵字和scope.
Contents
摘要引入this關鍵字關于apply和callscope詳述一些說明結論后記參考資料本文的源碼
引入
作為一個程序員, 你可能早已經習慣于面向對象語言中指代當前對象的引用(或者指針), 如的c++中的this或者 python 中的self,當然具有OO屬性( Javascript 其實更多的是一種所謂的函數式語言)的 Javascript 同樣, 它也具有引用當前屬性的對象的指針(或者引用), 也就是this關鍵字.
為了理解this關鍵字,如果你只想記住一句話,那應該是 this關鍵字總是指向當前函數的所有者對象(執行空間), 至于這句話如何理解, 可以參見下面的詳細說明.
那么什么是 scope 呢?
wikipedia 中的解釋是 In computer programming, scope is an enclosing context where values and expressions are associated. 中文即是所謂的 作用域, 它指明的是一個數值或者表達式所關聯的上下文(能夠被引用的執行空間).
scope 與this有什么關系呢? 如果從上面的定義來看, this指向的總是當前引用此函數的對象,而當你要判斷當前引用的對象時, 這時你就得弄清楚當前函數所在的 scope. 具體可見下面的分析.
Javascript this關鍵字
請看下面的幾個例子.
一個 python 的例子:
class Person(object): """a person class """ def __init__(self, name): self.name = name #這里的self指向的是實例化后的對象,如下面中的zhutao def get_name(self): return self.name zhutao = Person("zhutao") print zhutao.name
一個 javascript 的例子:
window.name = "zhutao from window" var get_name = function(){ return this.name; // this的具體指向只能在運行時才能確定,也就是確定運行時調用其的對象 }; alert(get_name()); // 輸出zhutao from window, get_name調用的對象為window var obj = {} obj.name = "zhutao from obj"; alert(get_name.apply(obj)); // 輸出zhutao from obj, 我們強制地使用了 apply來更改調用的對象,使其指向obj var innerobj = { "name" : "zhutao from innerobj" }; innerobj.get_name = get_name; // 使得innerobj的get_name方法指向了global scope的get_name函數 alert(innerobj.get_name()); // 輸出zhutao from innerobj, 此時this指向的是innerobj
那么從上面的簡單例子來看, this 總是在 運行時 才能確定其具體的指向, 也才能知道它的調用對象.而 這點也正是 動態語言 一個重要特性.
那么如何確定當前this指向的引用對象呢? 通常可以這樣判斷:
1. 如果在global的scope(可以參見下面的說明來明確什么是global scope)來調用,則指向的是bowser的頂級對象window 例如: get_name()
2. 如果, 有類似于這樣的引用, innerobj.get_name() 則很顯然this指向的是innerobj
3. 如果我們使用了apply, call來進行強制的引用對象指向, 則也會很顯然地指向強制的對象,如 get_name.apply(obj).
關于apply和call
這2個關鍵字可以很簡單地理解為 進行this引用對象(運行空間)強制轉換, 二者的語法如下:
fun.call(object, arg1, arg2, ...)
fun.apply(object, [arg1, arg2, ...])
二者目的是相同的(動態更改函數的運行空間, 或者稱作更改this指向的對象), 只是在提供給函數的參數上的調用方法不同.
示例代碼如下:
var test_call_apply = function(name, gender, school){ alert(this.age + name + gender + school); }; test_call_apply.call({age:24}, "zhutao", "male", "ISCAS"); test_call_apply.apply({age:24}, ["zhutao", "male", "ISCAS"]);
scope詳述
先看下面幾個例子:
var global_scope = "I'm global"; var fun = function(){ var fun_scope = "I'm in fun scope"; return innerfun(){ var inner_func_scope = "I'm in the inner fun scope"; return global_scope + fun_scope + inner_func_scope; //此處的引用是重要的,請特別注意 }; }; alert(fun()());
請注意上面的代碼,其中:
1. global_scope 它是global scope
2. fun_scope 它是 位于一個函數的scope
3. inner_func_scope 是一個位于一個函數內的函數的scope
你也可以繼續內嵌函數, 那么會生成若干個scope.
于是有個問題出現了, 為什么innerfun方法可以引用不在它自身scope的變量?
在回答這個問題之前,需要引入一個概念 scope chain. 所謂的 scope chain 是指 在 javascript 的代碼中形成的一個具有優先順序, 相關的作用域的鏈.
以上面的代碼為例,
1. 對于global的scope而言,它會為自己建立一個global的scope chain(當然此時,這個鏈只有一個scope).
2. 對于fun函數的scope而言, 它首先建立一個與global相同的scope chain,然后再加入自己的scope(此時,這個 鏈有2個scope), 類似于這樣的結構: global==>fun
3. 對于innerfun而言,除了fun函數所具有的鏈外,它還會加入自己的scope(當然,此時這個鏈有3個scope), 類似于這樣的結構: global==>fun==>innerfun
scope chain具有下面的特征:
1. 有序
2. 每當建立一個函數時,會自動生成一個scope并加入自己的scope chain中
3. 這個chain類似于一種棧,在查找變量時總是先從頂端查起
參見下圖:
上圖的3個部分對應上面代碼中的三個變量的scope, 并且在對每個變量求值時,是按照 圖中的scope chain從上到下依次查找,找到即返回值或者直到窮舉了scope chain返回undfined.
那么現在回答上面那個問題:
其實也很好理解, 在計算某個表達式時, 它會對自己的scope chain進行從上到下的查找,如果找到了 它會立即返回這個值,如果找完了整個chain也沒有找到,則返回undefined.
這個查找機制也就決定了,通常位于chain的前端的scope有更高的優先級.
例如 javascript 在計算 global_scope + fun_scope + inner_func_scope; 這個表達式時, 它會查找上面圖示中的scope chain,從而確定出最后的結果.
一些說明
如果你弄清楚了上面的論述, 應該說你對this關鍵字和scope已經具有完全的知識基礎了,但是 我們需要在實際中更好地使用和理解這些概念,這樣才能把能力上升到別一個層次, 這也即所謂的 理論與實踐 的關系.
請看下面這個例子:
var change_color = function(){ this.style.color = "red"; }; window.onload = function(){ var text = document.getElementById("text"); text.onclick = change_color; //此時this指向的是text這個對象(dom對象) }; // 下面這行代碼是在body中 <span id="another" onclick="change_color()">My color will be changed2.span> //這點需要特別注意, inline script指向的是window,此處會無定義
需要特別注意的是:
inline event registration中的this并非指向的是自己的dom節點,而是global scope的window,這點可以從上面的例子中得到證明這種inline event registration是不可取的, 推薦的是 Unobtrusive Javascript (處理邏輯和頁面結構相分離)結論
Javascript 是一種非常強大的動態語言, 它是 披著C語言外衣的函數式語言, 如果你只當作它是一種 類C的命令式語言,那么你的知識層次還過于低, 而倘若你能夠理解到Javascript 的函數式語言本質, 你在運用 Javascript ,理解 jQuery 及其它的庫, 甚至自己寫一些 Javascript 都會游刃有余的.
后記
本系列的計劃的內容已經結束,除了這些而外, 我還想寫一至二篇補遺的 Javascript 的高級知識來作為本系列的終結. 可能會寫的內容包括:
Javascript 函數式語言特征探究Javascript 相關庫的分析Unobtrusive Javascript 的一些理解和實踐總之, Javascript 本身是很值得探究的一個語言, 也有很多的值得一書的地方, 我希望后續能夠不斷地完成這個計劃.
其實,之前寫過 Django開發必知必會 以及本系列的 Javascript必知必會, 發現在寫這些內容的同時,自己的相關 知識也有了很大的提高, 寫的同時, 站的角度不只是作為一個 自學者 ,而是作為一個 教者 ,我希望能夠看到這些 內容的讀者也能夠受益. 所以我想后續,我可能會寫一些 計算機科學 的其它專題, 如:
python必知必會正則表達式必知必會Web開發必知必會等等如果這個"宏偉"的計劃得以完成, 我想也就成就我自已定義的一個 優秀程序員 的知識基礎.
這是一個初步的計劃, 我會逐漸展開的. 希望大家能夠不斷反饋.