Hướng dẫn dùng object.defineproperty JavaScript - use JavaScript object.defineproperty

Mở đầu

Nếu đã hoặc đang làm việc với JavaScript, có lẽ bạn đã biết đến lớp Object và sử dụng qua những phương thức như Object.keys(), Object.values() hay Object.entries(). Nhưng có thể bạn chưa biết nhưng phương thức khác của lớp mà nó cũng hữu ích không kém. Và trong bài viết này chúng ta hay cùng "chỉ mặt gọi tên" chúng và tìm hiểu cách sử dụng nhé.

1. Khái niệm Object trong JavaScript

Trong JavaScript, một Object (đối tượng) là một tập hợp các cặp key - value không có thứ tự. Mỗi cặp key - value được gọi là một thuộc tính.

  • Key của một thuộc tính có thể là một chuỗi
  • Value của một thuộc tính có thể là bất kỳ giá trị JavaScript hợp lệ nào, ví dụ: Một chuỗi, một số, một mảng và thậm chí là một hàm.

Khi một hàm là một thuộc tính của một object, nó không còn được gọi là hàm nữa mà thường được gọi là một phương thức (method)

JavaScript cung cấp cho bạn nhiều cách để tạo một object mới. Cách phổ biến nhất là sử dụng cú pháp theo nghĩa đen (sử dụng cú pháp { }) của object.

1.1 Tạo Object sử dụng cú pháp { }

Ví dụ sau tạo một object trống bằng cách sử dụng cú pháp theo nghĩa đen của object:

const student = { };

Để tạo một object có thuộc tính, bạn sử dụng key: value trong dấu ngoặc nhọn { }.

Ví dụ: Đoạn code tạo một student object.

const student = {
    name: "Ngọc Anh",
    age: 18
};

Hoặc sử dụng từ khóa new Object()

1.1 Tạo Object sử dụng cú pháp new Object()

Ví dụ sau tạo một object trống bằng cách sử dụng cú pháp new Object():

let student = new Object();

Để thêm các thuộc tính ta dùng

student.name = "Ngọc Anh";
student.age = 18

// Or

let student = new Object({
    name: "Ngọc Anh",
    age: 18
});

Bản thân Object là một hàm dựng (constructor) được dùng để tạo ra thể hiện (instance) của lớp kiểu dữ liệu tương ứng cho giá trị được truyền vào. Nếu value là null hay undefined, kết quả sẽ là một đối tượng rỗng.

2. Một số phương thức hữu ích của Object trong JavaScript

2.1 Object.create()

Cho phép bạn tạo một thể hiện của một lớp bằng cách dùng prototype mà không cần phải gọi đến hàm dựng (constructor)

Cú pháp:

Object.create(prototype, [properties])
  • prototype: Object prototype mới được tạo. Nó có thể là object hoặc null.
  • properties: Các thuộc tính của object mới (tùy chọn).

2.1.1 prototype

Ví dụ:

Không có prototype

const user = Object.create()
console.log(user) // {}

Có prototype

class User {
  constructor() {
    this.createdAt = new Date()
  }
}

const user = Object.create(User.prototype)
console.log(user.constructor === User) // true
console.log(user.createdAt) // undefined

// So sánh khi dùng từ khoá `new`
const user2 = new User()
console.log(user2.createdAt) // 2021-05-15T16:00:30.528Z

Một trong những ứng dụng phổ biến của Object.create() là tạo ra đối tượng không kế thừa từ bất cứ lớp nào, hay nói một cách khác, không có prototype. Vì mặc định trong JavaScript, khi bạn khai báo một object literal như thế này

const obj = {
  foo: 1
}
console.log( obj.constructor === Object) // true

Bản thân của obj là một thể hiện của lớp Object và obj.constructor === Object. Bằng cách gọi Object.create(null) hoặc Object.create(undefined), chúng ta có thể tạo ra những đối tượng "không cha không mẹ, là tinh tuý của đất trời".

const tonNgoKhong = Object.create(null)
console.log(tonNgoKhong.prototype) // undefined

Nếu đã học qua Java/C#/Ruby thì chắc bạn đã nghe tới tất cả các lớp đều được kế thừa từ lớp Object.

2.1.2 properties

Tham số properties của Object.create() cho phép bạn khai báo những thuộc tính của thể hiện được tạo bằng cách truyền vào các property descriptors.

Vậy property descriptor là gì?

Property descriptor (mô tả thuộc tính) là một object JavaScript thông thường (Plain Old JavaScript Object - POJO), được sử dụng trong Object.create(), Object.defineProperty(), hoặc Object.defineProperties() để thay đổi các thuộc tính đã có của một đối tượng, hoặc tạo đối tượng mới.

Ví dụ

const user = Object.create(User.prototype, {
  id: {
    // Sử dụng data descriptor
    writable: false,
    configurable: true,
    value: 1998,
  },
  name: {
    // Còn đây là accessor descriptor
    get() {
      return this.value
    },
    set(name) {
      this.value = name.toUpperCase()
    },
    configurable: true,
  },
})

