確定對象屬性是否存在
開發人員在 JavaScript 中執行的最基本測試之一是對像上是否存在特定屬性。由於特徵檢測是代碼分叉的首選方法,因此鼓勵開發人員在使用屬性之前測試它們是否存在。由於沒有經驗的開發人員嘗試使用特徵檢測,因此有很多錯誤的 JavaScript 代碼。很多問題在於對 JavaScript 中對象屬性的本質缺乏了解。
屬性從何而來?
在嘗試檢測屬性之前,了解它們的來源很重要。 JavaScript 中有兩種基本類型的屬性:存在於對像上的屬性(也稱為“自己的”屬性)和通過原型鏈繼承的屬性(通常稱為“原型”屬性)。考慮以下幾點:
var person = {
name: "Nicholas"
};
alert(person.name); //"Nicholas"
alert(person.toString()); //"[object Object]"
在這段代碼中,對象 person
只有一個自己的屬性,即 name
.您仍然可以訪問對像上的其他方法,例如 toString()
,但這些都是通過原型鏈繼承的。對象字面量繼承自 Object
type,所以Object
的所有基本方法 可以在實例上訪問。
自有屬性和原型屬性之間的最大區別在於唯一值和共享值之間的區別。自己的屬性屬於該單個對象實例,不受其他同類型實例的影響;原型屬性屬於對象的原型,由於原型可以在多個實例之間共享,因此這些屬性也可以在多個實例之間共享。這是另一個例子:
var person2 = Object.create(person);
var person3 = Object.create(person);
alert(person2.name); //"Nicholas"
alert(person3.name); //"Nicholas"
person.name = "Adam";
alert(person2.name); //"Adam"
alert(person3.name); //"Adam"
此示例使用 <a href="http://javascript.crockford.com/prototypal.html">Object.create()</a>
來自 ECMAScript 5 的方法來創建兩個對象,person2
和 person3
, 繼承自 person
. person2
的原型 和 person3
是 person
,所以 name
實際上是一個原型屬性,可以通過 person2
訪問 和 person3
.這就是為什麼在兩個對像上顯示 name 的值會導致相同的值:它們都共享原型屬性 name
.這意味著當 person.name
直接更改,可以從實例中訪問更改。
了解 name
很重要 是兩個 person2
的原型屬性 和 person3
, 但它是 person
的自己的屬性 .您只能將值分配給自己的屬性,因此嘗試為原型屬性分配值實際上會導致創建一個新的同名屬性。示例:
alert(person2.name); //"Nicholas"
alert(person3.name); //"Nicholas"
person2.name = "Adam";
alert(person2.name); //"Adam"
alert(person3.name); //"Nicholas"
由於您不能分配給原型屬性,因此為 person2.name
分配一個新值 實際上在 person2
上創建了一個新的自己的屬性 稱為 name
.自己的屬性總是隱藏原型屬性,所以下次訪問 person2.name
,您訪問的是自己的屬性而不是原型屬性。這將一直持續到使用 delete
刪除自己的屬性 ,如:
delete person2.name;
alert(person2.name); //"Nicholas"
你只能調用 delete
在自己的屬性上刪除它(調用原型屬性什麼都不做)。一旦擁有自己的屬性name
被刪除,原型屬性 name
沒有任何陰影 所以 person2.name
現在指原型屬性。
注意:雖然所有原生對像類型 (Array
, Boolean
, Date
, 所有 Error
變體,Function
, Number
, RegExp
, 和 String
) 繼承自 Object
,非本地對像類型,例如那些在瀏覽器中表示 DOM 的對像類型,不一定繼承自 Object
在所有瀏覽器中。
檢測屬性
假設您想確定給定對像是否具有名稱屬性。在有經驗的開發者中,往往會寫出這樣的代碼:
//doesn't accurately test for existence
if (person.name){
//yay! property exists!
}
乍一看,這似乎沒問題。但是,了解 JavaScript 的工作原理會發現這種方法存在一些問題。首先,只有 person.name
的值才會成功 是真實的,這意味著它是一個對象、一個非空字符串、一個非 NaN
的非零數字 , true
,而不是 null
或 undefined
.這意味著如果 person.name
是空字符串(“”),此檢查將失敗。在這種情況下,失敗並不意味著該屬性不存在。事實上,該屬性確實存在並包含一個值,但該值是假的,因此沒有通過這個測試。
檢測自己的屬性
請記住,這是關於測試存在 屬性而不是可用性或數據類型,有幾個選項。第一個選項是檢測自己的屬性,它通過 Object
上的方法來 名為 hasOwnProperty()
的類型 .由於原生對象繼承自 Object
,這個屬性被這些對象繼承,可以用來檢測自己的屬性是否存在:
alert(person.hasOwnProperty("name")); //true
alert(person2.hasOwnProperty("name")); //false
person2.name = "Adam";
alert(person2.hasOwnProperty("name")); //true
delete person2.name;
alert(person2.hasOwnProperty("name")); //false
最初,person2
有一個原型屬性 name
,所以 hasOwnProperty()
返回假。創建自己的屬性後,調用 hasOwnProperty()
返回真。並且在通過 delete
刪除該屬性之後 ,此方法再次返回false。
JSON 序列化僅適用於自己的屬性,非本機 JSON 序列化實用程序使用 hasOwnProperty()
確保只有在對象字面量上定義的屬性才包含在結果字符串中。
檢測所有屬性
如果只關心對像有屬性,不關心是自己的屬性還是原型屬性,可以使用in
運算符來確定屬性的存在。示例:
if ("name" in person){
//property exists
}
in
當對像上存在命名屬性時,運算符返回 true。在許多情況下,in
運算符就是您所需要的(尤其是在處理 DOM 對象時)。事實上,Mark Pilgrim 的 All-In-One Near-Alphabetical No-Bullshit Guide to Detecting Everything for HTML5 廣泛使用了 in
用於檢測 DOM 對像上的新 HTML5 功能。
結論
如果您只想檢查屬性是否存在,而不一定要檢查它們的值,那麼您有兩個安全的選擇:hasOwnProperty()
和 in
操作員。 hasOwnProperty()
如果您只想檢測自己的屬性,則應使用屬性方法。如果你想測試屬性是否存在而不關心它是一個自己的屬性還是一個對象的屬性,那麼 in
運算符是要使用的。
更新(2010 年 7 月 27 日): 添加了 false
和 NaN
到虛假值列表。**
更新(2010 年 7 月 29 日): 修正了真/假值的描述。
更新(2012 年 12 月 22 日): 修復了指向 Mark Pilgrim 列表的鏈接。