getter = special method that makes a property readable setter = special method that makes a property writeable

validate and modify a value when reading/writing a property

class Rectangle{
 
    constructor(width, height){
        // 使用下划线开头表示“内部属性”,避免和 getter/setter 混淆
        // 这里的“内部属性” 只是命名方式
        this._width = width;
        this._height = height;
    }
 
    // setter:设置宽度
    set width(newWidth){
      if(newWidth > 0){             // 只允许正数
        this._width = newWidth;     // ✅ 更新内部属性
      }
      else{
        console.log("Width must be a positive number");
      }
    }
 
    // setter:设置高度
    set height(newHeight){
      if(newHeight > 0){
        this._height = newHeight;   // ✅ 更新内部属性
      }
      else{
        console.log("Height must be a positive number");
      }
    }
 
    // getter:获取宽度(读取时自动调用)
    get width(){
      return `width is ${this._width}`;
    }
 
    // getter:获取高度
    get height(){
      return `height is ${this._height}`;
    }
 
    // getter:计算面积(只读属性)
    get area(){
      // toFixed(1) 保留一位小数
      return `area is ${(this._width * this._height).toFixed(1)}`;
    }
}
 
// ================== 测试 ==================
const rectangle = new Rectangle(3, 4);
 
// 使用 setter 修改属性
rectangle.width = 18; // ✅ setter 会检查数值是否大于 0
rectangle.height = 5;
 
// 使用 getter 获取属性(看起来像属性,其实调用的是函数)
console.log(rectangle.width);   // "width is 18"
console.log(rectangle.height);  // "height is 5"
 
// 调用 area getter,计算面积
console.log(rectangle.area);    // "area is 90.0"

another example:

class Person{
 
  constructor(firstName, lastName, age){
      // ⚠️ 注意:这里写的是 this.firstName = firstName
      // 实际上并不是直接赋值,而是会调用 set firstName()
      this.firstName = firstName;
      this.lastName = lastName;
      this.age = age;
  }
 
  // =================== Setter(设值器) ===================
  // 当你给 person.firstName 赋值时,会自动触发这个函数
  set firstName(newFirstName){
    // 检查传入值必须是 string 并且非空
    if (typeof newFirstName === "string" && newFirstName.length > 0) {
      this._firstName = newFirstName;  // ✅ 使用内部变量 _firstName 存储
    }
    else{
      console.error("first name must be a non-empty string");
    }
  }
  
  set lastName(newLastName){
    if (typeof newLastName === "string" && newLastName.length > 0) {
      this._lastName = newLastName;   // ✅ 存储到 _lastName
    }
    else{
      console.error("last name must be a non-empty string");
    }
  }
  
  set age(newAge){
    if (typeof newAge === "number" && newAge >= 0) {
      this._age = newAge;   // ✅ 存储到 _age
    }
    else{
      console.error("age must be a non-negative number");
    }
  }
  
  // =================== Getter(取值器) ===================
  // 当你访问 person.firstName 时,会执行这个函数
  get firstName(){
    return this._firstName;
  }
  
  get lastName(){
    return this._lastName;
  }
  
  // fullName 没有对应的 setter,只能读取
  // 它是一个 "计算属性",通过 _firstName + _lastName 动态生成
  get fullName(){
    return this._firstName + " " + this._lastName;
  }
  
  get age(){
    return this._age;
  }
}
 
// =================== 测试 ===================
// 这里初始化传错了类型:
// 420 (number) -> firstName ❌ (需要 string)
// 69  (number) -> lastName ❌ (需要 string)
// "pizza" (string) -> age ❌ (需要 number)
// 所以 constructor 会调用 setter,但失败并报错
const person = new Person(420, 69, "pizza");
 
// ✅ 这里重新赋值,触发 setter,成功写入
person.firstName = "spongebob";
person.lastName = "squarepants";
person.age = 30
 
// ✅ 读取属性时,触发 getter,输出内部存储的值
console.log(person.firstName); // spongebob
console.log(person.lastName);  // squarepants
console.log(person.age);       // 30