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

相关推荐
骑驴看星星a5 小时前
【回顾React的一些小细节】render里不可包含的东西
前端·javascript·react.js
小白阿龙5 小时前
浮动元素导致父元素高度塌陷
前端
惜.己5 小时前
前端笔记(三)
前端·笔记
妮妮喔妮5 小时前
Nextjs的SSR服务器端渲染为什么优化了首屏加载速度?
开发语言·前端·javascript
专注于找bug的wgwgwg25 小时前
标准答案,无论采用哪种实现方式,本质都是在安全性
前端
LYFlied5 小时前
【每日算法】131. 分割回文串
前端·数据结构·算法·leetcode·面试·职场和发展
二狗哈5 小时前
Cesium快速入门27:GeoJson自定义样式
前端·cesium·着色器
喝牛奶的小蜜蜂5 小时前
微信小程序|云环境共享-使用指南
前端·微信小程序·ai编程
xcLeigh6 小时前
HTML5实现好看的视频播放器(三种风格,附源码)
前端·音视频·html5
TE-茶叶蛋6 小时前
html5-qrcode扫码功能
前端·html·html5