
1. 問題初探當Object.hasOwn不再是函數如果你在前端開發中尤其是在處理一些較新的 JavaScript 特性時在控制臺看到了Uncaught TypeError: Object.hasOwn is not a function這個錯誤先別急著懷疑人生。這通常不是你代碼邏輯的錯而是一個典型的環境兼容性問題。簡單來說你當前代碼運行的 JavaScript 引擎或瀏覽器版本還不認識Object.hasOwn這個“新朋友”。Object.hasOwn是 ECMAScript 2022ES13中引入的一個靜態方法。它的作用非常專一判斷一個對象自身不包括原型鏈是否擁有指定的屬性。聽起來是不是很耳熟沒錯它的功能和我們用了很多年的Object.prototype.hasOwnProperty幾乎一樣。那為什么還要引入它呢核心原因有兩個安全性和便利性。obj.hasOwnProperty(prop)有個潛在風險如果obj恰好沒有繼承自Object.prototype比如是通過Object.create(null)創建的對象或者其hasOwnProperty方法被意外覆蓋或重寫那么調用就會失敗或產生非預期結果。Object.hasOwn(obj, prop)作為一個靜態方法完全規避了這個問題因為它不依賴于目標對象的原型鏈。所以當你遇到這個錯誤本質上是在一個不支持 ES2022 特性的舊環境中嘗試使用了一個新 API。接下來我們就從問題定位、解決方案到深層原理徹底拆解這個“不是函數”的報錯。2. 核心原理Object.hasOwn與hasOwnProperty的異同要徹底理解這個問題我們必須先搞清楚新舊兩種方法的區別。這不僅僅是 API 調用形式的變化背后是語言設計對健壯性的追求。2.1 為什么需要Object.hasOwn讓我們看一個經典的“翻車”場景// 場景一對象沒有原型鏈 const obj Object.create(null); obj.name test; console.log(obj.hasOwnProperty(name)); // TypeError: obj.hasOwnProperty is not a function console.log(Object.hasOwn(obj, name)); // true (在支持的環境中)通過Object.create(null)創建的對象是一個純字典它的原型鏈指向null因此它沒有繼承Object.prototype上的任何方法包括hasOwnProperty。這時調用obj.hasOwnProperty直接就會報錯。而Object.hasOwn是靜態方法它接收對象作為第一個參數完全不受對象自身原型的影響。// 場景二hasOwnProperty 被覆蓋 const obj { name: test }; obj.hasOwnProperty () Oops!; // 意外或惡意覆蓋 console.log(obj.hasOwnProperty(name)); // Oops! (結果被篡改) console.log(Object.hasOwn(obj, name)); // true (始終返回正確的布爾值)在實際的大型應用或庫中對象屬性被意外覆蓋的情況并非天方夜譚。Object.hasOwn從設計上就保證了行為的確定性。2.2 兩者的語法與性能淺析從語法上看Object.hasOwn更符合直覺它明確地將操作對象 (obj) 和操作屬性 (prop) 作為兩個參數傳入。// 傳統方式 obj.hasOwnProperty(prop) // 新方式 Object.hasOwn(obj, prop)在 V8 引擎Chrome、Node.js 的核心的優化下兩者的性能在絕大多數場景下是微乎其微的可以將性能因素排除在選型考量之外。選擇的關鍵在于代碼的健壯性和意圖的清晰性。Object.hasOwn明確宣告“我就是要檢查這個對象自己的屬性”避免了hasOwnProperty可能被覆蓋的歧義。3. 診斷與兼容性環境判斷遇到錯誤第一步是定位問題根源。你需要判斷你的代碼運行在什么樣的環境中。3.1 如何檢測當前環境是否支持最直接的方式是進行特性檢測。不要依賴瀏覽器版本號因為同一版本的不同發行版如 Chrome for Mobile 與 Desktop支持特性可能不同。// 安全的特性檢測方法 const isObjectHasOwnSupported typeof Object.hasOwn function; if (isObjectHasOwnSupported) { // 放心使用新語法 console.log(Object.hasOwn({ a: 1 }, a)); } else { // 降級方案 console.log(Object.prototype.hasOwnProperty.call({ a: 1 }, a)); }你也可以將檢測封裝成一個工具函數方便在項目中復用/** * 安全的對象自身屬性檢查 * param {Object} obj - 要檢查的對象 * param {string | symbol} prop - 要檢查的屬性名 * returns {boolean} */ function safeHasOwn(obj, prop) { if (typeof Object.hasOwn function) { return Object.hasOwn(obj, prop); } // 降級到使用 Object.prototype.hasOwnProperty.call return Object.prototype.hasOwnProperty.call(obj, prop); }3.2 主流環境支持情況一覽了解你的目標用戶或部署環境至關重要。以下是截至近期的主流環境支持概覽環境 / 平臺版本要求備注Chrome 932021年8月發布Firefox 922021年9月發布Safari 15.42022年3月發布 (iOS 15.4, macOS Monterey 12.3)Node.js 16.9.0需要啟用--harmony-object-has-own標志從 Node.js 16.14.0 開始默認啟用Edge 93基于 Chromium與 Chrome 同步微信瀏覽器需實測內核版本更新滯后需根據 X5 內核版本判斷注意移動端瀏覽器、嵌入式 WebView如 Cordova、React Native 的 WebView以及一些國產瀏覽器UC、QQ瀏覽器的內核更新往往滯后于桌面版。在這些環境上線前必須進行真機測試或嚴格的特性檢測。4. 解決方案從臨時墊片到構建集成知道了問題所在我們有多種策略來解決它從快速修復到工程化方案可以根據項目階段和復雜度選擇。4.1 方案一直接使用 Polyfill (墊片)這是最快、最直接的修復方法尤其適合在舊環境如需要支持 IE中快速讓新語法工作。你可以在入口文件的最頂部引入一段墊片代碼// polyfill.js if (!Object.hasOwn) { Object.defineProperty(Object, hasOwn, { value: function (object, property) { if (object null) { throw new TypeError(Cannot convert undefined or null to object); } return Object.prototype.hasOwnProperty.call(object, property); }, configurable: true, writable: true, enumerable: false // 保持與標準一致不可枚舉 }); }這段代碼做了幾件事條件判斷僅當Object.hasOwn不存在時才注入。模擬實現使用Object.prototype.hasOwnProperty.call來安全地實現相同功能。屬性描述通過Object.defineProperty定義并設置enumerable: false使其不可被for...in遍歷這符合原生 API 的行為。實操心得直接寫墊片雖然快但在大型項目中容易造成代碼重復和版本管理混亂。如果多個庫都寫了類似的墊片可能會產生沖突。更推薦使用社區維護的、經過充分測試的墊片庫。4.2 方案二使用核心-js 墊片庫對于現代前端工程化項目使用core-js是更專業和全面的選擇。core-js是 JavaScript 標準庫的模塊化 polyfill。安裝與使用npm install core-js # 或 yarn add core-js在你的應用入口文件如src/index.js或src/main.js中直接導入// 導入所有穩定的 ECMAScript 特性 import core-js/stable; // 如果需要也可以單獨導入 Object.hasOwn 的 polyfill // import core-js/features/object/has-own;如果你使用 Babel 進行轉譯通常會配合babel/preset-env和core-js自動按需引入 polyfill。這是目前最主流、最推薦的方案。4.3 方案三配置 Babel 與構建工具這是面向未來的工程化解決方案。通過構建工具的配置讓代碼在打包時自動根據你的目標瀏覽器環境進行語法轉換和墊片注入。以 Webpack Babel 為例安裝必要依賴npm install --save-dev babel/core babel/preset-env babel-loader core-js3配置babel.config.jsmodule.exports { presets: [ [ babel/preset-env, { // 指定你的目標瀏覽器版本babel 會按需 polyfill targets: { chrome: 60, firefox: 55, safari: 12, ie: 11, // 如果需要支持 IE }, // 使用 core-js 3并設置為‘usage’模式按需導入 useBuiltIns: usage, corejs: { version: 3, proposals: false }, // 使用 core-js 3 debug: false, // 開啟后會在控制臺打印哪些 polyfill 被引入了 }, ], ], };配置 Webpack在webpack.config.js中為 JS 文件配置babel-loader。關鍵配置解析useBuiltIns: usage這是精髓。Babel 會掃描你的項目代碼只將你用到的、且目標環境不支持的 API 的 polyfill 引入到最終打包文件中極大減少了打包體積。targets你必須根據你的用戶群體準確配置。你可以使用類似“ 0.5%, last 2 versions, not dead”的 browserslist 查詢語句但更建議根據你的數據分析報告來設定精確版本避免過度轉譯。踩坑記錄我曾在一個項目中因為targets里漏掉了 iOS Safari 的某個舊版本導致使用了Object.hasOwn的頁面在部分用戶手機上白屏。教訓是永遠不要假設移動端瀏覽器和桌面版同步更新。務必使用 Analytics 數據或 Can I Use 的覆蓋率報告來指導targets的配置。5. 錯誤排查與進階場景即使解決了基本的兼容性問題在實際使用中也可能遇到一些邊界情況或衍生錯誤。5.1 常見錯誤場景速查表錯誤現象可能原因解決方案Object.hasOwn is not a function1. 瀏覽器/Node.js 版本過低。2. 構建工具未正確注入 polyfill。1. 進行特性檢測并使用墊片。2. 檢查 Babel/Webpack 配置確保core-js被正確引入。Object.hasOwn called on non-object第一個參數傳入了null或undefined。Object.hasOwn要求第一個參數必須是對象。使用前需做空值判斷。if (obj Object.hasOwn(obj, prop))使用Object.hasOwn檢查Symbol屬性失敗檢查方式錯誤。Object.hasOwn完全支持 Symbol 屬性。確保傳入的是 Symbol 引用而不是字符串Object.hasOwn(obj, mySymbol)TypeScript 中報錯Property hasOwn does not exist on type ObjectConstructorTypeScript 的 lib 庫版本過低。在tsconfig.json中將lib字段設置為包含ES2022或更高版本例如[ES2022, DOM]。5.2 Node.js 環境下的特殊處理Node.js 在 16.9.0 到 16.13.x 版本之間Object.hasOwn處于實驗性階段需要啟用 harmony 標志。如果你的代碼需要在多個 Node 版本間運行需要做兼容處理。// 檢查 Node.js 環境并做兼容 function nodeSafeHasOwn(obj, prop) { // 方法一直接使用靜態方法如果可用 if (typeof Object.hasOwn function) { return Object.hasOwn(obj, prop); } // 方法二使用 Object.prototype.hasOwnProperty.call // 這是最安全、兼容性最好的方式在任何 JavaScript 環境中都有效 return Object.prototype.hasOwnProperty.call(obj, prop); } // 一個更簡潔的通用寫法也是很多流行庫如 Lodash 的 _.has的內部實現 const hasOwnProperty Object.prototype.hasOwnProperty; const safeHasOwn (obj, prop) hasOwnProperty.call(obj, prop);個人建議在編寫通用工具庫或 SDK 時如果不確定最終運行環境直接使用Object.prototype.hasOwnProperty.call是最穩妥的選擇。它的性能沒有差異兼容性達到 100%意圖同樣清晰。Object.hasOwn更適合在可控的現代前端項目如支持 Chrome 93 的 ToC 應用中使用以追求更優雅的代碼風格。5.3 與可選鏈操作符 (?.) 和空值合并操作符 (??) 的配合Object.hasOwn常與 ES2020 引入的可選鏈和空值合并操作符搭配使用能寫出非常簡潔健壯的代碼。const config getUserConfig(); // 可能返回 null、undefined 或一個對象 // 舊寫法冗長且容易出錯 let theme light; if (config config.hasOwnProperty config.hasOwnProperty(theme)) { theme config.theme; } // 新寫法簡潔安全假設環境支持 ES2020 和 ES2022 const theme Object.hasOwn(config, theme) ? config.theme : light; // 或結合空值合并 const theme Object.hasOwn(config, theme) ? config.theme ?? light : light;這種組合能有效避免Cannot read property ... of null的錯誤是現代 JavaScript 防御性編程的利器。6. 工程實踐與升級策略對于正在維護中的大型項目如何安全、平滑地引入Object.hasOwn這類新語法是一個需要謹慎規劃的過程。6.1 漸進式升級路線圖評估與檢測首先使用像eslint-plugin-compat這樣的工具掃描整個代碼庫找出所有使用obj.hasOwnProperty的地方并評估將其轉換為Object.hasOwn的可行性。同時分析你的用戶瀏覽器占比數據。引入 Polyfill在項目入口或公共依賴中引入core-js對Object.hasOwn的 polyfill。確保所有現有功能在舊瀏覽器中不受影響。配置構建工具更新 Babel 和 browserslist 配置將支持Object.hasOwn的瀏覽器版本如 Chrome 93從編譯目標中移除讓 Babel 不再為這些新瀏覽器轉換此語法以減小打包體積。漸進式重構不要一次性全局替換。可以在編寫新組件、新模塊時優先使用Object.hasOwn。對于舊代碼可以在進行功能修改或代碼審查時順便重構。可以建立一條 ESLint 規則如prefer-object-hasown來提示開發者使用新語法。監控與告警在 Sentry 或其他錯誤監控平臺上為TypeError: Object.hasOwn is not a function設置告警。一旦發生說明有 polyfill 未覆蓋到的路徑或環境需要立即排查。6.2 在 TypeScript 項目中的集成在 TypeScript 項目中使用Object.hasOwn除了配置tsconfig.json中的lib還需要注意類型定義。// tsconfig.json { compilerOptions: { target: ES2022, // 或更高 lib: [ES2022, DOM], // 必須包含 ES2022 // ... 其他配置 } }對于需要兼容舊版本 TypeScript 或需要更精確類型推斷的情況你可以考慮自己擴展類型定義但這通常不是必須的因為core-js的類型包types/core-js或types/node對于 Node.js會包含這些定義。一個實用的技巧如果你在重構舊代碼可以使用 TypeScript 的重構工具將obj.hasOwnProperty(key)安全地替換為Object.hasOwn(obj, key)。由于兩者在絕大多數情況下的語義完全相同這種替換風險極低。從obj.hasOwnProperty到Object.hasOwn的遷移是一個微小的語法變化但反映的是前端開發對代碼健壯性和開發者體驗的持續追求。理解其背后的兼容性陷阱和解決方案是每個前端開發者從“會用”到“精通”的必經之路。下次再看到這個錯誤你完全可以自信地定位問題并選擇最適合當前項目的策略來解決它。