各位观众,晚上好!我是你们的老朋友,今天咱们聊聊 JavaScript 里的 get
和 set
,也就是 getter 和 setter。这俩哥们儿,可以说是 JavaScript 面向对象编程里的一对黄金搭档,能让你对对象的属性进行更细致的控制。 别怕,听起来高大上,其实就是给属性设置“读”和“写”的关卡。
开场白:属性的“读”与“写”
在JavaScript里,我们经常会直接访问和修改对象的属性,比如:
const person = {
name: '张三',
age: 30
};
console.log(person.name); // 输出: 张三
person.age = 31;
console.log(person.age); // 输出: 31
这看起来很直接,也很方便。但是,有时候我们可能需要对属性的访问和修改进行一些额外的控制,比如:
- 数据校验: 确保赋给属性的值符合特定的规则。
- 计算属性: 属性的值不是直接存储的,而是通过计算得到的。
- 只读属性: 禁止外部修改属性的值。
- 副作用: 在访问或修改属性时执行一些额外的操作。
这时候,get
和 set
就派上用场了。
什么是 Getter 和 Setter?
简单来说:
- Getter (
get
): 是用来读取属性值的“拦截器”。当你尝试访问一个属性时,Getter 函数会被调用,它决定了实际返回什么值。 - Setter (
set
): 是用来设置属性值的“拦截器”。当你尝试给一个属性赋值时,Setter 函数会被调用,它决定了如何处理这个新值。
你可以把它们想象成属性的“门卫”,负责检查进出属性的值。
语法:如何定义 Getter 和 Setter
Getter 和 Setter 有两种定义方式:
-
对象字面量 (Object Literal) 方式: 直接在对象定义时声明。
const person = { firstName: '张', lastName: '三', get fullName() { return this.firstName + ' ' + this.lastName; }, set fullName(name) { const parts = name.split(' '); this.firstName = parts[0]; this.lastName = parts[1]; } }; console.log(person.fullName); // 输出: 张 三 (调用 getter) person.fullName = '李 四'; // 调用 setter console.log(person.firstName); // 输出: 李 console.log(person.lastName); // 输出: 四
在这个例子中,
fullName
属性并没有直接存储值,而是通过get fullName()
方法动态计算得到的。 当你访问person.fullName
时,实际上是调用了get fullName()
函数。 同样,当你设置person.fullName
时,实际上调用了set fullName(name)
函数,它会将新的名字拆分成firstName
和lastName
。 -
Object.defineProperty()
方法: 在对象创建后动态添加或修改属性的特性。const person = { firstName: '王', lastName: '五' }; Object.defineProperty(person, 'fullName', { get: function() { return this.firstName + ' ' + this.lastName; }, set: function(name) { const parts = name.split(' '); this.firstName = parts[0]; this.lastName = parts[1]; } }); console.log(person.fullName); // 输出: 王 五 (调用 getter) person.fullName = '赵 六'; // 调用 setter console.log(person.firstName); // 输出: 赵 console.log(person.lastName); // 输出: 六
Object.defineProperty()
提供了更强大的控制能力,可以设置属性的configurable
(是否可配置)、enumerable
(是否可枚举)等特性。
Getter 的使用场景
Getter 最常见的用途包括:
- 计算属性: 属性的值依赖于其他属性,需要动态计算。
- 数据转换: 在读取属性时对数据进行格式化或转换。
- 隐藏内部状态: 暴露一个只读的属性,但底层实现可能很复杂。
示例 1:计算属性
const rectangle = {
width: 10,
height: 5,
get area() {
return this.width * this.height;
}
};
console.log(rectangle.area); // 输出: 50
rectangle.width = 20;
console.log(rectangle.area); // 输出: 100 (area 会自动更新)
area
属性的值依赖于 width
和 height
,每次访问 rectangle.area
时都会重新计算。
示例 2:数据转换
const temperature = {
celsius: 25,
get fahrenheit() {
return this.celsius * 9 / 5 + 32;
},
set fahrenheit(value){
this.celsius = (value - 32) * 5 / 9;
}
};
console.log(temperature.fahrenheit); // 输出: 77
temperature.fahrenheit = 86;
console.log(temperature.celsius); // 输出: 30
fahrenheit
属性会将摄氏度转换为华氏度。
Setter 的使用场景
Setter 最常见的用途包括:
- 数据验证: 在设置属性值之前进行验证,确保值的有效性。
- 副作用: 在设置属性值时执行一些额外的操作,例如更新其他属性或触发事件。
- 控制属性的修改: 限制属性的修改方式或禁止修改。
示例 1:数据验证
const circle = {
radius: 1,
set radius(value) {
if (value <= 0) {
throw new Error('半径必须大于 0');
}
this._radius = value; // 使用 _radius 存储实际的值
},
get radius(){
return this._radius;
}
};
circle.radius = 5; // 正常赋值
console.log(circle.radius); // 输出 5
try {
circle.radius = -1; // 抛出错误
} catch (error) {
console.error(error.message); // 输出: 半径必须大于 0
}
在这个例子中,Setter 函数会检查 radius
的值是否大于 0,如果不是,则抛出一个错误。 注意,我们使用 _radius
来存储实际的半径值,避免 Setter 函数无限循环调用自身。这是很常见的做法,用一个下划线开头的变量来表示“私有”属性。
示例 2:副作用
const counter = {
_count: 0,
get count() {
return this._count;
},
set count(value) {
this._count = value;
console.log('计数器已更新为:', value); // 副作用:打印日志
}
};
counter.count = 10; // 输出: 计数器已更新为: 10
console.log(counter.count); // 输出: 10
在这个例子中,Setter 函数在设置 count
的值后,会打印一条日志。
示例 3:只读属性
const obj = {
_name: "只读属性",
get name() {
return this._name;
},
};
console.log(obj.name); // 输出 "只读属性"
obj.name = "尝试修改"; // 静默失败,不会报错,但值不会改变
console.log(obj.name); // 仍然输出 "只读属性"
这个例子中,我们只定义了 name
属性的 getter,没有定义 setter。这意味着外部无法修改 name
属性的值。
注意事项
-
避免无限循环: Getter 和 Setter 函数内部访问或修改自身属性时,一定要小心,避免无限循环调用。 通常的做法是使用一个不同的变量来存储实际的值,例如使用下划线前缀
_
。 -
性能影响: 过度使用 Getter 和 Setter 可能会对性能产生一定影响,因为每次访问或修改属性都需要调用函数。 所以,要权衡使用的必要性。
-
配合
Object.defineProperty()
使用:Object.defineProperty()
除了可以定义 Getter 和 Setter,还可以设置属性的其他特性,例如configurable
和enumerable
,可以更精细地控制属性的行为。
Object.defineProperty()
的更多用法
让我们更深入地了解 Object.defineProperty()
。
const myObject = {};
Object.defineProperty(myObject, 'myProperty', {
value: 'Hello', // 属性的初始值
writable: false, // 是否可写 (只读)
enumerable: true, // 是否可枚举 (是否可以通过 for...in 循环访问)
configurable: false // 是否可配置 (是否可以删除或修改属性的特性)
});
console.log(myObject.myProperty); // 输出: Hello
myObject.myProperty = 'World'; // 尝试修改,但无效,因为 writable: false
console.log(myObject.myProperty); // 输出: Hello (值没有改变)
for (let key in myObject) {
console.log(key); // 输出: myProperty (因为 enumerable: true)
}
// 尝试删除属性 (会抛出错误,因为 configurable: false)
// delete myObject.myProperty; // 严格模式下会报错, 非严格模式下静默失败
// 尝试重新定义属性 (会抛出错误,因为 configurable: false)
// Object.defineProperty(myObject, 'myProperty', { value: 'New Value' }); // 严格模式下会报错, 非严格模式下静默失败
特性 | 描述 |
---|---|
value |
属性的值。 |
writable |
布尔值,指示属性是否可写。如果为 false ,则该属性是只读的。 |
enumerable |
布尔值,指示属性是否可枚举。如果为 true ,则该属性可以通过 for...in 循环、Object.keys() 等方法访问。 |
configurable |
布尔值,指示属性是否可配置。如果为 false ,则无法删除该属性,也无法修改其特性(例如 writable 、enumerable 和 configurable 本身)。 注意:一旦设置为 false ,就无法再改回 true 。 |
get |
一个函数,作为属性的 getter。当属性被访问时,该函数会被调用。 |
set |
一个函数,作为属性的 setter。当属性被赋值时,该函数会被调用。 |
更高级的用法:使用 Symbol 作为属性名
有时候,我们希望定义一些“私有”属性,不希望被外部访问。虽然 JavaScript 没有真正的私有属性,但我们可以使用 Symbol 来模拟:
const _age = Symbol('age'); // 创建一个 Symbol
const person = {
name: '李明',
[_age]: 25, // 使用 Symbol 作为属性名
get age() {
return this[_age];
},
set age(value) {
if (typeof value !== 'number' || value < 0) {
throw new Error('年龄必须是大于等于0的数字');
}
this[_age] = value;
}
};
console.log(person.name); // 输出: 李明
console.log(person.age); // 输出: 25 (通过 getter 访问)
// 无法直接访问 _age 属性 (除非你知道 Symbol 的值)
// console.log(person._age); // undefined
// 尝试使用 Object.keys() 也无法访问到 Symbol 属性
console.log(Object.keys(person)); // 输出: [ 'name', 'age' ]
person.age = 30;
console.log(person.age); // 输出: 30
// 尝试通过 Object.getOwnPropertySymbols() 可以访问到 Symbol 属性
console.log(Object.getOwnPropertySymbols(person)); // 输出: [ Symbol(age) ]
// 但是仍然无法直接修改
person[Object.getOwnPropertySymbols(person)[0]] = 100; // 非常不推荐
console.log(person.age); // 仍然输出: 30
Symbol 属性不会被 for...in
循环或 Object.keys()
访问到,只能通过 Object.getOwnPropertySymbols()
获取。 虽然这并不能完全阻止外部访问,但可以有效地隐藏属性,提高代码的可维护性。
总结
Getter 和 Setter 是 JavaScript 中非常有用的特性,可以让你对对象的属性进行更细致的控制。 它们可以用于数据验证、计算属性、副作用处理等多种场景。 配合 Object.defineProperty()
和 Symbol,你可以构建更健壮、更易于维护的代码。
记住,没有银弹。 Getter 和 Setter 虽然强大,但也需要适度使用,避免过度设计和性能问题。 合理地运用它们,可以让你写出更优雅、更安全的代码。
今天就到这里,希望大家有所收获! 咱们下次再见!