Hướng dẫn toàn diện về Mô tả Thuộc tính Đối tượng ES6: Nắm vững kỹ thuật thao tác sâu vào đối tượng JavaScript

Hướng dẫn toàn diện về Mô tả Thuộc tính Đối tượng ES6: Nắm vững kỹ thuật thao tác sâu vào đối tượng JavaScript

ECMAScript 6 (ES6) là một bản cập nhật lớn cho ngôn ngữ JavaScript, mang đến nhiều tính năng mạnh mẽ. Trong số đó, Mô tả Thuộc tính Đối tượng (Property Descriptors) cung cấp cho nhà phát triển khả năng kiểm soát đối tượng ở một cấp độ chưa từng có. Bài viết này sẽ đi sâu vào các khái niệm cốt lõi, cách sử dụng và các kỹ thuật thực chiến của Mô tả Thuộc tính trong ES6, giúp bạn dễ dàng làm chủ các thao tác sâu vào đối tượng JavaScript.

Khái niệm Mô tả Thuộc tính Đối tượng là gì?

Mô tả Thuộc tính Đối tượng là siêu dữ liệu trong ES6 dùng để định nghĩa các đặc tính của thuộc tính đối tượng, cho phép nhà phát triển kiểm soát chính xác hành vi của thuộc tính, chẳng hạn như có thể sửa đổi hay không, có thể liệt kê hay không, có thể xóa hay không. Thông qua Mô tả Thuộc tính, chúng ta có thể thực hiện đóng gói dữ liệu, kiểm soát truy cập và các mẫu thiết kế đối tượng nâng cao.

Loại mô tả thuộc tính cốt lõi

ES6 định nghĩa hai loại mô tả thuộc tính chính:

  1. Mô tả Dữ liệu (Data Descriptors): Dùng để mô tả các thuộc tính dữ liệu thông thường, chứa các đặc tính valuewritable.
  2. Mô tả Truy cập (Accessor Descriptors): Dùng để mô tả các thuộc tính truy cập, chứa các hàm getset.

Hai loại này loại trừ lẫn nhau, một thuộc tính không thể vừa là mô tả dữ liệu vừa là mô tả truy cập.

Phân tích chi tiết Mô tả Dữ liệu

Mô tả Dữ liệu dùng để kiểm soát hành vi cơ bản của các thuộc tính thông thường, chủ yếu bao gồm bốn đặc tính:

value - Giá trị thuộc tính

Đặc tính value định nghĩa giá trị cụ thể của thuộc tính, có thể là bất kỳ kiểu dữ liệu JavaScript nào.

const myObject = {};
Object.defineProperty(myObject, 'title', {
  value: 'Tính năng ES6',
  writable: true,
  enumerable: true,
  configurable: true
});
console.log(myObject.title); // In ra: "Tính năng ES6"

writable - Khả năng ghi

Đặc tính writable kiểm soát xem giá trị thuộc tính có thể được sửa đổi hay không, mặc định là false.

const myObject = {};
Object.defineProperty(myObject, 'score', {
  value: 95,
  writable: false // Không thể sửa đổi
});

myObject.score = 100; // Trong chế độ nghiêm ngặt sẽ ném ra lỗi
console.log(myObject.score); // In ra: 95 (giá trị không thay đổi)

enumerable - Khả năng liệt kê

Đặc tính enumerable kiểm soát xem thuộc tính có xuất hiện trong các thuộc tính liệt kê của đối tượng (như vòng lặp for...in, Object.keys()) hay không, mặc định là false.

const myObject = {
  model: 'X',
  year: '2023'
};

Object.defineProperty(myObject, 'year', {
  enumerable: false // Đặt là không thể liệt kê
});

console.log(Object.keys(myObject)); // In ra: ["model"] (thuộc tính year không được liệt kê)

configurable - Khả năng cấu hình

Đặc tính configurable kiểm soát xem mô tả thuộc tính có thể được sửa đổi hay không, cũng như thuộc tính có thể bị xóa hay không, mặc định là false. Một khi được đặt thành false, bạn không thể thay đổi nó trở lại true.

const myObject = {};
Object.defineProperty(myObject, 'creator', {
  value: 'JavaScript',
  configurable: false
});

// Cố gắng sửa đổi mô tả sẽ thất bại
Object.defineProperty(myObject, 'creator', {
  writable: true
}); // Ném ra TypeError

// Cố gắng xóa thuộc tính cũng sẽ thất bại
delete myObject.creator; // Trả về false
console.log(myObject.creator); // In ra: "JavaScript"

Phân tích chi tiết Mô tả Truy cập

Mô tả Truy cập cho phép chúng ta định nghĩa hành vi đọc và gán giá trị cho thuộc tính thông qua các hàm getter và setter, chủ yếu bao gồm các đặc tính sau:

get - Hàm lấy giá trị

get là một hàm không có tham số, được gọi khi đọc thuộc tính, giá trị trả về sẽ là giá trị của thuộc tính.

set - Hàm đặt giá trị

set là một hàm chấp nhận một tham số, được gọi khi gán giá trị cho thuộc tính, tham số này là giá trị mới được gán.

const student = {
  _grade: '' // Theo quy ước, thuộc tính bắt đầu bằng dấu gạch dưới là thuộc tính riêng
};

Object.defineProperty(student, 'grade', {
  get() {
    console.log('Đang đọc điểm');
    return this._grade;
  },
  set(value) {
    console.log('Đang đặt điểm là:', value);
    if (typeof value === 'string' && value.length > 0) {
      this._grade = value;
    } else {
      console.log('Điểm phải là chuỗi không rỗng');
    }
  },
  enumerable: true,
  configurable: true
});

