Hi,我是石小石~
前段時間在做專案國際化時,遇到一個比較隱蔽的問題:
我們在定義列舉常量時,直接調用了 i18n
的翻譯方法:
export const OverdueStatus: any = {
ABOUT_TO_OVERDUE: {
value: 'ABOUT_TO_OVERDUE',
name: i18n.global.t('common.about_to_overdue'),
color: '#ad0000',
bgColor: '#ffe1e1'
},
}
結果發現翻譯始終不生效。排查後才發現原因很簡單 —— OverdueStatus
物件的初始化早於 i18n
實例的生成,因此取得的翻譯結果是空的。
雖然最後我通過封裝自定義 Vue 插件的方式徹底解決了問題,但排查過程中其實還有一個可選思路。
當時我想到的最直接辦法是:讓 name
在被訪問時再去執行 i18n.global.t
,而不是在物件定義時就執行。比如把 OverdueStatus
定義為函式:
export const OverdueStatus = () => ({
ABOUT_TO_OVERDUE: {
value: 'ABOUT_TO_OVERDUE',
name: i18n.global.t('common.about_to_overdue'),
color: '#ad0000',
bgColor: '#ffe1e1'
},
})
這樣在調用時:
OverdueStatus().ABOUT_TO_OVERDUE.name
就能確保翻譯邏輯在 i18n
實例創建完成之後再執行,從而避免初始化順序的問題。不過,這種方式也有明顯的缺點:所有類似的枚舉都要改成函式,調用時也得多加一層執行,整體程式碼會變得不夠簡潔。
上面提到的“把枚舉改成函式返回”雖然能解決問題,但在實際業務中顯得有些笨拙。是否有更優雅的方式,讓屬性本身就支持 動態計算 呢?
其實,JavaScript 本身就為我們提供了解決方案 —— getter。
舉個例子,我們可以把枚舉物件改寫成這樣:
export const OverdueStatus: any = {
ABOUT_TO_OVERDUE: {
value: 'ABOUT_TO_OVERDUE',
get name() {
return i18n.global.t('common.about_to_overdue')
},
color: '#ad0000',
bgColor: '#ffe1e1'
},
}
這樣一來,在訪問 name
屬性時,才會真正執行 i18n.global.t
,確保翻譯邏輯在 i18n
實例創建完成後才生效,完美解決問題。
在 JavaScript 規範裡,get
定義的屬性叫 訪問器屬性,區別於普通的 數據屬性 (Data Property)。簡單來說 getter
其實就是物件屬性的一種特殊定義方式。
當我們寫:
const obj = {
get foo() {
return "bar"
}
}
等價於用 Object.defineProperty
:
const obj = {}
Object.defineProperty(obj, "foo", {
get: function() {
return "bar"
}
})
所以訪問 obj.foo
時,其實是觸發了這個 get
函式,而不是讀取一個固定的值。
在 Vue 裡,我們經常寫 computed 計算屬性,其實就是 getter 的思想。
import { computed, ref } from "vue"
const firstName = ref("Tom")
const lastName = ref("Hanks")
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
computed
內部其實就是包裝了一個 getter 函式。
const obj = {
get name() { return "石小石" },
name: "石小石Orz" // 會報錯
}
setter
:const obj = {
_age: 18,
get age() { return this._age },
set age(val) { this._age = val }
}
obj.age = 20
console.log(obj.age) // 20
有些值計算比較複雜,但只有在真正使用時才去算,可以提升性能。
const user = {
firstName: "石",
lastName: "小石",
get fullName() {
// 類比一個計算,在開發中,一個很複雜的計算才使用此方法
console.log("計算了一次 fullName")
return `${this.firstName} ${this.lastName}`
}
}
console.log(user.fullName) // "石小石"
這種寫法讓 API 看起來更自然,不需要調用函式 user.getFullName()
,而是 user.fullName
。
有些屬性可能並不是一個固定欄位,而是基於內部狀態計算出來的:
const cart = {
items: [100, 200, 300],
get total() {
return this.items.reduce((sum, price) => sum + price, 0)
}
}
console.log(cart.total) // 600
這樣 cart.total
永遠是最新的,不用擔心手動維護,你也不用寫一個函式專門去更新這個值。