妙啊!Js的对象属性居然还能用这么写

Hi,我是石小石~


静态属性获取的缺陷

前段时间在做项目国际化时,遇到一个比较隐蔽的问题:

我们在定义枚举常量时,直接调用了 i18n 的翻译方法:

js 复制代码
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 定义为函数:

js 复制代码
export const OverdueStatus = () => ({
  ABOUT_TO_OVERDUE: {
    value: 'ABOUT_TO_OVERDUE',
    name: i18n.global.t('common.about_to_overdue'),
    color: '#ad0000',
    bgColor: '#ffe1e1'
  },
})

这样在调用时:

js 复制代码
OverdueStatus().ABOUT_TO_OVERDUE.name

就能确保翻译逻辑在 i18n 实例创建完成之后再执行,从而避免初始化顺序的问题。不过,这种方式也有明显的缺点:所有类似的枚举都要改成函数,调用时也得多加一层执行,整体代码会变得不够简洁。

如何优雅地实现"动态获取属性"?

上面提到的"把枚举改成函数返回"虽然能解决问题,但在实际业务中显得有些笨拙。有没有更优雅的方式,让属性本身就支持 动态计算 呢?

其实,JavaScript 本身就为我们提供了解决方案 ------ getter

举个例子,我们可以把枚举对象改写成这样:

js 复制代码
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 其实就是对象属性的一种特殊定义方式。

当我们写:

js 复制代码
const obj = {
  get foo() {
    return "bar"
  }
}

等价于用 Object.defineProperty

js 复制代码
const obj = {}
Object.defineProperty(obj, "foo", {
  get: function() {
    return "bar"
  }
})

所以访问 obj.foo 时,其实是触发了这个 get 函数,而不是读取一个固定的值。

类比Vue的computed

在 Vue 里,我们经常写 computed 计算属性,其实就是 getter 的思想。

js 复制代码
import { computed, ref } from "vue"

const firstName = ref("Tom")
const lastName = ref("Hanks")

const fullName = computed(() => `${firstName.value} ${lastName.value}`)

computed 内部其实就是包装了一个 getter 函数。

注意点

  • getter 不能跟属性值同时存在:
js 复制代码
const obj = {
  get name() { return "石小石" },
  name: "石小石Orz" //  会报错
}
  • getter 是只读的,如果你想支持赋值,需要配合 setter
js 复制代码
const obj = {
  _age: 18,
  get age() { return this._age },
  set age(val) { this._age = val }
}

obj.age = 20
console.log(obj.age) // 20

其他实用场景

延迟计算

有些值计算比较复杂,但只有在真正使用时才去算,可以提升性能

js 复制代码
const user = {
  firstName: "石",
  lastName: "小石",
  get fullName() {
    // 类比一个计算,实现开发中,一个很复杂的计算才使用此方法
    console.log("计算了一次 fullName")
    return `${this.firstName} ${this.lastName}`
  }
}

console.log(user.fullName) // "石小石"

这种写法让 API 看起来更自然,不需要调用函数 user.getFullName(),而是 user.fullName

数据封装与保护

有些属性可能并不是一个固定字段,而是基于内部状态计算出来的:

js 复制代码
const cart = {
  items: [100, 200, 300],
  get total() {
    return this.items.reduce((sum, price) => sum + price, 0)
  }
}

console.log(cart.total) // 600

这样 cart.total 永远是最新的,不用担心手动维护,你也不用写一个函数专门去更新这个值。

相关推荐
leobertlan3 小时前
2025年终总结
前端·后端·程序员
子兮曰4 小时前
OpenClaw架构揭秘:178k stars的个人AI助手如何用Gateway模式统一控制12+通讯频道
前端·javascript·github
百锦再4 小时前
Reactive编程入门:Project Reactor 深度指南
前端·javascript·python·react.js·django·前端框架·reactjs
莲华君4 小时前
React快速上手:从零到项目实战
前端·reactjs教程
百锦再4 小时前
React编程高级主题:测试代码
android·前端·javascript·react.js·前端框架·reactjs
易安说AI5 小时前
Ralph Loop 让Claude无止尽干活的牛马...
前端·后端
失忆爆表症6 小时前
05_UI 组件库集成指南:Shadcn/ui + Tailwind CSS v4
前端·css·ui
小迷糊的学习记录6 小时前
Vuex 与 pinia
前端·javascript·vue.js
发现一只大呆瓜7 小时前
前端性能优化:图片懒加载的三种手写方案
前端·javascript·面试
不爱吃糖的程序媛7 小时前
Flutter 与 OpenHarmony 通信:Flutter Channel 使用指南
前端·javascript·flutter