[翻譯] Understanding delete

zzNucker發表於2012-07-31

翻譯自 Perfection Kills: Understanding delete by kangax

理解delete

  1. 理論

    • 程式碼段的型別
    • 執行上下文
    • 活動物件 / 變數物件
    • 屬性的特性
    • 內建屬性與 DontDelete
    • 未宣告的賦值
  2. Firebug的困惑

    • 在eval中刪除變數
  3. 瀏覽器相容性

    • Gecko的DontDelete bug
  4. IE bugs

  5. 誤解

  6. 'delete' 和 宿主物件

  7. ES5嚴格模式

  8. 總結

幾個禮拜前, 我有了個機會去翻閱Stoyan Stefanov的 Object-Oriented Javascript 一書. 這本書在亞馬遜上擁有很高的評價(12篇評論, 5顆星), 所以我很好奇地想看看它到底是不是那麼值得推薦的一本書, 於是我開始閱讀函式的那章. 我非常欣賞這本書解釋事物的方式, 例子們被以一種非常漂亮, 漸進的方式被組織起來, 看起來即便是初學者也能夠輕鬆掌握這些知識. 然而, 幾乎是立刻, 我就發現了一個貫穿整個章節的有趣的誤解——刪除功能函式. 另外還有一些其它錯誤(例如函式宣告與函式表示式的區別), 但是我們目前將不去討論它們.

這本書聲稱:

"函式被作為像一般變數一樣對待-它可以被複制到不同的變數中, 甚至被刪除". 在這個解釋後面附加了這樣一段示例:

var sum = function(a, b) {return a + b;}
var add = sum;
delete sum
true
typeof sum;
"undefined"

忽略掉一些漏掉的分號, 你能看出這幾句程式碼的錯誤在哪麼? 顯然, 錯誤在於刪除sum這個變數的操作是不會成功的. delete表示式不應該返回true, 並且 typeof sum也不應該返回"undefined". 這一切都因為在JavaScript中刪除變數是不可能的. 至少, 在這種宣告方式下是不可能的.

所以, 在這個例子中到底發生了什麼? 它是一個錯誤麼? 抑或是一個特殊用法? 大概不是這樣的. 這一段程式碼事實上是Firebug控制檯中的真實輸出, Stoyan一定是使用了它作為快速測試的工具. 這幾乎就好像是Firebug遵守了其它一些delete的規則一樣. 是Firebug導致了Stoyan誤入歧途! 所以, 這兒到底發生了什麼?

在回答這個問題之前, 我們首先需要理解delete運算子到底在JavaScript中是如何工作的: 到底什麼能夠被刪除, 什麼不能夠被刪除? 今天, 我將嘗試著詳細解釋這個問題. 我們將看看Firebug的"奇怪"行為並且意識到它其實並不是那麼奇怪. 我們將深入瞭解在宣告變數, 函式, 給屬性賦值和刪除它們的這些場景背後到底隱藏了什麼. 我們將看看瀏覽器的相容性和一些最臭名昭著的bug. 我們還將討論ES5的嚴格模式, 和它如何改變delete操作符的行為.

我將交換著使用JavaScript和ECMAScript, 它們都意味著ECMAScript(除非明顯地談論Mozilla的JavaScript實現)

不出所料, 在網路上, 對delete的解釋是相當稀缺的. MDC article大概是最好理解的資源了, 但是, 不幸的是, 它缺失了這個主題的一些有趣的細節. 奇怪的是, 其中一個被遺忘的東西就是Firebug的奇怪表現的原因. 而MSDN reference在這些方面幾乎是無用處的.

Theory

那麼, 為什麼我們能夠刪除物件的屬性:

var o = { x: 1 };
delete o.x; // true
o.x; // undefined

卻不能刪除這樣宣告的物件:

var x = 1;
delete x; // false
x; // 1

或者函式呢:

function x(){}
delete x; // false
typeof x; // "function"

注意: 當一個屬性無法被刪除時,delete操作符只會返回false

