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.
Nội dung chính
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ếutrue
, giá trị có thể được thay đổi, nếu không thì chỉ đọc.enumerable
– nếutrue
, 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ếutrue
, 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, defineProperty
cậ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 true
và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.name
không thể ghi (không thể được chỉ định lại) bằng cách thay đổi writable
cờ:
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 toString
vào user
.
Thông thường, một toString
cho 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 toString
củ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..in
vò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.PI
khô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.PI
hoặ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
:
- Không thể thay đổi
configurable
cờ. - Không thể thay đổi
enumerable
cờ. - Không thể thay đổi
writable: false
thànhtrue
. - Không thể thay đổi
get/set
cho 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.name
liê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: false
là để 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.defineProperties
nó, 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..in
bỏ qua các thuộc tính tượng trưng, nhưng Object.getOwnPropertyDescriptors
trả 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: false
cho 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: false
cho 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ề false
nếu thêm thuộc tính bị cấm, nếu không true
. Object.isSealed(obj) Trả về true
nế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ề true
nế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!