Jeff的隨手筆記

學習當一個前端工程師

0%

『菜鳥的踩坑筆記』- JavaScript 相容性處理

前言

在 JavaScript 開發中,我們經常需要檢查一個物件是否擁有某個特定的屬性。最近,我在一個處理表格欄位排序的專案中嘗試使用 Object.hasOwn() 方法,但很快發現這個方法與我的開發環境不相容。這篇文章將分享我的經歷,以及我是如何找到並實施一個既安全又相容的解決方案的。

初次嘗試:Object.hasOwn()

起初,我的程式碼是這樣的:

1
2
3
4
5
6
7
8
var sortDataByOrder = function (dataMap, displayOrder) {
return displayOrder.reduce(function (acc, code) {
if (Object.hasOwn(dataMap, code)) {
acc.push([code, dataMap[code]]);
}
return acc;
}, []);
}

我選擇使用 Object.hasOwn(),因為它是檢查物件是否擁有自有屬性的現代方法,並且經常被推薦作為替代傳統 hasOwnProperty 的選項。

Object.hasOwn() 是 ECMAScript 2022 中引入的新方法,其設計目的是提供一種更簡潔且不易出錯的語法。與傳統的 hasOwnProperty 相比,它具有以下優點:

  1. 簡潔性:語法更直觀,降低了程式碼的開發與閱讀成本。
  2. 避免覆蓋風險Object.hasOwn() 直接作用於 Object,不依賴於物件本身的方法,因此即使物件覆蓋了 hasOwnProperty,它依然可以正常運作。

然而,當我運行程式碼時,卻出現了這樣的錯誤:

1
TypeError: Object.hasOwn is not a function

經過調查,我發現這是因為我的專案環境僅支援較早版本的 JavaScript,甚至早到不支援 ES6。這正是導致 Object.hasOwn() 無法運行的原因。

尋找替代方案

既然 Object.hasOwn() 在我們的開發環境中無法使用,我們就需要尋找其他方法來檢查物件的屬性。

在 JavaScript 中,有幾種方法可以用來檢查物件是否擁有某個自有屬性,其中以下兩種是常用的做法:

方法一:直接使用 hasOwnProperty

1
2
// 直接在物件上呼叫 hasOwnProperty 方法
columnsMap.hasOwnProperty(columnKey)

這是最直覺的寫法,就像是這樣使用:

1
2
3
4
5
6
7
const tableData = {
name: '產品名稱',
price: '單價'
};

// 檢查是否有 name 這個屬性
console.log(tableData.hasOwnProperty('name')); // 輸出: true

方法二:使用 Object.prototype 的 call 方法

1
2
// 透過 Object.prototype 來呼叫 hasOwnProperty
Object.prototype.hasOwnProperty.call(columnsMap, columnKey)

這種寫法看起來比較複雜,但是更安全。舉例來說:

1
2
3
4
5
6
7
8
const tableData = Object.create(null); // 創建一個沒有原型的物件
tableData.name = '產品名稱';

// 第一種方法會出錯
console.log(tableData.hasOwnProperty('name')); // 錯誤!

// 第二種方法安全可靠
console.log(Object.prototype.hasOwnProperty.call(tableData, 'name')); // 正確:true

在 JavaScript 中,有時候我們會遇到:

  1. 使用 Object.create(null) 創建的物件:這種物件沒有繼承任何方法,包括 hasOwnProperty
  2. 有人可能不小心覆蓋了原本的 hasOwnProperty 方法:
1
2
3
4
5
6
7
8
9
10
const tableData = {
name: '產品名稱',
hasOwnProperty: '糟糕,這個方法被覆蓋了'
};

// 第一種方法會出錯
console.log(tableData.hasOwnProperty('name')); // 錯誤!

// 第二種方法依然安全
console.log(Object.prototype.hasOwnProperty.call(tableData, 'name')); // 正確:true

最終解決方案

考慮到安全性和相容性,我最終選擇了這個解決方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 原始資料
const tableData = {
'name': '產品名稱',
'price': '單價',
'stock': '庫存',
'category': '分類',
'updateTime': '更新時間'
};

// 顯示順序設定
const displayOrder = ['category', 'name', 'price', 'stock', 'updateTime'];

var sortTableColumns = function (columnsMap, displayOrder) {
return displayOrder.reduce(function (acc, columnKey) {
if (Object.prototype.hasOwnProperty.call(columnsMap, columnKey)) {
acc.push({
key: columnKey,
label: columnsMap[columnKey]
});
}
return acc;
}, []);
}

// 使用範例
const sortedColumns = sortTableColumns(tableData, displayOrder);

這個方法有幾個優點:

  1. 高度相容:可以在幾乎所有的 JavaScript 環境中運行
  2. 安全:即使 columnsMap 覆蓋了 hasOwnProperty 方法,也能正常工作
  3. 可靠:不依賴於物件是否繼承自 Object.prototype

何時使用較簡單的方法

儘管我選擇了更安全的方法,但在某些情況下,使用 columnsMap.hasOwnProperty(columnKey) 也是可以接受的:

  1. 在完全可控的封閉開發環境中
  2. 單人或小團隊專案,團隊成員都了解潛在風險
  3. 效能關鍵的應用,需要處理大量數據

從這次專案學習到

這次經歷讓我學到了幾個重要的觀念:

  1. 始終考慮目標環境的相容性
  2. 不要忽視安全性,即使是在看似簡單的操作中
  3. 閱讀文件和社群最佳實踐可以幫助避免潛在問題

結論

雖然新的 JavaScript 特性如 Object.hasOwn() 提供了更簡潔和直觀的語法,但在處理需要廣泛相容性的專案時,使用經過時間考驗的方法如 Object.prototype.hasOwnProperty.call() 可能是更好的選擇。這不僅確保了程式碼在各種環境中的正常運行,還提供了額外的安全性保障。

作為開發者,我們需要在新特性的便利性和程式碼的相容性之間找到平衡。希望這篇文章能幫助其他開發者在遇到類似情況時,做出更好的選擇。