要理解這個, 我們首先需要掌握這些有關變數例項和屬性特性的概念——這些概念很不幸地, 很少在JavaScript書中被提及. 我將試著在接下來的幾個段落中簡單地複習一下這些概念. 這些概念是很難理解的!如果你不在乎"為什麼這些東西會以這種方式工作"的話,盡情跳過這一章節好了.

程式碼的型別:

在ECMAScript中, 有3種不同型別的可執行程式碼: 全域性程式碼(Global code), 函式程式碼(Function code)Eval程式碼(Eval code). 這些型別從名稱上來說或多或少是有自解釋性的, 這裡有一個簡短的概述:

  1. 當一段原始碼被看成程式(Program)時, 它將會在全域性環境下被執行, 並且被認為是全域性程式碼(Global code). 在一個瀏覽器環境中, 指令碼元素的內容通常被解釋為程式, 因此被作為全域性程式碼來執行.

  2. 任何直接在一個函式中執行的程式碼顯然被認為是函式程式碼(Function code). 在瀏覽器中, 事件屬性的內容(如 <p onclick="....">)通常被解釋成函式程式碼.

  3. 最後, 被應用到內建函式eval的程式碼文字被解釋成Eval程式碼(Eval code). 很快我們會發現為什麼這種型別是特殊的.

執行上下文(Execution context):

當ECMAScript程式碼執行時, 它通常會發生在特定的執行上下文中.執行上下文是一個有些抽象的實體概念, 它能幫助理解範圍(Scope)和變數例項(Variable instantiation)是如何工作的. 對三種可執行程式碼的每一種, 都有一個執行上下文相對應. 當一個函式被執行的時候, 我們說"程式控制進入了函式程式碼的執行上下文"; 當一段全域性程式碼被執行時, 程式控制進入了全域性程式碼的執行上下文, 等等.

正如你所見, 執行上下文可以在邏輯上構成一個堆疊. 首先, 可能有一段全域性程式碼和其自己的執行上下文, 然後這段程式碼可能會呼叫一個函式, 並帶著它(函式)的執行上下文. 這段函式可以呼叫另外一個函式, 等等等等. 即使函式是遞迴呼叫的, 每次呼叫時被也會進入一個新的執行上下文.

活動物件(Activation object) / 變數物件(Variable Object):

每一個執行上下文都有一個跟其所關聯的所謂變數物件(Variable Object). 類似於執行上下文, 變數物件是一個抽象實體, 一種用來描述變數例項的機制. 有趣之處在於, 在原始碼中宣告的變數和函式通常會被當做屬性(properties)增加到這個變數物件上.

當程式控制進入全域性程式碼的執行上下文時, 一個全域性物件(Global object)被用來作為一個變數物件. 這正是為什麼宣告為全域性的函式變數會變成全域性物件屬性的原因.

/* remember that `this` refers to global object when in global scope */
var GLOBAL_OBJECT = this;

var foo = 1;
GLOBAL_OBJECT.foo; // 1
foo === GLOBAL_OBJECT.foo; // true

function bar(){}
typeof GLOBAL_OBJECT.bar; // "function"
GLOBAL_OBJECT.bar === bar; // true

好, 所以全域性變數會變成全域性物件的屬性, 但是區域性變數(那些在函式程式碼中定義的變數)會發生什麼呢? 其實它們的行為也非常類似: 它們會變成變數物件(Variable object)的屬性. 唯一的不同在於, 當在函式程式碼中時, 一個變數物件並不是全域性物件, 而是所謂的活動物件(Activation object). 活動物件在會每次進入函式程式碼的執行上下文時被建立.

並不是只有在函式程式碼中宣告的變數和函式會變成活動物件的屬性; 這也會在每個函式引數(對應相應的形式引數的名稱)和一個特殊的Arguments物件(以arguments為名稱)上發生. 注意, 活動物件是一個內部描述機制, 在程式程式碼中並不能被訪問.