student.grade = 'A'; // In ra: "Đang đặt điểm là: A"
console.log(student.grade); // In ra: "Đang đọc điểm" và "A"

Các phương thức hữu ích về Mô tả Thuộc tính

ES6 cung cấp một số phương thức hữu ích để thao tác và truy vấn Mô tả Thuộc tính:

Object.getOwnPropertyDescriptor()

Lấy mô tả thuộc tính của một thuộc tính riêng của đối tượng.

const myObject = { x: 1 };
const descriptor = Object.getOwnPropertyDescriptor(myObject, 'x');
console.log(descriptor);
// In ra: { value: 1, writable: true, enumerable: true, configurable: true }

Object.getOwnPropertyDescriptors()

Lấy mô tả của tất cả các thuộc tính riêng của đối tượng.

const myObject = { x: 1, y: 2 };
const descriptors = Object.getOwnPropertyDescriptors(myObject);
console.log(descriptors); // Bao gồm mô tả của cả hai thuộc tính x và y

Object.defineProperty()

Định nghĩa hoặc sửa đổi một thuộc tính của đối tượng và trả về đối tượng đó.

Object.defineProperties()

Định nghĩa hoặc sửa đổi nhiều thuộc tính của đối tượng cùng một lúc.

const myObject = {};
Object.defineProperties(myObject, {
  x: { value: 5, writable: true },
  y: { get() { return this.x * 3; }, enumerable: true }
});
console.log(myObject.x); // 5
console.log(myObject.y); // 15

Các ứng dụng thực chiến

1. Thực hiện tính riêng tư của dữ liệu

Bằng cách đặt thuộc tính thành không thể liệt kê và không thể cấu hình, chúng ta có thể mô phỏng thuộc tính riêng tư:

function createStudent(fullName) {
  const student = {
    _fullName: fullName // Thuộc tính dùng nội bộ
  };
  
  // Truy cập công khai
  Object.defineProperty(student, 'fullName', {
    get() {
      return this._fullName;
    },
    set(value) {
      if (value && typeof value === 'string') {
        this._fullName = value;
      }
    },
    enumerable: true
  });
  
  return student;
}

const student = createStudent('Nguyễn Văn A');
console.log(student.fullName); // Nguyễn Văn A
student.fullName = 'Trần Thị B';
console.log(student.fullName); // Trần Thị B
console.log(student._fullName); // Trần Thị B (Lưu ý: đây chỉ là mô phỏng riêng tư, không phải riêng tư thực sự)

2. Thực hiện xác thực giá trị thuộc tính

Sử dụng setter có thể xác thực và chuyển đổi giá trị của thuộc tính:

const item = {};

Object.defineProperty(item, 'cost', {
  value: 0,
  writable: true,
  enumerable: true,
  configurable: true
});

Object.defineProperty(item, 'salePrice', {
  get() {
    return this.cost * 0.8; // Giảm giá 20%
  },
  set(value) {
    if (typeof value === 'number' && value >= 0) {
      this.cost = value / 0.8; // Tính ngược giá gốc
    } else {
      throw new Error('Giá bán phải là số không âm');
    }
  }
});

item.cost = 100;
console.log(item.salePrice); // 80

item.salePrice = 80;
console.log(item.cost); // 100

3. Thực hiện thuộc tính tính toán

Bằng cách sử dụng getter, chúng ta có thể tạo các thuộc tính tính toán phụ thuộc vào các thuộc tính khác:

const circle = {
  radius: 5
};

Object.defineProperty(circle, 'circumference', {
  get() {
    return 2 * Math.PI * this.radius;
  },
  enumerable: true
});

console.log(circle.circumference); // 31.4159...
circle.radius = 10;
console.log(circle.circumference); // 62.8318... (cập nhật tự động)

4. Đóng băng thuộc tính đối tượng

Kết hợp writable: falseconfigurable: false có thể đóng băng thuộc tính, ngăn chặn việc sửa đổi:

const settings = {
  endpoint: 'https://api.example.com/data'
};

// Đóng băng thuộc tính
Object.defineProperty(settings, 'endpoint', {
  writable: false,
  configurable: false
});

// Cố gắng sửa đổi sẽ thất bại
settings.endpoint = 'https://new-api.example.com/data';
console.log(settings.endpoint); // Vẫn là "https://api.example.com/data"

Các vấn đề thường gặp và lưu ý

1. Gán trực tiếp vs Object.defineProperty

Các thuộc tính được tạo bằng cách gán trực tiếp có các giá trị mô tả mặc định:

  • writable: true
  • enumerable: true
  • configurable: true

Trong khi đó, các thuộc tính được tạo bằng Object.defineProperty có các giá trị mặc định là false.

2. Mô tả thuộc tính kế thừa

Object.getOwnPropertyDescriptor chỉ trả về mô tả của các thuộc tính riêng của đối tượng, không trả về mô tả của các thuộc tính kế thừa.

3. Hạn chế của thuộc tính không thể cấu hình

Một khi thuộc tính có configurable được đặt thành false, bạn sẽ không thể:

  • Thay đổi giá trị của configurable.
  • Thay đổi giá trị của enumerable.
  • Thay đổi writable từ false thành true (nhưng có thể từ true thành false).
  • Thay đổi value (nếu writablefalse).
  • Xóa thuộc tính đó.

Thẻ: es6 JavaScript Object Property Descriptors Object.defineProperty get

Đăng vào ngày 23 tháng 5 lúc 06:11