MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript类的getter和setter方法

2021-04-214.2k 阅读

JavaScript类的getter和setter方法

理解JavaScript中的类

在深入探讨JavaScript类的gettersetter方法之前,我们先来回顾一下JavaScript中的类。从ES6开始,JavaScript引入了类的概念,它为创建对象提供了一种更加结构化和面向对象的方式。类本质上是一个函数,它可以作为创建对象的模板。

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    greet() {
        return `Hello, my name is ${this.name} and I'm ${this.age} years old.`;
    }
}

let person1 = new Person('Alice', 30);
console.log(person1.greet()); 

在上述代码中,我们定义了一个Person类,它有一个构造函数constructor用于初始化对象的属性nameage,还有一个实例方法greet用于返回问候语。通过new关键字,我们创建了person1这个Person类的实例,并调用了greet方法。

类的属性访问控制需求

在很多面向对象编程的场景中,我们不仅仅希望能够直接访问对象的属性,还希望在访问和修改属性时执行一些额外的逻辑。比如,我们可能希望在获取某个属性值时进行计算,或者在设置属性值时进行合法性检查。这就是gettersetter方法发挥作用的地方。

什么是getter方法

getter方法是一种特殊的方法,它允许我们定义当访问对象的某个属性时执行的代码。它没有参数,并且总是返回一个值。getter方法的语法是在类中定义一个以get关键字开头的方法,方法名就是我们想要通过属性访问来触发的名称。

class Circle {
    constructor(radius) {
        this._radius = radius;
    }
    get area() {
        return Math.PI * this._radius * this._radius;
    }
}

let circle1 = new Circle(5);
console.log(circle1.area); 

在上面的Circle类中,我们定义了一个getter方法area。注意,我们在构造函数中使用了一个下划线前缀_radius来表示这是一个内部使用的属性,通常这种命名约定表示该属性不应该被直接访问。通过定义area这个getter方法,当我们访问circle1.area时,实际上执行的是get area()方法中的代码,它会返回圆的面积。

什么是setter方法

setter方法与getter方法相对应,它允许我们定义当设置对象的某个属性值时执行的代码。setter方法有一个参数,这个参数就是要设置的新值。setter方法的语法是在类中定义一个以set关键字开头的方法,方法名同样是我们想要通过属性设置来触发的名称。

class Temperature {
    constructor(celsius) {
        this._celsius = celsius;
    }
    get fahrenheit() {
        return (this._celsius * 1.8) + 32;
    }
    set fahrenheit(value) {
        this._celsius = (value - 32) / 1.8;
    }
}

let temp1 = new Temperature(25);
console.log(temp1.fahrenheit); 
temp1.fahrenheit = 77;
console.log(temp1._celsius); 

在上述Temperature类中,我们定义了一个getter方法fahrenheit用于将摄氏温度转换为华氏温度,同时定义了一个setter方法fahrenheit。当我们设置temp1.fahrenheit的值时,会执行set fahrenheit(value)方法中的代码,它会根据设置的华氏温度值重新计算并设置摄氏温度。

深入理解getter和setter的本质

从本质上来说,gettersetter方法是JavaScript实现属性访问控制和计算属性的一种机制。它们使得我们可以在属性访问和设置的过程中插入自定义的逻辑,而不是简单地进行直接读写操作。

在JavaScript引擎的底层实现中,当我们定义了gettersetter方法后,对象的属性描述符会发生变化。属性描述符是JavaScript对象中用于描述属性特征的对象。通过Object.getOwnPropertyDescriptor方法,我们可以查看属性的描述符。

class Example {
    constructor() {
        this._value = 0;
    }
    get value() {
        return this._value;
    }
    set value(newValue) {
        if (typeof newValue === 'number' && newValue >= 0) {
            this._value = newValue;
        }
    }
}

let example1 = new Example();
let descriptor = Object.getOwnPropertyDescriptor(example1, 'value');
console.log(descriptor); 

