在開始以前,先讓咱們看一段代碼javascript
這段代碼是Firebug控制檯裏的實際結果,初看這段代碼,你以爲有什麼問題?但我要說的是,刪除sum應該是失敗的,同時typeof sum的結果不該該是undefined,由於在Javascript裏以這種方式聲明的變量是沒法被刪除的。html
那麼問題出在哪裏?爲了回答這個問題,咱們須要理解delete操做符在各類狀況下的實現細節,而後再回過頭來看Firebug的這個看似「詭異」的輸出。java
P.S 沒有特殊聲明的狀況下,下文中所提到的Javascript都指的是ECMAScript規範。算法
1. 理論瀏覽器
delete操做符一般用來刪除對象的屬性:app
而不是通常的變量:函數
或者是函數:性能
注意delete只有在沒法刪除的狀況下才會返回false。ui
爲了理解這一點,咱們必須解釋一下變量初始化以及變量屬性的一些基本概念--很不幸的是不多有Javascript的書能講到這些。若是你只想知其然而不是知其因此然的話,你徹底能夠跳過這一節。this
代碼的類型
在ECMAScript中,有三種可執行代碼類型:全局代碼、函數代碼、eval代碼。
1. 當一段代碼被當作程序段運行的時候,它是在全局做用域下執行的,也就是全局代碼。在瀏覽器環境下,一般<SCRIPT>元素就是一段全局代碼。
2. 全部在function中聲明的代碼便是函數代碼,最多見的是HTML元素的響應事件(<p onclick="...">)。
3. 傳入內建的eval函數中的代碼段稱爲eval代碼,稍後咱們會看到這種類型的特別性。
執行上下文(Execution Context)
在ECMAScript代碼執行的時候,老是會有一個執行的上下文。這是一個比較抽象的概念,但能夠幫助咱們理解做用域以及變量初始化的相關過程。對於以上三種代碼段類型,都有一個相應的執行上下文,好比函數代碼有函數上下文,全局代碼有全局上下文,等等。
邏輯上執行上下文相互間能夠造成堆棧,在全局代碼執行的最開始會有一個全局上下文,當調用一個函數的時候會進入相應函數的上下文,以後又能夠再繼續調用其餘的函數亦或是遞歸調用本身,這時執行上下文的嵌套相似於函數調用棧。
Activation object / Variable object
每一個執行上下文都和一個Variable object(變量對象)相關聯 ,這也是一個抽象的概念,便於咱們理解變量實例化機制:在源代碼中聲明的變量和方法實際上都是做爲屬性被加入到與當前上下文相關聯的這個對象當中 。
當執行全局代碼的時候,Variable object就是一個全局對象,也就是說全部全局變量和函數都是做爲這個變量的屬性存在。
那麼對於在函數中聲明的變量呢?狀況是相似的,函數中聲明的變量也是被當作相應上下文對象的屬性,惟一的區別是在函數代碼段中,這個對象被稱爲Activation object(活動對象)。每次進入一個函數調用都會新建一個新的活動對象。
在函數段中,並非只有顯式聲明的變量和函數會成爲活動對象的屬性,對於每一個函數中隱式存在的arguments對象(函數的參數列表)也是同樣的。注意活動對象實際上是一種內部機制,程序代碼是沒法訪問到的。
最後,在evel代碼段中定義的變量都是被加入到當前執行eval的上下文環境對象中,也就是說進入eval代碼時並不會新建新的變量對象,而是沿用當前的環境。
變量屬性的標記
咱們已經知道聲明變量時發生了什麼(他們都變成了當前上下文對象的屬性),接下來咱們就要看一下屬性到底是怎麼樣一回事。每個變量屬性均可以有如下任意多個屬性: ReadOnly, DontEnum, DontDelete, Internal。你能夠把這些當作標記,標明瞭變量屬性能夠持有的某種特性。這裏咱們最感興趣的就是DontDelete標記。
在聲明變量或者函數時,他們都變成了當前上下文對象的屬性--對於函數代碼來講是活動對象,對於全局代碼來講則是變量對象,而值得注意的是這些屬性在建立時都帶有DontDelete標記,可是顯式或者隱式的賦值語句所產生的屬性並不會帶有這個標記!這就是爲何有一些屬性咱們能夠刪除,但另外一些卻不能夠:
內建對象與DontDelete
DontDelete就是一個特殊的標記,用來代表某一個屬性可否被刪除。須要注意的是一些內建的對象是自動持有這個標記的,從而不能被刪除,好比函數內的arguments,以及函數的length屬性。
函數的傳入參數也是同樣的:
非聲明性賦值
你可能知道,非聲明性的賦值語句會產生全局變量,進而變成全局變量對象的屬性。因此根據上面的解釋,非聲明性的賦值所產生的對象是能夠被刪除的:
須要注意的是屬性標記諸如DontDelete是在這個屬性被建立的時候 產生的,以後對該屬性的任何賦值都不會改變此屬性的標記!
2. Firebug的困擾
如今再讓咱們回到最開始的問題,爲何在Firebug控制檯裏聲明的變量能夠被刪除呢?這就要牽涉到eval代碼段的特殊行爲,也就是在eval中聲明的變量建立時都不會帶有DontDelete標記!
在函數內部也是同樣的:
這就是致使Firebug"詭異"行爲的罪魁禍首: 在Firebug控制檯中的代碼最終將經過eval執行,而不是做爲全局代碼或函數代碼。顯然地,這樣聲明出來的變量都不會帶DontDelete標記,因此才能被刪除!(譯者:也不能太信任Firebug啊。)
3. Browsers Compliance
//譯者:這一節講了主流瀏覽器對一些delete的特殊狀況的不一樣處理,篇幅所限暫不贅述,有興趣的能夠參看原文。
4. IE bugs
是的,你沒有看錯,整個這一節都是在講IE的bug!
在IE6-8中,下面的代碼會拋出錯誤(全局代碼):
看起來彷佛在IE裏變量聲明並不會在全局變量對象裏產生相應的屬性。還有更有趣的,對於顯式賦值的屬性老是會在刪除時出錯,並非真正拋出錯誤,而是這些屬性彷佛都帶有DontDelete標記,和咱們的設想相反。
但下面的代碼代表,非聲明性的賦值產生的屬性確實是能夠刪除的:
不過當你試圖經過全局變量對象this來訪問x的時候,錯誤又來了:
總而言之,經過全局this變量去刪除屬性(delete this.x)總會出錯,而直接刪除該屬性(delete x)時:若是x是經過全局this賦值產生會(this.x=1)致使錯誤;若是x經過顯式聲明建立(var x=1)則delete會像咱們預料的那樣沒法刪除並返回false;若是x經過非聲明式賦值建立(x=1)則delete能夠正常刪除。
對於以上的問題,Garrett Smith 的一個解釋是"IE的全局變量對象是經過JScript實現,而通常的全局變量是由host實現的。"(ref: Eric Lippert’s blog entry )
咱們能夠本身驗證一下這個解釋,注意this和window看上去是指向同一個對象,可是函數所返回的當前環境的變量對象卻和this不一樣。
5. 錯誤的理解
//譯者: 大意爲網上對Javascript一些行爲有各類不一樣的解釋,有的甚至可能徹底矛盾,不要輕易相信別人的解釋,試着本身去尋找問題的核心:)
6. delete與宿主對象(host objects)
delete的大體算法以下:
1. 若是操做對象不是一個引用,返回true
2. 若是當前上下文對象沒有此名字的一個直接屬性,返回true(上下文對象能夠是全局對象或者函數內的活動對象)
3. 若是存在這樣一個屬性可是有DontDelete標記,返回false
4. 其餘狀況則刪除該屬性並返回true
然而有一個例外,即對於宿主對象而言,delete操做的結果是不可預料的。這並不奇怪,由於宿主對象根據不一樣瀏覽器的實現容許有不一樣的行爲,這其中包括了delete。因此當處理宿主對象時,其結果是不可信的,好比在FF下:
總而言之,任什麼時候候都不要相信宿主對象。
7. ES5 strict mode
爲了能更早地發現一些應該被發現的問題,ECMAScript 5th edition 提出了strict mode的概念。下面是一個例子:
刪除不存在的變量:
對未聲明的變量賦值:
能夠看出,strict mode採用了更主動而且描述性的方法,而不是簡單的忽略無效的刪除操做。
8. 總結