Getter và setter trong JavaScript

Ngoài thuộc tính và phương thức thông thường, object còn có getter và setter. Getter trong JavaScript và setter trong JavaScript thực chất là các phương thức. Tuy nhiên, cách sử dụng của chúng lại giống như thuộc tính.

Sau đây mình sẽ cùng tìm hiểu về định nghĩa và cách sử dụng của getter, setter trong JavaScript.

Định nghĩa getter và setter

Để định nghĩa getter trong JavaScript, bạn sử dụng từ khóa get. Và để định nghĩa setter trong JavaScript thì bạn dùng từ khóa set, ví dụ:

let obj = {
  // getter
  get propName() {
    // code trả về obj.propName
  },

  // setter
  set propName(value) {
    // code gán giá trị value cho obj.propName
  },
};

Hàm getter obj.propName() được dùng để đọc giá trị của thuộc tính. Và hàm setter obj.propName(value) dùng để gán giá trị cho thuộc tính.

Ví dụ tiếp theo, giả sử đối tượng user có hai thuộc tính là namesurname:

let user = {
  name: "David",
  surname: "Walsh",
};

Và bạn cần thêm thuộc tính fullName với giá trị là "David Walsh".

Nếu bạn không muốn copy-paste lại giá trị namesurname trên thì có thể viết hàm getter như sau:

let user = {
  name: "David",
  surname: "Walsh",
  get fullName() {
    return `${this.name} ${this.surname}`;
  },
};

console.log(user.fullName); // David Walsh

Rõ ràng, fullName được viết như một phương thức, nhưng cách sử dụng user.fullName thì lại giống việc truy cập giá trị của thuộc tính trong object.

Trong ví dụ trên, fullName đang chỉ là hàm getter. Do đó, nếu bạn gán giá trị cho fullName thì sẽ có lỗi:

"use strict";
let user = {
  name: "David",
  surname: "Walsh",
  get fullName() {
    return `${this.name} ${this.surname}`;
  },
};

user.fullName = "Lam Pham"; // Cannot set property fullName of #<Object> which has only a getter

lỗi trên chỉ được in ra khi chạy code ở strict mode.

Để tránh lỗi trên, bạn cần viết thêm hàm setter cho fullName như sau:

"use strict";

let user = {
  name: "David",
  surname: "Walsh",
  get fullName() {
    return `${this.name} ${this.surname}`;
  },
  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  },
};

user.fullName = "Lam Pham";
console.log(user.fullName);

Với cách triển khai như trên, đối tượng user đã có thêm một thuộc tính ảofullName.

Đối tượng mô tả thuộc tính cho getter và setter

Mỗi thuộc tính của object trong JavaScript đều có một đối tượng mô tả thuộc tính, bao gồm giá trị value và các cờ là writable/enumerable/configurable.

Nhưng với getter trong JavaScript và setter trong JavaScript thì khác.

Đối tượng mô tả thuộc tính cho getter và setter không có giá trị value và cờ writable, thay vào đó là hai phương thức getset, cụ thể là:

  • get: hàm không có tham số, được dùng khi đọc giá trị của thuộc tính.
  • set: hàm có một tham số, được dùng để gán giá trị cho thuộc tính.
  • enumerable: giống như thuộc tính thông thường.
  • configurable: giống như thuộc tính thông thường.

Ví dụ tạo getter và setter cho user.fullName với phương thức defineProperty như sau:

let user = {
  name: "David",
  surname: "Walsh",
};

Object.defineProperty(user, "fullName", {
  get() {
    return `${this.name} ${this.surname}`;
  },
  set(value) {
    [this.name, this.surname] = value.split(" ");
  },
});
console.log(user.fullName); // David Walsh

for (let key in user) console.log(key); // name, surname

Chú ý: một thuộc tính trong object có thể là getter/setter hoặc là thuộc tính bình thường, nhưng không được phép tồn tại cả hai.

Nói cách khác, nếu bạn định nghĩa cả hàm get/set và thuộc tính value trong cùng một đối tượng mô tả thuộc tính thì sẽ có lỗi:

Object.defineProperty({}, "prop", {
  get() {
    return 0;
  },
  value: 5,
});

// Lỗi:
// Invalid property descriptor.
// Cannot both specify accessors and a value or writable attribute, #<Object>

Ứng dụng của getter và setter

Getter trong JavaScript và setter trong JavaScript có thể dùng làm "wrapper" cho thuộc tính.

Ví dụ, bạn muốn chặn việc gán giá trị quá ngắn cho tên của user. Bạn có thể viết hàm setter user.name và lưu giá trị thật vào thuộc tính user._name.

let user = {
  get name() {
    return this._name;
  },

  set name(value) {
    if (value.length < 4) {
      console.log("Tên quá ngắn, tối thiểu là 4 chữ cái.");
      return;
    }
    this._name = value;
  },
};

user.name = "Alex";
console.log(user.name); // Alex
user.name = ""; // Tên quá ngắn, tối thiểu là 4 chữ cái.

Trong ví dụ trên, tên của user được lưu thật sự trong thuộc tính user._name, còn việc truy cập vào tên thì thông qua getter và setter user.name.

Thực tế, bạn có thể truy cập trực tiếp vào thuộc tính user._name. Tuy nhiên, cách viết thuộc tính bắt đầu bằng dấu gạch dưới là một quy ước ngầm rằng, thuộc tính user._nameprivate và không nên truy cập trực tiếp từ bên ngoài.

Sử dụng getter và setter giúp tương thích ngược

Lợi ích lớn nhất của việc dùng getter và setter trong JavaScript là cách sử dụng giống như thuộc tính thông thường. Nghĩa là bạn có thể biến thuộc tính thông thường thành getter/setter bất kỳ lúc nào.

Ví dụ bạn có đối tượng user với hai thuộc tính là nameage:

function User(name, age) {
  this.name = name;
  this.age = age;
}

let alex = new User("Alex", 28);
console.log(alex.age); // 28

Nhưng sau đó, bạn quyết định thay đổi. Và bạn muốn lưu thông tin birthday thay vì age:

function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;
}

let alex = new User("Alex", new Date(1993, 6, 6));

Chuyện gì sẽ xảy ra với những code cũ - đang sử dụng thuộc tính age?

Bạn có thể tìm tất cả những chỗ có sử dụng user.age để sửa lại cho đúng. Tuy nhiên, nếu code của bạn được sử dụng bởi nhiều người và ở nhiều nơi khác nhau thì cách làm trên là bất khả thi.

Cách đơn giản để giải quyết vấn đề trên là biến thuộc tính age thành getter/setter như sau:

function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;

  // tính toán age dựa trên birthday và năm hiện tại  Object.defineProperty(this, "age", {    get() {      let todayYear = new Date().getFullYear();      return todayYear - this.birthday.getFullYear();    },  });}
  let alex = new User("Alex", new Date(1993, 6, 6));
  console.log(alex.birthday); // Tue Jul 06 1993 00:00:00 GMT+0700 (Indochina Time)
  console.log(alex.age); // 29
}

Với cách làm trên, code cũ đảm bảo là vẫn chạy như bình thường.

Tham khảo: Property getters and setters.

Bài trước
left Bài trước
left Thuộc tính writable, enumerable và configurable của object trong Javascript
Bài tiếp theo
Prototype trong JavaScript right
Bài tiếp theo right
Avatar Techmely Team
VIẾT BỞI

Techmely Team