访问器属性:getter 与 setter 的定义

访问器属性:getter 与 setter 的定义

开场白

大家好,欢迎来到今天的编程讲座!今天我们要聊一聊 JavaScript 中的访问器属性(Accessor Properties),特别是 gettersetter。如果你觉得这些概念听起来有点高深莫测,别担心,我会用轻松诙谐的语言和大量的代码示例来帮助你理解。准备好了吗?让我们开始吧!

什么是访问器属性?

在 JavaScript 中,对象的属性可以分为两种类型:数据属性(Data Properties)和访问器属性(Accessor Properties)。数据属性就是我们平时最常见的属性,比如:

const person = {
  name: 'Alice',
  age: 30
};

这里,nameage 都是数据属性。你可以直接读取或修改它们的值。

而访问器属性则不同,它们不直接存储值,而是通过函数来控制属性的读取和写入操作。具体来说,访问器属性由两个可选的函数组成:gettersetter

  • Getter:当你尝试读取属性时,JavaScript 会调用这个函数,并返回它的返回值。
  • Setter:当你尝试设置属性时,JavaScript 会调用这个函数,并将你要设置的值传递给它。

听起来是不是有点像“拦截器”?没错,gettersetter 就像是你和对象之间的“中间人”,它们可以在你读取或修改属性时执行一些额外的操作。

为什么需要 getter 和 setter?

你可能会问:“我直接读取和修改属性不香吗?为什么要多此一举呢?”其实,gettersetter 在很多场景下都非常有用:

  1. 封装逻辑:你可以将复杂的计算或验证逻辑放在 gettersetter 中,而不暴露底层实现细节。
  2. 懒加载:有时候你不想在初始化对象时就计算某个属性的值,而是等到真正需要时才计算。getter 可以帮助你实现这一点。
  3. 监听变化:通过 setter,你可以在每次修改属性时触发某些操作,比如更新界面或记录日志。
  4. 兼容性:有时候你需要保持 API 的向后兼容性,但又想改变内部实现。gettersetter 可以让你在不改变外部接口的情况下,修改内部逻辑。

如何定义 getter 和 setter?

定义 gettersetter 有几种方法,最常见的是使用对象字面量中的 getset 关键字。我们来看一个简单的例子:

const person = {
  firstName: 'Alice',
  lastName: 'Smith',

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },

  set fullName(name) {
    [this.firstName, this.lastName] = name.split(' ');
  }
};

console.log(person.fullName); // 输出: Alice Smith
person.fullName = 'Bob Johnson';
console.log(person.firstName); // 输出: Bob
console.log(person.lastName);  // 输出: Johnson

在这个例子中,fullName 是一个访问器属性。当你读取 person.fullName 时,JavaScript 会调用 get fullName() 函数并返回拼接后的全名。而当你设置 person.fullName 时,JavaScript 会调用 set fullName(name) 函数,并将新值拆分为 firstNamelastName

使用 Object.defineProperty

除了对象字面量,你还可以使用 Object.defineProperty 来定义访问器属性。这种方法更加灵活,尤其是在你需要动态添加或修改属性时非常有用。

const person = {};

Object.defineProperty(person, 'age', {
  get() {
    console.log('Getting age...');
    return this._age;
  },
  set(value) {
    if (typeof value !== 'number' || value < 0) {
      throw new Error('Age must be a non-negative number');
    }
    console.log('Setting age...');
    this._age = value;
  },
  configurable: true,
  enumerable: true
});

person.age = 25; // 输出: Setting age...
console.log(person.age); // 输出: Getting age... 25

在这里,我们使用 Object.defineProperty 定义了一个 age 属性。getset 函数分别在读取和设置 age 时被调用。此外,我们还设置了 configurableenumerable 属性,这决定了该属性是否可以被删除或枚举。

使用类中的 getter 和 setter

在 ES6 引入了类(Class)之后,你也可以在类中定义 gettersetter。这使得代码更加结构化和面向对象。

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  set fullName(name) {
    [this.firstName, this.lastName] = name.split(' ');
  }
}

const alice = new Person('Alice', 'Smith');
console.log(alice.fullName); // 输出: Alice Smith
alice.fullName = 'Bob Johnson';
console.log(alice.firstName); // 输出: Bob
console.log(alice.lastName);  // 输出: Johnson

在这个例子中,fullName 是一个类属性,它在实例化 Person 对象时自动可用。你可以像之前一样使用 getset 来控制对 fullName 的访问。

getter 和 setter 的高级用法

懒加载

有时候你不想在对象初始化时就计算某个属性的值,而是等到真正需要时才计算。getter 可以帮助你实现懒加载。我们来看一个例子:

class User {
  constructor(id) {
    this.id = id;
    this._data = null;
  }

  get data() {
    if (this._data === null) {
      console.log('Loading data...');
      this._data = this.fetchData(); // 模拟异步数据加载
    }
    return this._data;
  }

  fetchData() {
    // 模拟从服务器获取数据
    return { name: 'Alice', age: 30 };
  }
}

const user = new User(1);
console.log(user.data); // 输出: Loading data... { name: 'Alice', age: 30 }
console.log(user.data); // 输出: { name: 'Alice', age: 30 } (不再重新加载)

在这个例子中,data 属性只有在第一次访问时才会触发 fetchData(),之后它会缓存结果,避免重复加载。

验证输入

setter 可以用于验证用户输入,确保属性的值符合预期。我们来看一个简单的例子:

class Temperature {
  constructor(celsius) {
    this.celsius = celsius;
  }

  get fahrenheit() {
    return this.celsius * 9 / 5 + 32;
  }

  set fahrenheit(value) {
    if (typeof value !== 'number') {
      throw new Error('Temperature must be a number');
    }
    this.celsius = (value - 32) * 5 / 9;
  }
}

const temp = new Temperature(25);
console.log(temp.fahrenheit); // 输出: 77
temp.fahrenheit = 86;
console.log(temp.celsius);    // 输出: 30

在这个例子中,fahrenheit 是一个访问器属性,它允许你在摄氏度和华氏度之间进行转换。setter 确保传入的值是有效的数字,并将其转换为摄氏度。

总结

今天我们学习了 JavaScript 中的访问器属性 gettersetter,它们为我们提供了更强大的属性控制能力。通过 gettersetter,我们可以封装复杂的逻辑、实现懒加载、验证输入,甚至在不改变外部接口的情况下修改内部实现。

希望这篇文章能帮助你更好地理解和使用 gettersetter。如果你有任何问题或想法,欢迎在评论区留言!下次见! 😄


参考资料

  • MDN Web Docs: Accessors in JavaScript
  • ECMAScript Language Specification: Property Accessors

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注