(function(foo){

  var bar = 2;
  function baz(){}

  /*
  In abstract terms,

  Special `arguments` object becomes a property of containing function's Activation object:
    ACTIVATION_OBJECT.arguments; // Arguments object

  ...as well as argument `foo`:
    ACTIVATION_OBJECT.foo; // 1

  ...as well as variable `bar`:
    ACTIVATION_OBJECT.bar; // 2

  ...as well as function declared locally:
    typeof ACTIVATION_OBJECT.baz; // "function"
  */

})(1);

最後, 在Eval程式碼中宣告的變數會成為呼叫者上下文(calling context)的變數物件的屬性. Eval程式碼只是簡單地使用呼叫它的程式碼的執行上下文的變數物件.

var GLOBAL_OBJECT = this;

/* `foo` is created as a property of calling context Variable object,
  which in this case is a Global object */

eval('var foo = 1;');
GLOBAL_OBJECT.foo; // 1

(function(){

  /* `bar` is created as a property of calling context Variable object,
  which in this case is an Activation object of containing function */

  eval('var bar = 1;');

  /*
  In abstract terms,
  ACTIVATION_OBJECT.bar; // 1
  */

})();

屬性的特性(property attributes)

我們幾乎是已經在這了. 既然我們已經很清楚在變數上發生了什麼(它們變成了屬性), 唯一剩下的需要理解的概念就是屬性的特性(property attributes)了. 每一個屬性可以擁有0個或多個特性, 它們從以下集合中選取: ReadOnly, DontEnum, DontDeleteInternal. 你可以把它們認為是flags —— 一種特性可以在屬性中存在, 也可以不存在. 對於我們今天的討論來說, 我們只對DontDelete感興趣.

當被宣告的變數和函式成為變數物件(或者函式程式碼的活動物件, 或全域性程式碼的全域性物件)的屬性時, 這些屬性在建立時就帶上了DontDelete的特性. 然而, 任何顯式(或隱式)的屬性賦值所建立的屬性將不會被帶上DontDelete特性. 這就是為什麼我們能夠刪除一些屬性, 但刪除不了其它的.

var GLOBAL_OBJECT = this;

/*  `foo` is a property of a Global object.
    It is created via variable declaration and so has DontDelete attribute.
    This is why it can not be deleted. */

var foo = 1;
delete foo; // false
typeof foo; // "number"

/* `bar` is a property of a Global object.
   It is created via function declaration and so has DontDelete attribute.
   This is why it can not be deleted either. */

function bar(){}
delete bar; // false
typeof bar; // "function"

/*  `baz` is also a property of a Global object.
    However, it is created via property assignment and so has no DontDelete attribute.
    This is why it can be deleted. */

GLOBAL_OBJECT.baz = 'blah';
delete GLOBAL_OBJECT.baz; // true
typeof GLOBAL_OBJECT.baz; // "undefined"

內建物件和DontDelete

所以, 這就是有關它(DontDelete)的所有: 屬性的一個特殊特性, 用來控制這個屬性是否能夠被刪除. 注意, 有些內建物件的屬性是指定含有DontDelete的, 所以無法被刪除. 如特殊的arguments變數(或者, 正如我們現在所知道的, 一個活動物件的屬性)擁有DontDelete. 函式例項的length屬性也具有DontDelete屬性.

(function(){

  /* can't delete `arguments`, since it has DontDelete */

  delete arguments; // false
  typeof arguments; // "object"

  /* can't delete function's `length`; it also has DontDelete */

  function f(){}
  delete f.length; // false
  typeof f.length; // "number"

})();

函式引數所對應的屬性也是從建立開始就擁有DontDelete特性的, 所以我們也無法刪除它.

(function(foo, bar){

  delete foo; // false
  foo; // 1

  delete bar; // false
  bar; // 'blah'

})(1, 'blah');

未宣告的賦值:

