Như chúng ta biết, các đối tượng có thể lưu trữ các thuộc tính.

Cho đến bây giờ, một thuộc tính là một cặp khóa-giá trị đơn giản. Nhưng một thuộc tính đối tượng thực sự là một thứ linh hoạt và mạnh mẽ hơn.

Trong chương này, chúng ta sẽ nghiên cứu các tùy chọn cấu hình bổ sung và trong phần tiếp theo chúng ta sẽ xem làm thế nào để vô hình biến chúng thành các hàm getter / setter.

1. Các cờ(flags) của một thuộc tính cờ

Các thuộc tính đối tượng, ngoài một value, có ba thuộc tính đặc biệt (còn gọi là cờ – Flag):

  • writable– nếu true, giá trị có thể được thay đổi, nếu không thì chỉ đọc.
  • enumerable– nếu true, sau đó được liệt kê trong các vòng lặp, nếu không thì không được liệt kê.
  • configurable– nếu true, thuộc tính có thể bị xóa và các thuộc tính này có thể được sửa đổi, nếu không thì không.

Chúng ta chưa nhìn thấy chúng, vì nhìn chung chúng không xuất hiện. Khi chúng ta tạo một thuộc tính theo cách thông thường, thì tất cả đều true. Nhưng chúng ta cũng có thể thay đổi chúng bất cứ lúc nào.

Trước tiên, hãy xem làm thế nào để có được những flag.

Phương thức Object.getOwnPropertyDescriptor cho phép truy vấn thông tin đầy đủ về một thuộc tính.

Cú pháp là:

let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);

obj

Các đối tượng cần có được thông tin từ.

propertyName

Tên của thuộc tính.

Giá trị được trả về là một đối tượng được gọi là đối tượng mô tả thuộc tính: có chứa giá trị và tất cả các cờ.

Ví dụ:

/*
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"
};

let descriptor = Object.getOwnPropertyDescriptor(user, 'name');

alert( JSON.stringify(descriptor, null, 2 ) );
/* property descriptor:
{
  "value": "John",
  "writable": true,
  "enumerable": true,
  "configurable": true
}
*/

Để thay đổi các cờ, chúng ta có thể sử dụng Object.defineProperty.

Cú pháp là:

Object.defineProperty(obj, propertyName, descriptor)

obj, propertyName

Các đối tượng và thuộc tính của nó để áp dụng mô tả.

descriptor

Đối tượng mô tả thuộc tính để dùng.

Nếu thuộc tính tồn tại, definePropertycập nhật cờ của nó. Mặt khác, nó tạo ra thuộc tính với giá trị và cờ đã cho; trong trường hợp đó, nếu một cờ không được cung cấp, nó được giả sử false.

Chẳng hạn, ở đây một thuộc tính nameđược tạo với tất cả các cờ(flags):

let user = {};

Object.defineProperty(user, "name", {
  value: "John"
});

let descriptor = Object.getOwnPropertyDescriptor(user, 'name');

alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
  "value": "John",
  "writable": false,
  "enumerable": false,
  "configurable": false
}
 */

So sánh nó với các trò chơi thông thường được tạo ra user.nameở trên: bây giờ tất cả các cờ đều sai. Nếu đó không phải là những gì chúng ta muốn thì tốt hơn chúng ta nên đặt chúng truevào descriptor.

Bây giờ hãy xem hiệu ứng của các flag bằng ví dụ.

2. Non-writable

Hãy làm cho user.namekhông thể ghi (không thể được chỉ định lại) bằng cách thay đổi writablecờ:

let user = {
  name: "John"
};

Object.defineProperty(user, "name", {
  writable: false
});

user.name = "Pete"; // Error: Cannot assign to read only property 'name'

Bây giờ không ai có thể thay đổi tên người dùng của chúng ta, trừ khi họ áp dụng tên riêng của họ definePropertyđể ghi đè tên của chúng ta.

Lỗi chỉ xuất hiện ở chế độ nghiêm ngặt

Trong chế độ không nghiêm ngặt, không có lỗi xảy ra khi ghi vào các thuộc tính không thể ghi và như vậy. Nhưng hoạt động vẫn không thành công. Các hành động vi phạm cờ chỉ âm thầm bị bỏ qua trong không nghiêm ngặt.

Đây là ví dụ tương tự, nhưng thuộc tính được tạo từ đầu:

let user = { };

Object.defineProperty(user, "name", {
  value: "John",
  // for new properties we need to explicitly list what's true
  enumerable: true,
  configurable: true
});

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

3. Non-enumerable

Bây giờ hãy thêm một tùy chỉnh toStringvào user.

Thông thường, một toStringcho các đối tượng là không thể đếm được, nó không hiển thị trong for..in. Nhưng nếu chúng ta thêm một cái toStringcủa riêng mình, thì theo mặc định, nó sẽ hiển thị for..in, như thế này:

let user = {
  name: "John",
  toString() {
    return this.name;
  }
};

// By default, both our properties are listed:
for (let key in user) alert(key); // name, toString

