- 教程
【免费下载链接】jstips
This is about useful JS tips!
本文對應 jstips 倉庫第 10 期技巧(繁體中文版:
_posts/zh_TW/javascript/2016-01-10-check-if-a-property-is-in-a-object.md,另有英文原版、簡體中文版與西班牙語版)。本篇將從最常見的直覺寫法出發,完整對比 JavaScript 中檢查物件屬性是否存在的兩大原生手段——in運算子與Object.prototype.hasOwnProperty,剖析兩者在「自身屬性」與「原型鏈繼承屬性」上的核心差異,並結合本倉庫其他技巧(如無原型物件、屬性挑選等)說明何時該用哪一種,幫你寫出正確、高效且無隱患的屬性存在性判斷。
一、最常見的寫法:if (myObject.name)及其陷阱
檢查某個屬性是否存在於物件中,許多開發者的第一反應是直接讀取值並判斷真偽:
var myObject = { name: '@tips_js' }; if (myObject.name) { ... }這段寫法「能用」,但它其實隱藏著一個關鍵缺陷:它判斷的是屬性的「真值(truthy)」狀態,而不是屬性的「存在與否」。JavaScript 中所有 falsy 值——0、''(空字串)、false、null、undefined、NaN——都會讓這個條件判斷失敗,即使屬性本身確實存在:
var config = { retries: 0, // 屬性存在,但值為 falsy label: '', // 屬性存在,但值為 falsy enabled: false // 屬性存在,但值為 falsy }; if (config.retries) { /* 不會執行,即使 retries 已定義且等於 0 */ } if (config.label) { /* 不會執行,即使 label 已定義為空字串 */ }換言之,只要屬性的值恰好落在 falsy 集合裡,if (obj.prop)就會誤判為「屬性不存在」。若要精確回答「這個 key 到底存不存在」,JavaScript 提供了兩個原生方法:in運算子與Object.hasOwnProperty。任何繼承自Object的物件,都可以使用這兩種方式——這是本技巧(tip 10)的核心內容,也與倉庫中第 5 期「undefined與null的差異」所討論的 falsy/truthy 語義一脈相承。
二、兩種原生方法:in運算子與hasOwnProperty
2.1in運算子
in是 JavaScript 的內建運算子,語法為propertyName in object。它會沿著物件的整個原型鏈向上查找,只要在自身屬性或任何一層繼承屬性中找到該 key,就回傳true:
var myObject = { name: '@tips_js' }; 'name' in myObject; // true,自身屬性 'valueOf' in myObject; // true,繼承自原型鏈注意in運算子的左側必須是字串形式的屬性名稱(或會被轉換為字串的表達式),且當右側不是物件時會拋出TypeError。
2.2Object.prototype.hasOwnProperty
hasOwnProperty是定義在Object.prototype上的方法,任何普通物件都能直接呼叫。它只檢查自身屬性,不往上追溯原型鏈:
var myObject = { name: '@tips_js' }; myObject.hasOwnProperty('name'); // true myObject.hasOwnProperty('valueOf'); // false,valueOf 繼承自原型鏈2.3 兩者的本質差異
原文用一段對比清晰地呈現了兩者的差別:
var myObject = { name: '@tips_js' }; myObject.hasOwnProperty('name'); // true 'name' in myObject; // true myObject.hasOwnProperty('valueOf'); // false, valueOf 繼承自原型鏈結 'valueOf' in myObject; // true兩者不同的地方在於確認屬性的深度不同。換句話說,如果 key 直接存在於物件本身,hasOwnProperty才會回傳true;而in運算子無法分辨屬性是建立在物件上,還是繼承自原型鏈——只要是沿著原型鏈可達的屬性,in一律回答「存在」。
這個差異在實務上非常關鍵:任何普通物件都繼承了Object.prototype上的方法,例如constructor、toString、valueOf、hasOwnProperty、isPrototypeOf等。因此:
'toString' in {}→true(繼承而來){}.hasOwnProperty('toString')→false(並非自身屬性)
三、原型鏈情境下的對比:構造函數範例
原文給出了第二組更貼近真實開發的範例,透過構造函數與prototype展示繼承屬性的行為:
var myFunc = function() { this.name = '@tips_js'; // 建立「自身屬性」 }; myFunc.prototype.age = '10 days'; // 在原型上建立「繼承屬性」 var user = new myFunc(); user.hasOwnProperty('name'); // true user.hasOwnProperty('age'); // false, 因為 age 繼承自原型鏈結這段程式碼說明了三件事:
- 在構造函數內用
this.name = ...賦值的屬性,會成為每個實例的自身屬性,因此user.hasOwnProperty('name')為true; - 掛在
myFunc.prototype上的age,會被所有實例透過原型鏈「看到」,但它不是實例的自身屬性,所以user.hasOwnProperty('age')為false; - 若改用
in運算子,'age' in user則會回傳true——因為in會沿著user → myFunc.prototype → Object.prototype這條鏈逐一查找。
由此可以歸納出選擇原則:
| 判斷目標 | 推薦方式 | 行為 |
|---|---|---|
| 只關心「自身屬性」是否存在(例如資料校驗、白名單過濾) | Object.prototype.hasOwnProperty | 不追溯原型鏈 |
| 關心「可讀取到該屬性」(含繼承屬性,例如特性偵測) | in運算子 | 沿原型鏈完整查找 |
四、現代進化:Object.hasOwn與無原型物件
4.1Object.hasOwn(ES2022)
hasOwnProperty有一個已知弱點:如果物件本身被賦予了名為hasOwnProperty的屬性(例如var o = { hasOwnProperty: null }),直接呼叫o.hasOwnProperty(...)就會失敗或行為異常。ES2022 提供了更穩健的靜態方法Object.hasOwn(obj, prop),它不依賴物件自身的同名屬性,是取代hasOwnProperty的推薦寫法:
Object.hasOwn({ name: '@tips_js' }, 'name'); // true Object.hasOwn({ name: '@tips_js' }, 'valueOf'); // false4.2Object.create(null):完全繞過原型鏈
當你需要一個「純資料容器」、不想被任何繼承屬性干擾時,可以建立無原型物件。本倉庫第 73 期「無副作用雜湊表(Hash maps without side effects)」正是這個主題的代表:
透過
Object.create(null),我們明確指定null作為其原型。因此它完全沒有任何屬性——連constructor、toString、hasOwnProperty等都沒有——你可以放心地在資料結構中使用這些 key 當作鍵名。
該文同時展示了在遍歷雜湊表時使用hasOwnProperty來排除繼承屬性的典型寫法:
if (dirtyMap.hasOwnProperty(key)) { // 檢查以避開繼承屬性 // 僅處理自身屬性 }這與本期技巧正好互為表裡:當你面對普通物件(帶有Object.prototype繼承鏈)時,必須用hasOwnProperty/Object.hasOwn或in來明確控制檢查深度;而Object.create(null)則從根源上消除繼承屬性的干擾。
五、倉庫中的實際應用佐證
jstips 倉庫內多篇技巧都在真實使用這兩種檢查方式,可作為最佳實踐的參考:
- 第 70 期「挑選與排除物件屬性」:其
pick函式用k in obj判斷 key 是否存在於目標物件,再決定是否取出該屬性:
function pick(obj, keys) { return keys.map(k => k in obj ? {[k]: obj[k]} : {}) .reduce((res, o) => Object.assign(res, o), {}); }這裡使用in而非hasOwnProperty,是因為pick的語義是「取出該物件(含繼承鏈)可讀到的屬性」,與in的深度行為一致。
第 73 期「無副作用雜湊表」:如上所述,在
for...in這類會遍歷繼承屬性的語法中,用hasOwnProperty過濾掉非自身屬性,是經典的安全寫法。第 39 期「進階屬性(Advanced Properties)」:講解
Object.defineProperty定義value、writable、enumerable、configurable等屬性描述元。當屬性被設為enumerable: false時,for...in不會列出它,但in與hasOwnProperty仍會正確回報其存在——這提醒我們:屬性是否「可列舉」與屬性是否「存在」是兩個獨立維度。
六、實務選擇建議與常見錯誤總結
綜合原文與倉庫內相關技巧,給出以下決策指引:
- 判斷「是否存在」而非「是否 truthy」:當屬性值可能為
0、''、false、null、undefined、NaN時,if (obj.prop)必然誤判。此時改用in或hasOwnProperty。 - 只要自身屬性(如表單欄位校驗、資料欄位白名單、
for...in過濾):優先使用Object.hasOwn(obj, prop)(現代環境)或obj.hasOwnProperty(prop)(相容舊環境)。 - 需要沿原型鏈查找(如特性偵測、判斷某方法是否「可被呼叫」):使用
in運算子。 - 避免呼叫被覆寫的
hasOwnProperty:若物件可能帶有同名自訂屬性,改用Object.hasOwn或Object.prototype.hasOwnProperty.call(obj, key)。 - 純資料容器:需要徹底不受繼承屬性干擾時,使用
Object.create(null)建立無原型物件(參見第 73 期)。
常見錯誤的根源在於:把「屬性值為 falsy」誤當成「屬性不存在」,以及把「屬性可沿原型鏈讀取」誤當成「屬性為自身所有」。釐清in與hasOwnProperty的深度差異後,這兩類問題都能迎刃而解——這正是本期 jstips 技巧(tip 10)要傳達的核心價值。
七、延伸閱讀
- 第 5 期「
undefined與null的差異」:falsy 值語義與typeof檢查的完整說明 - 第 73 期「無副作用雜湊表」:
Object.create(null)與hasOwnProperty的實戰組合 - 第 70 期「挑選與排除物件屬性」:
in運算子在屬性挑選工具中的應用 - 第 39 期「進階屬性」:屬性描述元與可列舉性的深入講解
- POST_TEMPLATE.md:jstips 文章格式範本,可了解本期技巧的標準結構
- 教程
【免费下载链接】jstips
This is about useful JS tips!
相关推荐
jstips 第 10 期:检查对象属性是否存在的正确姿势 —— `in` 操作符与 `hasOwnProperty` 深度对比
jstips 第 10 期:检查对象属性是否存在的正确姿势 —— in 操作符与 hasOwnProperty 深度对比 导读 在 JavaScript 日常开
教程jstips 技巧 10 深度解读:用 `in` 与 `hasOwnProperty` 正确检查 JavaScript 对象属性是否存在
jstips 技巧 10 深度解读:用 in 与 hasOwnProperty 正确检查 JavaScript 对象属性是否存在 导读 :在 JavaScrip
教程如何看懂 video-compress-cj 压缩质量调优:高、中、低三档背后的码率缩放原理
如何看懂 video compress cj 压缩质量调优:高、中、低三档背后的码率缩放原理 video compress cj 是一款基于硬件编解码 API(
教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考