你可能還記著, 未宣告的賦值會在全域性物件上建立一個屬性, 除非這個屬性已經在這個作用域鏈中全域性物件之前的其它地方被找到. 並且, 現在我們知道屬性賦值和變數宣告的不同之處——後者會設定DontDelete屬性, 但前者不會. 我們必須清楚, 為什麼未宣告的賦值會建立一個可刪除的屬性.

var GLOBAL_OBJECT = this;

/* create global property via variable declaration; property has DontDelete */
var foo = 1;

/* create global property via undeclared assignment; property has no DontDelete */
bar = 2;

delete foo; // false
typeof foo; // "number"

delete bar; // true
typeof bar; // "undefined"

請注意: 特性是在屬性被建立時被決定的, 之後的賦值不會修改已存在屬性的特性. 理解這一點區別非常重要.

/* `foo` is created as a property with DontDelete */
function foo(){}

/* Later assignments do not modify attributes. DontDelete is still there! */
foo = 1;
delete foo; // false
typeof foo; // "number"

/* But assigning to a property that doesn't exist,
   creates that property with empty attributes (and so without DontDelete) */

this.bar = 1;
delete bar; // true
typeof bar; // "undefined"

Firebug的困惑:

在Firebug中發生了什麼? 為什麼在console中宣告的變數可以被刪除, 這不是違背了我們之前所學到的知識麼? 嗯, 就像我之前所說的那樣, Eval程式碼在面對變數宣告時會有特殊的表現. 在Eval中宣告的變數實際上是作為不帶DontDelete特性的屬性被建立的.

eval('var foo = 1;');
foo; // 1
delete foo; // true
typeof foo; // "undefined"

同樣, 類似的, 當在函式程式碼中呼叫時:

(function(){

  eval('var foo = 1;');
  foo; // 1
  delete foo; // true
  typeof foo; // "undefined"

})();

這就是Firebug反常行為的依據. 在console中的所有文字都會被當做Eval程式碼來解析和執行, 而不是全域性或函式程式碼. 顯然, 這裡宣告的所有變數最後都會成為不帶DontDelete特性的屬性, 所以它們都能被輕鬆刪除. 我們需要了解這個在全域性程式碼和Firebug控制檯之間的差異.

通過Eval來刪除變數:

這個有趣的eval行為, 再加上ECMAScript的另一個方面, 可以在技術上允許我們刪除"non-deletable"的屬性. 有關函式宣告的一點是, 它們能夠覆蓋相同執行上下文中同名的變數.

function x(){ }
var x;
typeof x; // "function"

注意函式宣告是如何獲得優先權並且覆蓋同名變數(或者, 換句話說, 在變數物件中的相同屬性)的. 這是因為函式宣告是在變數宣告之後被例項化的, 並且被允許覆蓋它們(變數宣告). 函式宣告不僅會替換掉一個屬性的值, 它還會替換掉那個屬性的特性. 如果我們通過eval來宣告一個函式, 那個函式就應該會用它自己的特性來替換掉原有的(被替換的)屬性的特性. 並且, 由於通過eval宣告的變數會建立不帶DontDelete特性的屬性, 例項化這個新函式將會實際上從屬性中刪除已存在的DontDelete特性, 從而使得一個屬效能夠被刪除(並且, 顯然會將其值指向新建立的函式).

var x = 1;

/* Can't delete, `x` has DontDelete */

delete x; // false
typeof x; // "number"

eval('function x(){}');

/* `x` property now references function, and should have no DontDelete */

typeof x; // "function"
delete x; // should be `true`
typeof x; // should be "undefined"

不幸的是, 這種"欺騙"在目前的任何實現中都不起作用. 也許我在這漏掉了什麼, 或者是這種行為只是太晦澀了以至於實現者都沒有注意到它.

瀏覽器相容性:

在理論上了解事物是如何工作的是有用的, 但是實踐卻是最重要的. 當面對變數/屬性的建立/刪除時, 瀏覽器有遵循標準麼? 答案是: 在大多數情況下, 是的.

