妙啊!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 永远是最新的,不用担心手动维护,你也不用写一个函数专门去更新这个值。

相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万3 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋3 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁3 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95273 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大3 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师3 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学3 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端