在上述代码中,我们创建了一个Example类,它有一个gettersetter方法的value属性。通过Object.getOwnPropertyDescriptor获取value属性的描述符,我们可以看到描述符中有getset属性,分别指向我们定义的gettersetter方法。这表明gettersetter方法实际上是通过修改属性描述符来实现其功能的。

为什么使用getter和setter方法

  1. 数据验证:通过setter方法,我们可以在设置属性值时进行数据验证。比如在前面的Temperature类中,我们可以确保设置的华氏温度值是一个合理的数字,并且在Example类中,我们可以确保设置的value是一个非负数字。
  2. 计算属性getter方法允许我们定义计算属性。在Circle类中,area属性并不是一个存储在对象中的实际值,而是通过计算得到的。这样可以避免在对象中存储冗余数据,同时保证每次获取area时都是最新的计算结果。
  3. 隐藏内部实现细节:使用gettersetter方法可以隐藏对象的内部实现细节。例如,我们使用下划线前缀表示内部属性(如_radius_celsius_value),通过gettersetter方法来控制对这些属性的访问,外部代码不需要知道内部是如何存储和管理数据的。

注意事项

  1. 属性名冲突:在定义gettersetter方法时,要注意避免属性名冲突。如果定义了一个gettersetter方法,就不能再直接定义同名的普通属性,否则会导致语法错误。
  2. 链式调用:当使用setter方法时,要注意返回值的问题。如果希望实现链式调用(例如obj.prop1 = value1.prop2 = value2),setter方法通常应该返回this对象。
class Chainable {
    constructor() {
        this.value = 0;
    }
    set newValue(val) {
        this.value = val;
        return this;
    }
}

let chain1 = new Chainable();
chain1.newValue = 10.newValue = 20;
console.log(chain1.value); 
  1. 性能影响:虽然gettersetter方法提供了很大的灵活性,但在性能敏感的场景中,频繁使用gettersetter方法可能会带来一定的性能开销,因为每次访问或设置属性都需要执行额外的函数调用。

与传统对象属性访问的对比

在ES6类出现之前,JavaScript通过对象字面量和构造函数来创建对象,对于属性的访问和设置是直接进行的。

// 传统方式
let person = {
    name: 'Bob',
    age: 25
};
console.log(person.name); 
person.age = 26;
console.log(person.age); 

这种方式简单直接,但缺乏对属性访问和设置的控制。而通过gettersetter方法,我们可以在属性访问和设置时添加自定义逻辑,使得代码更加健壮和灵活。

在实际项目中的应用场景

  1. 数据模型层:在前端开发中,当处理数据模型时,gettersetter方法非常有用。例如,在一个用户信息管理系统中,我们可能有一个User类,其中的密码属性可能需要在设置时进行加密处理,在获取时可能需要进行权限检查。
class User {
    constructor(username, password) {
        this.username = username;
        this._password = password;
    }
    get password() {
        // 这里可以添加权限检查逻辑
        return this._password;
    }
    set password(newPassword) {
        // 这里可以添加密码加密逻辑
        this._password = newPassword;
    }
}

let user1 = new User('admin', '123456');
user1.password = 'newpassword';
console.log(user1.password); 
  1. 视图层与数据绑定:在一些前端框架(如Vue.js、Angular)中,虽然框架本身提供了数据绑定和响应式系统,但理解gettersetter的原理有助于我们更好地理解框架的工作机制。例如,在Vue.js中,数据的响应式原理部分依赖于Object.defineProperty方法,而gettersetter方法是其实现的关键部分。

总结

JavaScript类的gettersetter方法为我们提供了一种强大的属性访问控制机制。通过getter方法,我们可以实现计算属性,通过setter方法,我们可以进行数据验证和隐藏内部实现细节。在实际项目中,合理使用gettersetter方法可以提高代码的可维护性、健壮性和灵活性。同时,了解其底层实现原理,即通过属性描述符来实现,有助于我们更好地掌握和运用这一特性。在使用过程中,要注意避免属性名冲突、合理处理链式调用以及考虑性能影响等问题。无论是在数据模型层还是在视图层与数据绑定的场景中,gettersetter方法都有着广泛的应用。掌握它们是成为一名优秀的JavaScript开发者的重要一步。