我寫了一個簡單的測試集來測試瀏覽器對於delete操作符的相容性, 包括在全域性程式碼, 函式程式碼和Eval程式碼下的測試. 測試集檢查了delete操作符的返回值和屬性值是否(像它們應當表現的一樣)真的被刪除了. delete的返回值並不像它的真實結果一樣重要. 如果delete返回true而不是false, 這其實並不重要, 重要的是那些擁有DontDelete特性的屬性沒有被刪除,反之亦然.

現代瀏覽器大致上來說是相當相容的. 除去了我之前提到的eval特點, 如下的瀏覽器通過了全部的測試集: Opera 7.54+, Firefox 1.0+, Safari 3.1.2+, Chrome 4+.

Safari 2.x 和 3.0.4在刪除函式引數時有問題; 這些屬性看起來是不帶DontDelete被建立的, 所以可以刪除它們. Safari 2.x有更多的問題——刪除非引用型別變數(如: delete 1)會丟擲異常; 函式宣告會建立可刪除的屬性(但是, 奇怪的是, 變數宣告卻不會); eval中的變數宣告會變成不可刪除的(但是函式宣告是可刪除的).

跟Safari類似, Konqueror(3.5, 不是4.3)會在刪除非引用型別時丟擲異常(如: delete 1), 並且錯誤地讓函式變數變為可刪除的.

譯者注:

我測試了最新版本的chrome和firefox以及IE, 基本還是保留在除23,24會fail其它均pass的情況. 同時測試了UC和一些手機瀏覽器, 除了諾基亞E72的自帶瀏覽器還會Fail 15,16之外, 其餘的自帶瀏覽器大都與桌面瀏覽器效果一樣. 但值得一提的是, Blackberry Curve 8310/8900的自帶瀏覽器可以pass測試23, 令我很驚訝.

Gecko DontDelete bug:

Gecko 1.8.x 瀏覽器 —— Firefox 2.x, Camino 1.x, Seamonkey 1.x等等. —— 表現出了一個非常有趣的bug, 對屬性的顯式賦值會刪除它的DontDelete特性, 即使這個屬性是通過變數宣告或函式宣告創造的.

function foo(){}
delete foo; // false (as expected)
typeof foo; // "function" (as expected)

/* now assign to a property explicitly */

this.foo = 1; // erroneously clears DontDelete attribute
delete foo; // true
typeof foo; // "undefined"

/* note that this doesn't happen when assigning property implicitly */

function bar(){}
bar = 1;
delete bar; // false
typeof bar; // "number" (although assignment replaced property)

令人吃驚的是, Internet Explorer 5.5 - 8 通過了完整的測試集, 除了刪除非引用型別(如: delete 1)會丟擲異常(就像舊的Safari一樣). 但是在IE下有更嚴重的bugs, 它不是那麼明顯. 這些bugs跟Global object有關.

IE bugs:

這整章都在說Internet Explorer的bugs? 哇! 真是令人吃驚!

在IE中(至少是IE 6-8), 以下表示式會丟擲異常(當在全域性程式碼中執行時):

this.x = 1;
delete x; // TypeError: Object doesn't support this action

這一個也會, 但是會丟擲不同的異常, 這使得事情更有趣了:

var x = 1;
delete this.x; // TypeError: Cannot delete 'this.x'

這看上去好像是在IE中, 全域性程式碼中的變數宣告沒有在全域性物件上建立屬性. 通過賦值來建立屬性(this.x = 1)和之後通過delete x來刪除它會丟擲錯誤. 通過宣告來建立屬性(var x = 1)並且在之後通過delete this.x來刪除它會丟擲另一個錯誤.

但這還不是全部. 通過顯式賦值來建立屬性事實上總會引起在刪除時的丟擲異常. 這裡不止有錯誤, 而且所建立的屬性似乎會擁有DontDelete特性, 而這當然是不應該具有的.

this.x = 1;

