深入理解Delete(JavaScript)


 Delete  眾所周知是刪除對象中的屬性. 但如果不深入了解delete的真正使用在項目中會出現非常嚴重的問題 (:

Following 是翻譯  kangax 的一篇文章 "Understanding Delete";


PS:文章主要分為8部分, 有時間弄點瓜子兒, 整壺茶了解一下. (小編建議直接看原文地址, 以下翻譯僅供自己學習使用);

相信大家如果有時間看完會有收獲...也希望有大牛能指出其中翻譯的不足...

目錄:

§  原理

  § Type of Code (代碼級別)

   § Execution context(執行上下文) 

   § Activation object / Variable object  

   § Property attributes(屬性特性)

   § Build-ins and DontDelete(嵌入式/不可刪除) 

   § Undeclared assignments (未聲明任務)

§  FireBug confusion  (奇異的FireBug)

   § Deleting variables via eval(通過eval刪除變量)

§  Browsers compliance (瀏覽器兼容性)

§  'delete' and host objects

§  ES5 strict mode 

§  Summary  

 

================Enein翻譯===================

先上例子:

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

忽略幾個丟失分號. 這段代碼你能看出什么問題? 

當然這個問題很明顯 "delete sum" 是不會成功的. delete 返回的值不應該是 "true" , "typeof sum" 返回的結果也不是"undefined"。造成問題的原因是 在JavaScript中"delete 是不可以刪除變量的".

這個例子有問題? 排版問題? 是個變相題? 應該都不是. 上面的代碼會在FireBug Console下正確輸出.(你可以快速測試一下) 仿佛在FireBug下有它自己的刪除規則. 這...給我干蒙了. 到底是怎么回事? 我們來討論一下.

要想知道答案我們首先要先知道 "delete" 操作符在JavaScript中的實際是怎樣工作的: (主要從3個方向 什么情況能正確刪除, 什么時候不能刪除, 為什么);

讓我們帶着疑問繼續往下看:(I’ll try to explain this in details

我們來看一個FireBug的古怪行為.並了解其實這是正常的.

我們將深入了解下 "聲明變量","函數","加入屬性"是怎么工作的並在適當的時候刪除它們.我們還會看一下瀏覽器的兼容性以及其一些常見的Bugs, ECMAScript 5 strict mode 和如何改變delete 操作符的行為

注釋: 在這里我將使用JavaScript和ECMAScript(這是真正意義上的ECMAScript除非有明確聲明為Mozilla's ECMAScript擴展)

PS:這一段是作者對Mozilla MSNMSDN 上的兩篇文章發表的個人看法(他會認為practically useless)這里不做翻譯有興趣的同學可以點其鏈接自行查看分析.

 

§ 原理

  為什么它能刪除對象的屬性

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

  變量卻不能, like this:

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

  函數也不允許, like this:

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

注意當 屬性不能被刪除的時候將返回 false 

要明白理解這些, 要需要進一步理解變量實例概念、屬性的特性。(有限的是在JavaScript相關書籍中涉及的知識還是比較少的)以下就要詳細的介紹.

(如果你不關心這些東西為什么工作方式是這樣的,那就skip this chapter)

 

§ Type of Code (代碼級別) [ps:代碼級別是出於自己的理解]

  在ECMAScript中有3種作用域: Global code(全局作用域), Function code(函數作用域), Eval code(Eval作用域) 以下對三種級別的描述.

  Global code :  當一段文本做為一個程序的時候, 它是在全局作用域下執行的. 在瀏覽器環境中通常寫在SCRIPT標簽下的內容會被解析, 因為也算是全局作用域

  Function code : 任何東西在function里是會隨着function執行並執行.很明顯這是屬於函數作用域, 在瀏覽器中事件屬性通過也會被當作函數作用域.(e.g <p onclick=""/>)

  Eval code : 最后,  在eval函數體里的代碼就是 Eval作用域.很快我們就會看到為什么這個類型是特殊的.

 

§ Execution context(執行上下文)  

  當ECMAScript的代碼執行的時候, 它就一直在某一個執行上下文中,  “Execution context 是一個抽象的實體” 它會使我們明白作用或和變量實例化的過程. 對以上中種類型的范圍, 它們就是一個執行上下文.

當function被執行的時候, 這個實體的上下文就是"Function code", 當代碼是在Global code下被執行的時候, 那么它就是 "Global code" , 也 so on. 

  就你像我們看到的那樣, 執行上下文邏輯上屬於一個 stack (棧) 首先它可能是執行在全局作用域下的, 它擁有自己的上下文, 在這段代碼里, 可能還會調用一個function, 這個function也會有自己的上下文, 在這個function里有可能還會調用一個function, function還可以遞歸調用, 以此類推.

 

§ Activation object / Variable object   

  每個執行上下文都會和一個("Variable Object")可變的對象相關聯, 和執行上下文類型類似, Variable Object 也是一個抽象的實體. 通過一種機制來描述變量初始化過程, 現在, 我們感興趣的是 變量和函數聲明的時候 實際上是作為 Variable Object 的屬性被加入的.

  當這個實體的執行上下文為 全局作用域的時候, 那么這個全局的對象會當做一個 "Variable 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

OK, 所以 全局變量 會變成 全局對象 的屬性, 但對於局部變量它發生了什么, 在函數體內他們是怎么聲明的, 很簡單、它們變成了(Variable Object)變量對象的屬性. 只是在作為Function code的時候有所不同, 一個變量對象不是一個全局對象. 但它會調用一個"Activation object"(激活對象), 每一次給函數分配上下文的時候Activation object將會被創建.

  不僅僅是變量和函數的聲明會成為Activation object的屬性, 函數的形參(形式參數:對應實際參數)和特殊對象Arguments object 注意 Activation object是一種內部機制, 是永遠不可能訪問的程序代碼.

(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 code中的變量聲明是作為 創建變量對象上下文調用時的屬性  Eval code 簡單的使用變量對象的執行上下文, 代碼執行是這樣的:

var GLOBAL_OBJECT = this;


/*'foo' 被創建為一個變量對象調用上下文的屬性, 在這個案例中它是全局對象*/ eval('var foo = 1;'); GLOBAL_OBJECT.foo; // 1 (function(){ /* 'bar' 被創建作為變量對象調用時上下文中的屬性, 在案例中是function鏈中的激活對象*/ eval('var bar = 1;'); /* In abstract terms, ACTIVATION_OBJECT.bar; // 1 */ })();

§ Property attributes(屬性特性)  

  我們就快要明白了, 現在我們清楚的明白變量到底發生了什么(它們屬性之間的變換), 剩下 Property attributes了.每個屬性都會存在0個或多個屬性包括(ReadOnly(只讀),DontEnum(不可枚舉), DontDelete(不可刪除)) 對於今天的話題我們只討論DontDelete.

  當聲明變量和函數變成變量對象的屬性或者一個激活對象(作為一個Function code), 或者全局對象(Global code), 這些屬性被創建並含有DontDelete特性.無論怎么樣, 一些顯式(隱式)屬性分配上也會創建不含有Dontdelete的屬性 為什么有的能有的不能:

 

var GLOBAL_OBJECT = this;

 /* 'foo' 是全局對象屬性 它被創建通過變量聲明所以它存在DontDelete特性
      這就是它為什么不會被刪除
*/

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

 /* 'bar' 是一個全局對象的屬性
      它被創建通過函數聲明所以它存在DontDelete屬性
      這也就是它為什么也刪除不了
*/

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

 /* ‘baz’ 也是全局對象的屬性
      它的創建是通過分配屬性所以它沒有DontDelete是可以刪除的
*/

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

 

§ Build-ins and DontDelete(嵌入式和不可刪除) 

  這節我們說的是, 屬性的一些特殊特性來控制這些屬性可否被刪除(注意: 一些內置的屬性會被默認指定成DontDelete, 固不能被刪除)特殊arguments變量(現在我們知道它是激活對象的屬性)有DontDelete. 同樣的一些function實例的length屬性也存在DontDelete:

(function(){

    /* 不能刪除 'arguments', 它是不可刪除的*/
    delete arguments; // false
    typeof arguments; // "object"

    /* 不能刪除function的lenth屬性, 它也是不可刪除的 */
    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');

§ Undeclared assignments (未聲明的任務)

  未聲明的任務創建一個全局對象的屬性. 除非在全局對象之前你能找到這個屬性是屬性哪個作用域鏈的. 現在我們清楚,屬性任務和變量聲明之間的不同, 后者是是DontDelete屬性, 前者則不是(它應該清楚為什么未被聲明的會創建不含有DontDelete的屬性).

 var GLOBAL_OBJECT = this;

 /* 創建全局屬性通過變量聲明; 屬性不可刪除的*/
  var foo = 1;

 /* 創建全局屬性通過未聲明的任務 其屬性是可刪除的 */
  bar = 2;

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

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

注意在屬性創建期間, 其屬性是被確定的. 后面的任務是不可改變已存在的屬性的, 明白這點是很重要的.

/* 'foo' 作為含有DontDelete的屬性被創建 */
  function foo(){}

/* 之后的任務不能修改其屬性, DontDelete還在 */
  foo = 1;
  delete foo; // false
  typeof foo; // "number"

 /* 但加入屬性是新的, 就不含有DontDelete. */

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

§ FireBug confusion  (奇異的FireBug)

  在FireBug發生了什么? 之前說過在FireBug console中變量的聲明是可以刪除的. 這違背了我們之前說的所有? 好吧, 之前我說過, Eval code的變量聲明時有着特殊的行為, 變量聲明在Eval code里實際上是創建了沒有DontDelete的屬性:

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

同樣對於在Function code里調用:

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

這就是重點, 所有在Firebug console中執行的代碼會被當成是 Eval code來進行解析  所以

和console的不同.

  

§ Deleting variables via eval(通過eval刪除變量) 

  最有意思的是eval的特性, 另一方面ECMAScript能從技術上允許我們去刪除不可刪除的屬性.在同一個上下文中function的聲明是可以被同名變量重寫的.

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

注意 function聲明優先,重寫同名變量(或者, 換句話說, 在變量對象中存在了相同屬性). 這是因為 函數聲明被實例是在變量聲名(Variable declarations)之后, 是允許被覆蓋的不僅函數聲明替換這前屬性的值, 它也能替換它的屬性.

如果我們通過eval來聲明function那么還是可以替換相應的屬性, 因為在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"

不幸的事,  我嘗試各種不能工作的場景, 有可能我會有所疏漏.

 

§ Browsers compliance (瀏覽器兼容性)

   學習這些東西的工作原理是很實用的, 實踐至上. 在瀏覽器兼容上會存在多在的差異.作者做了很多的瀏覽器測試, 最主要的是屬性中含有DontDelete是不可刪除的,相反則然.

當今瀏覽器的脾氣都是很友好的. 我測試的Opera 7.54+, Firefox 1.0+, Safari 3.1.2+, Chrome 4+瀏覽器都是可行的.

Safari 2.x and 3.0.4 是有問題的對於function的參數問題上;這些參數被看做沒有DontDelete特性, 問題主要是我們可以detele它們. 實際上Safari 2.x存在更多的問題(刪除沒有引用的變量e.g delete 1)會拋異常, function的聲明會創建可刪除屬性(不包括變量聲名), 變量聲明在eval中變為不可刪除(除了function聲明).

   Konqueror (3.5)也同樣(刪除function參數會報錯)

Gecko DontDelete Bug 

  Gecko 1.8.x browsers — Firefox 2.x, Camino 1.x, Seamonkey 1.x, etc. 顯示出一個很有意思的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)

比較出乎意外的是IE 5.5 - 8 基本測試都通過了. 只是刪除沒有引用的會報錯(e.g delete 1) , 但其實實際上IE中有更嚴重的BUG是關於全局對象的.

IE BUGS(IE bug) 

  這一章主要是說一下Internat Explorer下的BUGS:

  在IE5.5-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刪除也會報錯.

但說的也不全部, 顯示的創建一個屬性在刪除的時候是一直會報錯的 

    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"

但你要嘗試使用全局對象的屬性的方式來刪除, 則會報錯:

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

小結: 只要是 delete this.x 都不會成功.

Misconceptions (歧義) 

/*...*/

§ 'delete' and host objects

簡單的推算一下delete如下:

  • 如果這個運算對象沒有引用, 返回 true.
  • 如果不是Object的內部屬性, 返回 true.
  • 如果Object有屬性但有DontDelete特性, 返回 false.
  • 其它移除屬性返回 true.

無論怎么, delete操作符在宿主對象上的行為也是不可預知的 這是其實是沒有問題的, 宿主對象是允許(通過規范)去實現各種操作行為比如 read(內部實現[[Get]]方法), write(內部實現[[Put]]方法), delete(內部實現[[Delete]]方法).

之前我們已經討論了IE的差異, delete 某一對象會拋異常, 在一些火狐版本中刪除 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 , 它的解析過程 :

  One step : 被解析成一個引用(不會返回true);

  two step : 是window的內部屬性(不會返回true);

  只有在真正 "delete window.alert" 的時候才真正刪除了嘛? no 它還是沒有被刪除.

小結: 從來不要相信宿主對象

 

§ ES5 strict mode  

  ECMAScript 規范 嚴格格式下會有很多限制, 在這幾種情況下會報語法錯誤: 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 

 同樣的在未指定確定類型的變量在嚴格模式(strict mode)下也是會報詞法錯誤的:

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

 現在我明白了, 在嚴格模式下的這些限制都是很有用的, ECMAScript strict mode 解決了很多問題, 而不是忽視它們, 從中我們也可以通過ECMAScript這些限制, 反向理解來學習更深入的知識. 

§ Summary  

  這篇文章說的太長了, 如果你能靜下心來好好看完, 那你將會明白很多, 這里我只是說了一部分關於Array的delete我這里就不說了, 但希望有興趣的同學可以自己去嘗試(你可以參考MDC  for that particular explanation 文章).

   這是里簡單的做一下在JavaScript中delete的操作:

  • 變量和函數聲明屬性要么是激活對象, 要么是全局對象

  • 屬性里有DontDelete特性的表示不可刪除屬性.
  • 變量和函數聲明只要是在"全局代碼塊"/"函數級代碼塊"中都會有 —— DontDelete.

  • Functions的參數也是屬於激活對象的屬性, 所以也有 —— DontDelete.
  • 變量和函數聲明在Eval代碼塊中的, 都不會創建 —— DontDelete.

  • 為對象加入新的屬性(沒有任何特性), 也是不會創建 —— DontDelete.
  • 不管他們想怎樣, 宿主對象對刪除是會返回狀態的.

 ================Enein翻譯===================


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM