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

相关推荐
漂流瓶jz2 小时前
Webpack中各种devtool配置的含义与SourceMap生成逻辑
前端·javascript·webpack
前端架构师-老李2 小时前
React 中 useCallback 的基本使用和原理解析
前端·react.js·前端框架
木易 士心2 小时前
CSS 中 `data-status` 的使用详解
前端·css
明月与玄武2 小时前
前端缓存战争:回车与刷新按钮的终极对决!
前端·缓存·回车 vs 点击刷新
牧马少女3 小时前
css 画一个圆角渐变色边框
前端·css
zy happy3 小时前
RuoyiApp 在vuex,state存储nickname vue2
前端·javascript·小程序·uni-app·vue·ruoyi
小雨青年3 小时前
Cursor 项目实战:AI播客策划助手(二)—— 多轮交互打磨播客文案的技术实现与实践
前端·人工智能·状态模式·交互
小光学长3 小时前
基于Vue的儿童手工创意店管理系统as8celp7(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
前端·数据库·vue.js
meichaoWen3 小时前
【Vue】Vue框架的基础知识强化
前端·javascript·vue.js
jingling5554 小时前
Flutter | 基础环境配置和创建flutter项目
前端·flutter