delete this.x; // TypeError: Object doesn't support this action
typeof x; // "number" (still exists, wasn't deleted as it should have been!)

delete x; // TypeError: Object doesn't support this action
typeof x; // "number" (wasn't deleted again)

現在, 我們會認為 在IE下, 未宣告的賦值(應當在全域性物件上建立屬性)確實會建立可刪除的屬性.

x = 1;
delete x; // true
typeof x; // "undefined"

但是, 如果你是同通過全域性程式碼中的this引用來刪除這個屬性的話(delete this.x), 就會彈出一個類似的錯誤.

x = 1;
delete this.x; // TypeError: Cannot delete 'this.x'

如果我們想要歸納一下這種行為的話, 看起來是從全域性程式碼中使用delete this.x來刪除變數從來不可能成功. 當問題中的屬性通過顯式的賦值(this.x = 1)來建立時,delete丟擲了一個錯誤; 當屬性是通過未宣告的賦值(x = 1)或通過宣告(var x = 1)來建立時, delete丟擲另外一個錯誤.

delete x, 另一方面來說, 應當只在屬性是通過顯式賦值來建立時丟擲錯誤 ——this.x = 1.如果一個屬性是通過宣告來建立的(var x = 1), 刪除操作從來不會發生, 並且刪除操作會正確地返回false. 如果一個屬性是通過未宣告的賦值來建立的(x = 1), 刪除操作會像期望地一樣工作.

我這9月份又思考了一下這個問題, Garrett Smith建議說在IE下,

"全域性變數物件(The global variable object)是實現為一個JScript物件的, 並且全域性物件是由host來實現的".

Garrett使用了Eric Lippert's blog entry作為參考.

我們多多少少可以通過實施一些測試來確認這個理論. 注意到thiswindow看起來是應當指向同一個物件的(如果我們能夠信任===操作符的話), 但是變數物件(函式宣告所在的那個物件)卻與this所指向的不同.

/* in Global code */
function getBase(){ return this; }

getBase() === this.getBase(); // false
this.getBase() === this.getBase(); // true
window.getBase() === this.getBase(); // true
window.getBase() === getBase(); // false

誤解:

理解事物為何以那種方式工作的美是不可低估的. 我在網路上見過一些有關delete操作符的誤解. 例如, 這個Stackoverflow上的答案(擁有令人吃驚的高rating), 自信地解釋道

"當目標運算元不是一個物件屬性時, delete應當是無操作的".

現在既然我們已經理解了delete操作行為的核心, 這個答案的錯誤也就變得顯而易見了. delete並不會去區分因為變數和屬性(事實上, 對於delete來說, 它們都是引用型別)並且事實上只關心DontDelete特性(和屬性本身是否存在).

看到各種誤解互相反駁也是非常有趣的, 在一個相同的話題中一個人首先建議只delete變數(這將不會有效果, 除非它是在eval中宣告的), 而另一個人提供了一個錯誤的糾正說明delete是如何在全域性程式碼中用於刪除變數, 而在函式程式碼中卻不行.

對於網路上JavaScript的解釋要格外小心, 理想的方法是總去理解問題的本質. ;)

delete和宿主物件(Host Object):

delete的演算法大概是這樣的:

  1. 如果運算元不是引用型別, 則返回true
  2. 如果物件沒有這個名字的直接屬性, 返回true(正如我們所知, 物件可以是活動物件或者全域性物件)
  3. 如果屬性存在但是有DontDelete特性, 返回false
  4. 其它情況, 刪除屬性並且返回true

然而, delete操作符在宿主物件上的行為是難以預測的. 並且這種行為實際上並沒有錯: (根據標準), 宿主物件是被允許對於像read(內部[[Get]]方法), write(內部[[Put]]方法)和delete(內部[[Delete]]方法)其中幾個操作符實現任何行為的. 這種對自定義[[Delete]]行為的寬限就是將宿主物件變得如此混亂的原因.

