Có hai loại thuộc tính.

Loại đầu tiên là thuộc tính dữ liệu. Chúng ta đã biết làm thế nào để làm việc với họ. Tất cả các thuộc tính mà chúng ta đã sử dụng cho đến bây giờ là thuộc tính dữ liệu.

Loại thuộc tính thứ hai là một cái gì đó mới. Đó là thuộc tính accessor(try cập). Chúng cơ bản là các hàm hoạt động trên việc nhận và thiết lập một giá trị, nhưng trông giống như các thuộc tính thông thường đối với một code bên ngoài.

1. Getters và setters

Các thuộc tính của Accessor được thể hiện bằng các phương thức của getter getter. Trong một đối tượng theo nghĩa đen, chúng được ký hiệu là getset:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

                     
let obj = {
  get propName() {
    // getter, the code executed on getting obj.propName
  },

  set propName(value) {
    // setter, the code executed on setting obj.propName = value
  }
};

Trình getter hoạt động khi obj.propNameđược đọc, setter – khi nó được gán.

Chẳng hạn, chúng ta có một đối tượng uservới namesurname:

let user = {
  name: "John",
  surname: "Smith"
};

Bây giờ chúng ta muốn thêm một thuộc tínhfullName, đó là tên "John Smith". Tất nhiên, chúng ta không muốn sao chép-dán thông tin hiện có, vì vậy chúng ta có thể triển khai nó như một người truy cập:

let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${this.name} ${this.surname}`;
  }
};

alert(user.fullName); // John Smith

Từ bên ngoài, một thuộc tính truy cập trông giống như một thuộc tính thông thường. Đó là ý tưởng của các thuộc tính accessor. Chúng ta không gọi user.fullName là một hàm, chúng tôi đọc nó bình thường: getter chạy ngầm phía sau của biến đó.

Đến bây giờ, fullNamechỉ có một getter. Nếu chúng ta cố gắng gán user.fullName=, sẽ có một lỗi:

let user = {
  get fullName() {
    return `...`;
  }
};

user.fullName = "Test"; // Error (property has only a getter)

Hãy sửa nó bằng cách thêm một setter cho user.fullName:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${this.name} ${this.surname}`;
  },

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  }
};

// set fullName is executed with the given value.
user.fullName = "Alice Cooper";

alert(user.name); // Alice
alert(user.surname); // Cooper

Kết quả là, chúng ta có một thuộc tính ảo fullName. Nó có thể đọc và ghi được.

2. Mô tả phụ

Các mô tả cho các thuộc tính của trình truy cập khác với các thuộc tính cho các thuộc tính dữ liệu.

Đối với các thuộc tính accessor, không có valuehoặc writable, nhưng thay vào đó là hàmgetvà các set.

Đây là, một mô tả truy cập có thể có:

  • get – một hàm không có đối số, hoạt động khi một thuộc tính được đọc,
  • set – một hàm có một đối số, được gọi khi thuộc tính được đặt,
  • enumerable – giống như đối với các thuộc tính dữ liệu,
  • configurable – tương tự như đối với các thuộc tính dữ liệu.

Chẳng hạn, để tạo một trình truy cập fullNamevới defineProperty, chúng ta có thể chuyển một mô tả với getset:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/


let user = {
  name: "John",
  surname: "Smith"
};

Object.defineProperty(user, 'fullName', {
  get() {
    return `${this.name} ${this.surname}`;
  },

  set(value) {
    [this.name, this.surname] = value.split(" ");
  }
});

alert(user.fullName); // John Smith

for(let key in user) alert(key); // name, surname

Xin lưu ý rằng một thuộc tính có thể là một người truy cập (có phương thức get/set) hoặc thuộc tính dữ liệu (có một value), không phải cả hai.

Nếu chúng ta cố gắng cung cấp cả hai getvaluetrong cùng một mô tả, sẽ có một lỗi:

// Error: Invalid property descriptor.
Object.defineProperty({}, 'prop', {
  get() {
    return 1
  },

  value: 2
});

3. Getters / setters thông minh hơn

Getters / setters có thể được sử dụng như các hàm bao trên các giá trị thuộc tính thực để có thêm quyền kiểm soát các hoạt động với chúng.

Chẳng hạn, nếu chúng ta muốn cấm các tên quá ngắn user, chúng ta có thể có một setter namevà giữ giá trị trong một thuộc tính riêng biệt _name:

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

  set name(value) {
    if (value.length < 4) {
      alert("Name is too short, need at least 4 characters");
      return;
    }
    this._name = value;
  }
};

user.name = "Pete";
alert(user.name); // Pete

user.name = ""; // Name is too short...

Vì vậy, tên được lưu trữ trong thuộc tính _name và việc truy cập được thực hiện thông qua getter và setter.

Về mặt kỹ thuật, code bên ngoài có thể truy cập tên trực tiếp bằng cách sử dụng user._name. Nhưng có một quy ước được biết đến rộng rãi rằng các thuộc tính bắt đầu bằng dấu gạch dưới "_"là nội bộ và nó không được truy cấp từ bên ngoài đối tượng.

4. Sử dụng để tương thích

Một trong những ứng dụng tuyệt vời của người truy cập là họ cho phép kiểm soát thuộc tính dữ liệu thông thường của bất cứ lúc nào bằng cách thay thế nó bằng một getter và setter và điều chỉnh hành vi của nó.

Hãy tưởng tượng chúng ta bắt đầu triển khai các đối tượng người dùng bằng các thuộc tính dữ liệu nameage:

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

let john = new User("John", 25);

alert( john.age ); // 25

Tuy nhiên, sớm hay muộn, mọi thứ có thể thay đổi. Thay vì agechúng ta có thể quyết định lưu trữ birthday, vì nó chính xác và tiện lợi hơn:

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

let john = new User("John", new Date(1992, 6, 1));

Bây giờ phải làm gì với code cũ vẫn sử dụng thuộc agetính?

Chúng ta có thể cố gắng tìm tất cả các địa điểm như vậy và sửa chúng, nhưng điều đó tốn thời gian và khó thực hiện nếu code đó được sử dụng bởi nhiều người khác.

Hãy giữ nó.

Thêm một getter để agegiải quyết vấn đề:


/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

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

  // age is calculated from the current date and birthday
  Object.defineProperty(this, "age", {
    get() {
      let todayYear = new Date().getFullYear();
      return todayYear - this.birthday.getFullYear();
    }
  });
}

let john = new User("John", new Date(1992, 6, 1));

alert( john.birthday ); // birthday is available
alert( john.age );      // ...as well as the age

Bây giờ code cũ cũng hoạt động và chúng ta đã có một thuộc tính bổ sung tốt đẹp.

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!