Javascript中的delete

ndblog發表於2015-10-19

一、問題的提出 

我們先來看看下面幾段程式碼,要注意的是,以下程式碼不要在瀏覽器的開發者工具(如FireBug、Chrome Developer tool)中執行,原因後面會說明: 

為什麼我們可以刪除物件的屬性: 

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操作符返回true時表示可以刪除,返回false表示不能刪除 

  要理解這一點,我們首先需要掌握像變數例項化和屬性特性這樣的概念--遺憾的是這些內容在一些javascript的書中很少講到。理解它們並不難,如果你不在乎它們為什麼這麼執行,你可以隨意的跳過這一部分。 

二、程式碼型別 

  在ECMAScript中有三種型別的可執行程式碼:Global code(全域性程式碼)、Function code(函式程式碼)和 Eval code(放在Eval中執行的程式碼)。 

var x=1;//Global code 
function test(){ 
var y=2;//Function Code 
eval("var z=3");//Eval Code in Function 
} 
eval("function evalTest(){}");//Eval Code in Global 

三、執行上下文 

  當ECMAScript 程式碼執行時,它總是在一定的上下文中執行,執行上下文是一個有點抽象的實體,它有助於我們理解作用域和變數例項化如何工作的。對於三種型別的可執行程式碼,每個都有執行的上下文。當一個函式執行時,可以說控制進入到函式程式碼(Function code)的執行上下文。全域性程式碼執行時,進入到全域性程式碼(Global code)的執行上下文。 

  正如你所見,執行上下文邏輯上來自一個棧。首先可能是有自己作用域的全域性程式碼,程式碼中可能呼叫一個函式,它有自己的作用域,函式可以呼叫另外一個函式,等等。即使函式遞迴地呼叫它自身,每一次呼叫都進入一個新的執行上下文。 

四、Activation 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 

全域性變數變成了全域性物件的屬性,但是,那些在函式程式碼(Function code)中定義的區域性變數又會如何呢?行為其實很相似:它成了變數物件的屬性。唯一的差別在於在函式程式碼(Function code)中,變數物件不是全域性物件,而是所謂的啟用物件(Activation object)。每次函式程式碼(Function code)進入執行作用域時,就會建立一個啟用物件(Activation object)。 

  不僅函式程式碼(Function code)中的變數和函式成為啟用物件的屬性,而且函式的每一個引數(與形參相對應的名稱)和一個特定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 程式碼(Eval code)中宣告的變數作為正在呼叫的上下文的變數物件的屬性被建立。Eval 程式碼(Eval code)只使用它正在被呼叫的哪個執行上下文的變數物件。 

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 
*/ 

})(); 

五、屬性特性 

  現在變數會怎樣已經很清楚(它們成為屬性),剩下唯一的需要理解的概念是屬性特性。每個屬性都有來自下列一組屬性中的零個或多個特性--ReadOnly, DontEnum, DontDelete 和Internal,你可以認為它們是一個標記,一個屬性可有可無的特性。為了今天討論的目的,我們只關心DontDelete 特性。 

  當宣告的變數和函式成為一個變數物件的屬性時--要麼是啟用物件(Function code),要麼是全域性物件(Global code),這些建立的屬性帶有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 變數(或者,正如我們現在瞭解的,啟用物件的屬性),任何函式例項的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`); 

 

七、未宣告的賦值 

  簡單地就是未宣告的賦值在一個全域性物件上建立一個可刪除的屬性。 

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;//可理解為 window.bar=2; 根據上面的第五點是可以刪除的 

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

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

請注意,DontDelete特性是在屬性建立的過程中確定的,後來的賦值不會修改現有屬性已經存在的特性,理解這一點很重要。 

/* `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" 

 

八、Eval code 
  在Eval中建立的變數或方法比較特別,沒有DontDelete特性,也就是說可以刪除。 

eval("var x = 1;"); 
console.log(x); // 1 
delete x; 
console.log(typeof x); // undefined 

eval("function test(){ var x=1; console.log(delete x);/* false */;return 1;}"); 
console.log(test()); // 1 
delete test; 
console.log(typeof test); // undefined 

 

注意,這裡說的在Eval中建立的變數或方法不包括方法內部的變數或方法,如上面程式碼中的紅色部分,仍然跟之前講的一致:不能被刪除。 

九、FireBug的困惑 

  我們看一段在FireBug中執行的程式碼結果: 

var x=1; 
delete x; 
console.log(typeof x);//undefined 

function y(){ 
var z=1; 
console.log(delete z);//false 
} 
y(); 
delete y; 
console.log(typeof y);//undefined 

 

這明明是違反上述規則的,但跟上面第八點對比後發現,這正在程式碼在eval中執行的效果。雖然沒有證實,但我猜測FireBug(Chrome Developer tool)中控制檯程式碼是用eval執行的。 

所以,當大家在測試JS程式碼時,如果涉及到當前上下文環境時特別要注意。 

十、delete操作符刪除的物件 

  C++中也有delete操作符,它刪除的是指標所指向的物件。例如: 

class Object { 
public: 
Object *x; 
} 

Object o; 
o.x = new Object(); 
delete o.x; // 上一行new的Object物件將被釋放 

 

但Javascript的delete與C++不同,它不會刪除o.x指向的物件,而是刪除o.x屬性本身。 

var o = {}; 
o.x = new Object(); 
delete o.x; // 上一行new的Object物件依然存在 
o.x; // undefined,o的名為x的屬性被刪除了 

 

 在實際的Javascript中,delete o.x之後,Object物件會由於失去了引用而被垃圾回收, 所以delete o.x也就“相當於”刪除了o.x所指向的物件,但這個動作並不是ECMAScript標準, 也就是說,即使某個實現完全不刪除Object物件,也不算是違反ECMAScript標準。 

  “刪除屬性而不是刪除物件”這一點,可以通過以下的程式碼來確認。 

var o = {}; 
var a = { x: 10 }; 
o.a = a; 
delete o.a; // o.a屬性被刪除 
o.a; // undefined 
a.x; // 10, 因為{ x: 10 } 物件依然被 a 引用,所以不會被回收 

另外,delete o.x 也可以寫作 delete o[“x”],兩者效果相同。 

十一、其他不能被刪除的屬性 

  除了上面說過的內建屬性(即預定義屬性)不能被刪除外,prototype中宣告的屬性也不能delete: 

function C() { this.x = 42; } 
C.prototype.x = 12; 
C.prototype.y = 13; 

var o = new C(); 
o.x; // 42, 建構函式中定義的o.x 

delete o.x; //true 刪除的是自身定義的x 
o.x; // 12, prototype中定義的o.x,即使再次執行delete o.x也不會被刪除 

delete o.y; //true,因為 o自身沒有o.y屬性,y存在於prototype鏈中,也就是說物件自身屬性和prototype屬性是不同的 
o.y; //13 

 

小結 

  上面說了那麼多,希望對大家認識JavaScript中的Delete有所幫助。由於水平有限,不保證完全正確,如果發現錯誤歡迎指正。 

作者:Tyler Ning

出處:http://www.cnblogs.com/tylerdonet/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,如有問題,可以通過以下郵箱地址williamningdong@gmail.com
 聯絡我,非常感謝。


相關文章