我們已經見過了一些IE的怪癖, 刪除特定的物件(顯然是指被實現為宿主物件的)會丟擲錯誤. Firefox的一些版本在刪除window.location的時候會丟擲. 當運算元是宿主物件時, 你不可以信任delete的返回值. 讓我們來看看在Firefox中發生了什麼:

/* "alert" is a direct property of `window` (if we were to believe `hasOwnProperty`) */
window.hasOwnProperty('alert'); // true

delete window.alert; // true
typeof window.alert; // "function"

刪除window.alert返回true, 即使這個屬性完全沒有任何應該導致這樣的結果的理由. 它將解析為一個引用(所以不會在第一步就返回true). 這是個window物件的直接屬性(所以不會在第二步返回true). 所以delete唯一能夠返回true的情況就是到達第四步並且真正刪除那個屬性. 然而, 這個屬性從未被刪除.

這個故事的寓意是: 永遠不要相信宿主物件.

ES5 嚴格模式:

所以, 嚴格模式的ECMAScript5給我們帶來了什麼呢? 它介紹了很少的一些限制. 當delete操作符的表示式是一個變數的直接引用, 函式引數或者函式標示符時, 語法錯誤將會被丟擲. 另外, 如果屬性具有內部特性[[Configurable]] == false, 則一個型別錯誤將會被丟擲.

(function(foo){

  "use strict"; // enable strict mode within this function

  var bar;
  function baz(){}

  delete foo; // SyntaxError (when deleting argument)
  delete bar; // SyntaxError (when deleting variable)
  delete baz; // SyntaxError (when deleting variable created with function declaration)

  /* `length` of function instances has { [[Configurable]] : false } */

  delete (function(){}).length; // TypeError

})();

另外, 刪除未宣告的變數(或者說未解析的引用)將也會丟擲語法錯誤:

"use strict";
delete i_dont_exist; // SyntaxError

未宣告的賦值跟嚴格模式下未宣告的變數所表現的行為類似(除了這次是引發引用錯誤而不是語法錯誤):

"use strict";
i_dont_exist = 1; // ReferenceError

正如你現在所明白的, 所有的限制多多少少是有道理的, 因為刪除變數, 函式宣告和 引數會導致這麼多混亂. 與其靜默地忽略刪除操作, 嚴格模式採用了一種更加激進和更具描述性的措施.

總結:

這篇博文最後變得相當的長, 所以我不準備再去談論類似於用delete刪除陣列物件或它的含義是什麼等. 你可以參考MDC文章對其專門的解釋(或者閱讀標準和自己做實驗).

這裡有一份對於JavaScript中刪除操作是如何工作的簡短的總結:

  • 變數和函式宣告是活動物件或者全域性物件的屬性
  • 屬性擁有一些特性, 這其中的DontDelete是決定這個屬效能否被刪除的那個特性.
  • 全域性或者函式程式碼中的變數和函式宣告總是建立帶有DontDelete特性的屬性.
  • 函式引數總是活動物件的屬性, 並且帶有DontDelete.
  • Eval程式碼中宣告的變數和函式總是建立不帶DontDelete的屬性.
  • 新的屬性在建立時是沒有特性的(當然也沒有DontDelete).
  • 宿主物件被允許自己決定如何對delete操作做出反應.

如果你想要對這裡所描述的東西更加熟悉的話, 請參閱 ECMA-262 3rd edition specification.

我希望你能夠享受這篇文章, 並且學到一些新的東西. 歡迎提出任何問題, 建議或者糾正.

譯者注:

此為個人翻譯文章, 其中一些專業詞語的翻譯可能不標準甚至晦澀, 還請大家多多指正. 另外, 今天翻譯完準備發表時, 去搜尋了一下, 發現1年前lizuochao發表過一篇博文, 大致翻譯過這篇文章, 這讓我非常欣慰. 這樣的好文就理應有人翻譯共享. 今日我完全翻譯這篇文章, 就權當是給lizuochao那篇文章的補充.

相關文章