user.name = 'Doan Phu'
console.log(user.id) // 1998
console.log(user.name) // DOAN PHU

Property descriptor được chia làm hai loại: accessor descriptors và data descriptors. Bạn chỉ có thể sử dụng MỘT TRONG HAI loại descriptor này cùng lúc mà thôi.MỘT TRONG HAI loại descriptor này cùng lúc mà thôi.

const student = {
    name: "Ngọc Anh",
    age: 18
};
0

Property descriptors

  • configurable: Nếu bằng true, property descriptor của thuộc tính này có thể được thay đổi, hoặc thuộc tính này có thể được xoá ra khỏi đối tượng. Mặc định: false.
  • enumerable: Nếu bằng true, thuộc tính này có thể được truy xuất khi dùng for...in hoặc Object.keys(). Mặc định: false.

Accessor descriptors là một cặp getter/ setter gồm hai hàm: là một cặp getter/ setter gồm hai hàm:

  • get: () -> any Hàm get() trả về giá trị của thuộc tính, hoặc undefined nếu không được khai báo.
  • set: any -> () Hàm set(value) nhận vào một giá trị bất kì.

Data descriptors lại bao gồm hai thiết lập sau: lại bao gồm hai thiết lập sau:

  • value: Cái này quá rõ ràng rồi, không cần phải nói nhiều.
  • writable: Nếu bằng true, thuộc tính này có thể được gán giá trị mới. Mặc định: false.

Note: Nếu bạn khai báo một mô tả thuộc tính mà có chứa lẫn lộn accessor và data descriptors, trình biên dịch sẽ quăng ra một TypeError. Nếu bạn khai báo một mô tả thuộc tính mà có chứa lẫn lộn accessor và data descriptors, trình biên dịch sẽ quăng ra một TypeError.

const student = {
    name: "Ngọc Anh",
    age: 18
};
1

2.2 Object.defineProperty()

Phương thức này cho phép chúng ta khai báo thuộc tính mới, hoặc thay đổi một thuộc tính đã có của một object bằng cách sử dụng property descriptors, như đã trình bày ở phần trước.

Phương thức này chỉ cho phép ta thay đổi một thuộc tính duy nhất.

Cú pháp:

const student = {
    name: "Ngọc Anh",
    age: 18
};
2

Ví dụ:

Không có prototype

Có prototype

Nhưng khác nhau, bản chất nó là đây:

const student = {
    name: "Ngọc Anh",
    age: 18
};
4

2.3 Object.defineProperties()

Nó cũng là phương thức này cho phép chúng ta khai báo thuộc tính mới, hoặc thay đổi một thuộc tính đã có của một object bằng cách sử dụng property descriptors.

Cái này gần giống với giống như Object.defineProperty(), nhưng khác ở chỗ là lại cho phép bạn thay đổi nhiều thuộc tính cùng lúc.

Cú pháp:

const student = {
    name: "Ngọc Anh",
    age: 18
};
5

Ví dụ:

const student = {
    name: "Ngọc Anh",
    age: 18
};
6

2.4 Object.assign()

Sẽ sao chép những thuộc tính có thể duyệt được (enumerable) của một hoặc nhiều đối tượng nguồn (sources) qua đối tượng đích (target).

Cú pháp:

const student = {
    name: "Ngọc Anh",
    age: 18
};
7

Ví dụ:

const student = {
    name: "Ngọc Anh",
    age: 18
};
8

2.4 Object.assign()

const student = {
    name: "Ngọc Anh",
    age: 18
};
9

Sẽ sao chép những thuộc tính có thể duyệt được (enumerable) của một hoặc nhiều đối tượng nguồn (sources) qua đối tượng đích (target).

let student = new Object();
0

Thông thường chúng ta sẽ dùng Object.assign() để sao chép một đối tượng, thế nên bạn hay thấy tham số đầu tiên của Object.assign() là một đối tượng rỗng.

Nhưng giờ thì ai cũng xài object spread cho nhanh hết rồi.

2.5 Object.preventExtensions()

Cú pháp:

let student = new Object();
1

Ví dụ:

let student = new Object();
2

2.4 Object.assign()

Sẽ sao chép những thuộc tính có thể duyệt được (enumerable) của một hoặc nhiều đối tượng nguồn (sources) qua đối tượng đích (target).

Thông thường chúng ta sẽ dùng Object.assign() để sao chép một đối tượng, thế nên bạn hay thấy tham số đầu tiên của Object.assign() là một đối tượng rỗng.

Cú pháp:

let student = new Object();
3

Ví dụ:

let student = new Object();
4

2.4 Object.assign()

Sẽ sao chép những thuộc tính có thể duyệt được (enumerable) của một hoặc nhiều đối tượng nguồn (sources) qua đối tượng đích (target).

Hướng dẫn dùng object.defineproperty JavaScript - use JavaScript object.defineproperty

Cú pháp:

let student = new Object();
5

Ví dụ:

Ví dụ:

let student = new Object();
6

