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

相关推荐
又写一个小bug16 分钟前
如何让你的Vue项目支持局域网访问 - 完整指南
前端
walking95720 分钟前
前端开发中常用的JavaScript方法
前端·面试
大舔牛23 分钟前
图片优化全景策略
前端·面试
卸任34 分钟前
阿里云域名迁移到Cloudflare DNS管理
前端·dns
谢小飞44 分钟前
Echarts高级柱状图开发:渐变与3D效果实现
前端·echarts
FogLetter1 小时前
Vite vs Webpack:前端构建工具的双雄对决
前端·面试·vite
tianchang1 小时前
JS 排序神器 sort 的正确打开方式
前端·javascript·算法
怪可爱的地球人1 小时前
ts的类型兼容性
前端
方圆fy1 小时前
探秘Object.prototype.toString(): 揭开 JavaScript 深层数据类型的神秘面纱
前端