Nếu chúng ta không thích nó, thì chúng ta có thể thiết lập enumerable:false. Sau đó, nó sẽ không xuất hiện trong một for..invòng lặp, giống như một vòng lặp tích hợp:

/*
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",
  toString() {
    return this.name;
  }
};

Object.defineProperty(user, "toString", {
  enumerable: false
});

// Now our toString disappears:
for (let key in user) alert(key); // name

Các thuộc tính không liệt kê cũng được loại trừ khỏi Object.keys:

alert(Object.keys(user)); // name

4. Non-configurable

Cờ không cấu hình ( configurable:false) đôi khi được đặt sẵn cho các đối tượng và thuộc tính tích hợp.

Một thuộc tính không thể cấu hình không thể bị xóa.

Chẳng hạn, Math.PIkhông thể ghi, không liệt kê và không cấu hình:

let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI');

alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
  "value": 3.141592653589793,
  "writable": false,
  "enumerable": false,
  "configurable": false
}
*/

Vì vậy, một lập trình viên không thể thay đổi giá trị Math.PIhoặc ghi đè lên nó.

Math.PI = 3; // Error

// delete Math.PI won't work either

Làm cho một thuộc tính không thể cấu hình là một con đường một chiều. Chúng ta không thể thay đổi nó trở lại với defineProperty.

Nói chính xác, việc không cấu hình áp đặt một số hạn chế đối với defineProperty:

  1. Không thể thay đổi configurablecờ.
  2. Không thể thay đổi enumerablecờ.
  3. Không thể thay đổi writable: falsethành true.
  4. Không thể thay đổi get/setcho một thuộc tính người truy cập (nhưng có thể chỉ định chúng nếu vắng mặt).

Ở đây chúng ta đang thực hiện user.nameliên tục niêm phong mãi mãi


/*
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 = { };

Object.defineProperty(user, "name", {
  value: "John",
  writable: false,
  configurable: false
});

// won't be able to change user.name or its flags
// all this won't work:
//   user.name = "Pete"
//   delete user.name
//   defineProperty(user, "name", { value: "Pete" })
Object.defineProperty(user, "name", {writable: true}); // Error

Không thể cấu hình được

Ngoại lệ đáng chú ý: một giá trị của thuộc tính không thể cấu hình, nhưng có thể ghi có thể thay đổi.

Ý tưởng configurable: falselà để ngăn chặn các thay đổi đối với cờ thuộc tính và xóa nó, không thay đổi giá trị của nó.

5. Object.defineProperies

Có một phương thức Object.defineProperties(obj, descriptors) cho phép xác định nhiều thuộc tính cùng một lúc.

Cú pháp là:

Object.defineProperties(obj, {
  prop1: descriptor1,
  prop2: descriptor2
  // ...
});

Ví dụ:

Object.defineProperties(user, {
  name: { value: "John", writable: false },
  surname: { value: "Smith", writable: false },
  // ...
});

Vì vậy, chúng ta có thể thiết lập nhiều thuộc tính cùng một lúc.

6. Object.getOwnPropertyDescriptors

Để có được tất cả các mô tả thuộc tính cùng một lúc, chúng ta có thể sử dụng phương thức Object.getOwnPropertyDescriptors(obj).

Cùng với Object.definePropertiesnó, nó có thể được sử dụng như một cách nhân bản một cách nhận biết cờ:

let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));

Thông thường khi chúng ta sao chép một đối tượng, chúng ta sử dụng một phép gán để sao chép các thuộc tính, như thế này:

for (let key in user) {
  clone[key] = user[key]
}

Nhưng điều đó không sao chép cờ. Vì vậy, nếu chúng ta muốn có một bản sao tốt hơn, thì Object.definePropertiesđược ưu tiên.

Một sự khác biệt khác là for..inbỏ qua các thuộc tính tượng trưng, ​​nhưng Object.getOwnPropertyDescriptorstrả về tất cả các mô tả thuộc tính bao gồm các thuộc tính tượng trưng.

7. Niêm phong một đối tượng trên toàn cầu

Mô tả thuộc tính làm việc ở cấp độ của các thuộc tính cá nhân.

Ngoài ra còn có các phương thức giới hạn quyền truy cập vào toàn bộ đối tượng:

Object.preventExtensions(obj) Cấm bổ sung các thuộc tính mới cho đối tượng.

Object.seal(obj) Cấm thêm / loại bỏ các thuộc tính. Đặt configurable: falsecho tất cả các thuộc tính hiện có.

Object.freeze(obj) Cấm thêm / xóa / thay đổi thuộc tính. Đặt configurable: false, writable: falsecho tất cả các thuộc tính hiện có.

Và cũng có những bài kiểm tra cho họ:

Object.isExtensible(obj) Trả về falsenếu thêm thuộc tính bị cấm, nếu không true. Object.isSealed(obj) Trả về truenếu thêm / xóa thuộc tính bị cấm và tất cả các thuộc tính hiện có configurable: false.

Object.isFrozen(obj) Trả về truenếu việc thêm / xóa / thay đổi thuộc tính bị cấm và tất cả các thuộc tính hiện tại là configurable: false, writable: false.

Những phương pháp này hiếm khi được sử dụng trong thực tế.

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!