一次偶然操作,處理http接口返回的數據時,判斷這個對象是否有某個key,用了hasOwnProperty
const { data } = await getDataFromAjax()
console.log(data.hasOwnProperty('key1'))
結果報錯,data.hasOwnProperty undefined
這個錯誤解決不是重點,用in和Object.hasOwn都可以。
重點是無原型對象,這個平常很少涉及的地方。
無原型對象的表現
const pureObj = Object.create(null);
pureObj.key = "value";
console.log(pureObj.toString); // undefined
console.log(Object.getPrototypeOf(pureObj));// null
用瀏覽器控制枱查看展開,是沒有那一連串眼花繚亂的[[Prototype]]一直到原型鏈根部null的。
所以包括toString,valueOf和hasOwnProperty等繼承自Object的方法屬性全部不可用
以下是瀏覽器JavaScript環境中生成無原型對象的場景及操作注意事項的整理:
⚙️ 一、生成無原型對象的主要方式
-
Object.create(null)- 原理:顯式創建原型鏈指向
null的對象,不繼承Object.prototype的任何方法(如toString、hasOwnProperty)。 -
特點:
Object.getPrototypeOf(obj) === nullobj instanceof Object === false- 適用於純淨鍵值存儲,避免原型污染。
- 原理:顯式創建原型鏈指向
-
顯式修改原型鏈
Object.setPrototypeOf(obj, null):
將現有對象的原型設為null,效果類似Object.create(null),但性能較差(可能觸發引擎優化失效),不推薦高頻使用。
-
特殊場景下的無原型對象
-
JSON解析:
若JSON字符串包含__proto__: null(如'{"__proto__": null, "key": "value"}'),則JSON.parse()生成的對象無原型鏈。開頭出現的問題就是因為除了node外的大部分http服務(java/go)返回的都是純數據json,即JSON.parse返回對象沒有原型鏈繼承
- Proxy代理:
通過getPrototypeOf陷阱返回null,可創建無原型的代理對象。
-
-
對象字面量聲明
- 使用
{ __proto__: null }可直接創建無原型對象(ES6+),但__proto__是非標準屬性,建議優先用Object.create(null)。
- 使用
⚠️ 二、無原型對象的操作注意事項
1. 方法調用限制
- 問題:無法調用
Object.prototype上的方法(如obj.toString()、obj.hasOwnProperty())。 -
解決方案:
-
顯式借用方法:
Object.prototype.hasOwnProperty.call(obj, "key"); // ✅ - 避免直接調用
obj.toString()(會拋出TypeError)。
-
2. 類型轉換問題
-
隱式轉換失敗:
- 字符串拼接(
"" + obj)或算術運算(如obj * 1)會因缺少valueOf/toString而報錯。
- 字符串拼接(
-
解決方案:
-
顯式定義
toString方法:obj.toString = () => "[Custom Object]"; console.log("" + obj); // "[Custom Object]" - 避免依賴默認的類型轉換邏輯。
-
3. 對象比較
-
引用比較:
===或==比較兩個無原型對象時,始終返回false(比較的是內存地址)。
-
內容比較:
-
需使用深度比較函數或
JSON.stringify():const isEqual = JSON.stringify(obj1) === JSON.stringify(obj2); // 注意屬性順序影響結果 - 深度比較函數需遞歸遍歷屬性,並處理無原型對象的特殊方法調用。
-
4. 遍歷與擴展
-
遍歷更安全:
for...in循環不會遍歷原型鏈屬性(因無原型),無需hasOwnProperty過濾。- 推薦使用
Object.keys()、Object.entries()等靜態方法。
-
擴展運算符問題:
{ ...pureObj }會生成繼承Object.prototype的新對象,丟失無原型特性。
5. JSON序列化
- 序列化正常:
JSON.stringify()可正常處理無原型對象的屬性。 -
反序列化陷阱:
JSON.parse(JSON.stringify(obj))默認生成帶原型的對象(除非輸入字符串含__proto__: null)。
6. 第三方庫兼容性
- 潛在問題:某些庫依賴對象的原型方法(如
obj.constructor或obj.toString())。 -
解決方案:
-
手動恢復基礎原型鏈(謹慎使用):
const safeObj = Object.assign(Object.create(Object.prototype), pureObj); - 或確保庫支持無原型對象。
-
📊 三、無原型對象 vs 普通對象
| 特性 | 無原型對象 (Object.create(null)) |
普通對象 ({}) |
|---|---|---|
| 原型鏈 | null |
Object.prototype |
| 繼承方法 | 無 (toString 等) |
有 (toString, hasOwnProperty 等) |
instanceof Object |
false |
true |
| 遍歷屬性 | 僅自身屬性(無需過濾) | 需用 hasOwnProperty 過濾原型屬性 |
| 擴展運算符 | 生成帶原型的對象 | 保留原型 |
可以看到,無原型對象在純數據處理方面的優勢,節省內存,無需考慮原型污染。
不過涉及原型方法使用,比如最常見的js隱式轉換就要注意了。