2.4 Object.assign()

Sẽ sao chép những thuộc tính có thể duyệt được (enumerable) của một hoặc nhiều đối tượng nguồn (sources) qua đối tượng đích (target).

Cú pháp:

let student = new Object();
7

Ví dụ:

let student = new Object();
8

2.4 Object.assign() Đừng quên là những thuộc tính này phải có enumerable = true nhé.

Sẽ sao chép những thuộc tính có thể duyệt được (enumerable) của một hoặc nhiều đối tượng nguồn (sources) qua đối tượng đích (target).

Thông thường chúng ta sẽ dùng Object.assign() để sao chép một đối tượng, thế nên bạn hay thấy tham số đầu tiên của Object.assign() là một đối tượng rỗng.

Cú pháp:

let student = new Object();
9

Ví dụ:

student.name = "Ngọc Anh";
student.age = 18

// Or

let student = new Object({
    name: "Ngọc Anh",
    age: 18
});
0

2.4 Object.assign()

student.name = "Ngọc Anh";
student.age = 18

// Or

let student = new Object({
    name: "Ngọc Anh",
    age: 18
});
1

Sẽ sao chép những thuộc tính có thể duyệt được (enumerable) của một hoặc nhiều đối tượng nguồn (sources) qua đối tượng đích (target). thứ tự của các cặp thuộc tính được trả về không phụ thuộc vào thứ tự chúng được khai báo nhé. Bạn cũng có thể dùng Object.entries() để chuyển đổi một object thường thành Map, WeakMap hay bất cứ constructor nào nhận một mảng các cặp [key, value].

student.name = "Ngọc Anh";
student.age = 18

// Or

let student = new Object({
    name: "Ngọc Anh",
    age: 18
});
2

Thông thường chúng ta sẽ dùng Object.assign() để sao chép một đối tượng, thế nên bạn hay thấy tham số đầu tiên của Object.assign() là một đối tượng rỗng.

Nhưng giờ thì ai cũng xài object spread cho nhanh hết rồi.

Cú pháp:

student.name = "Ngọc Anh";
student.age = 18

// Or

let student = new Object({
    name: "Ngọc Anh",
    age: 18
});
3

Ví dụ:

student.name = "Ngọc Anh";
student.age = 18

// Or

let student = new Object({
    name: "Ngọc Anh",
    age: 18
});
4

2.4 Object.assign()

Sẽ sao chép những thuộc tính có thể duyệt được (enumerable) của một hoặc nhiều đối tượng nguồn (sources) qua đối tượng đích (target).

Cú pháp:

student.name = "Ngọc Anh";
student.age = 18

// Or

let student = new Object({
    name: "Ngọc Anh",
    age: 18
});
5

Thông thường chúng ta sẽ dùng Object.assign() để sao chép một đối tượng, thế nên bạn hay thấy tham số đầu tiên của Object.assign() là một đối tượng rỗng.

  • Nhưng giờ thì ai cũng xài object spread cho nhanh hết rồi.
  • 2.5 Object.preventExtensions()
  • Phươn thức này làm một chuyện rất đơn giản: không cho phép bạn thêm thuộc tính mới vào đối tượng.
  • Để kiểm tra một đối tượng có thể được mở rộng hay không, bạn có thể dùng phương thức Object.isExtensible().
  • 2.6 Object.seal()
  • Phương thức này dùng để ngăn không cho bạn thêm thuộc tính mới vào đối tượng. Nhưng bạn vẫn có thể thay đổi giá trị của chúng.
  • Để kiểm tra một đối tượng có bị phong kín hay không, bạn dùng Object.isSealed().

2.7 Object.freeze()

Ví dụ:

student.name = "Ngọc Anh";
student.age = 18

// Or

let student = new Object({
    name: "Ngọc Anh",
    age: 18
});
6

Phương thức "đông cứng" một đối tượng: không cho phép thêm vào thuộc tính mới, hay thay đổi hành vi của những thuộc tính đã có, hay xóa thuộc tính. Nói tóm lại, không làm được gì cả

Bạn có thể dùng Object.isFrozen(obj) để kiểm tra một object có bị đông cứng không. Tình hình là hiện tại không có phương thức nào để "rã đông" một đối tượng hết.

2.8 Object.keys() và Object.values()

Cặp đôi hoàn cảnh này thì quá quen thuộc rồi. Object.keys() trả về một mảng chứa tên các thuộc tính của một đối tượng, và Object.values() trả về một mảng chứa giá trị của các thuộc tính đó.

student.name = "Ngọc Anh";
student.age = 18

// Or

let student = new Object({
    name: "Ngọc Anh",
    age: 18
});
7

Note: Đừng quên là những thuộc tính này phải có enumerable = true nhé.

2.9 Object.entries()

Phương thức này trả về một mảng các cặp (pair) thuộc tính có dạng [tên thuộc tính, giá trị].

Object.entries() rất hữu ích khi bạn cần truy xuất tên và giá trị của thuộc tính cùng lúc. Chẳng hạn như:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object