Các đối tượng là đơn vị đóng gói chính trong Lập trình hướng đối tượng. Trong bài viết này, tôi sẽ mô tả một số cách để xây dựng các đối tượng trong JavaScript. Họ đang:
- Đối tượng theo nghĩa đen
- Object.create()
- Các lớp học
- chức năng nhà máy
đối tượng chữ
Đầu tiên, chúng ta cần phân biệt giữa cấu trúc dữ liệu và đối tượng hướng đối tượng. Cấu trúc dữ liệu có dữ liệu công khai và không có hành vi. Điều đó có nghĩa là họ không có phương pháp.
Chúng ta có thể dễ dàng tạo các đối tượng như vậy bằng cách sử dụng cú pháp nghĩa đen của đối tượng. Nó trông như thế này:
const product = {
name: 'apple',
category: 'fruits',
price: 1.99
}
console.log(product);
Đối tượng trong JavaScript là tập hợp động của các cặp khóa-giá trị. Khóa luôn là một chuỗi và phải là duy nhất trong bộ sưu tập. Giá trị có thể là nguyên hàm, đối tượng hoặc thậm chí là hàm.
Chúng ta có thể truy cập một thuộc tính bằng ký hiệu dấu chấm hoặc hình vuông.
console.log(product.name);
//"apple"
console.log(product["name"]);
//"apple"
Đây là một ví dụ trong đó giá trị là một đối tượng khác.
const product = {
name: 'apple',
category: 'fruits',
price: 1.99,
nutrients : {
carbs: 0.95,
fats: 0.3,
protein: 0.2
}
}
Giá trị của carbs
tài sản là một đối tượng mới. Đây là cách chúng ta có thể truy cập vào carbs
tài sản.
console.log(product.nutrients.carbs);
//0.95
Tên thuộc tính viết tắt
Hãy xem xét trường hợp chúng ta có các giá trị thuộc tính được lưu trữ trong các biến.
const name="apple";
const category = 'fruits';
const price = 1.99;
const product = {
name: name,
category: category,
price: price
}
JavaScript hỗ trợ cái được gọi là tên thuộc tính tốc ký. Nó cho phép chúng ta tạo một đối tượng chỉ bằng tên của biến. Nó sẽ tạo một thuộc tính có cùng tên. Đối tượng tiếp theo theo nghĩa đen tương đương với đối tượng trước đó.
const name="apple";
const category = 'fruits';
const price = 1.99;
const product = {
name,
category,
price
}
Object.create
Tiếp theo, chúng ta hãy xem cách triển khai các đối tượng có hành vi, các đối tượng hướng đối tượng.
JavaScript có cái được gọi là hệ thống nguyên mẫu cho phép chia sẻ hành vi giữa các đối tượng. Ý tưởng chính là tạo một đối tượng được gọi là nguyên mẫu với một hành vi phổ biến và sau đó sử dụng nó khi tạo các đối tượng mới.
Hệ thống nguyên mẫu cho phép chúng ta tạo các đối tượng kế thừa hành vi từ các đối tượng khác.
Hãy tạo một đối tượng nguyên mẫu cho phép chúng ta thêm các sản phẩm và lấy tổng giá từ một giỏ hàng.
const cartPrototype = {
addProduct: function(product){
if(!this.products){
this.products = [product]
} else {
this.products.push(product);
}
},
getTotalPrice: function(){
return this.products.reduce((total, p) => total + p.price, 0);
}
}
Lưu ý rằng lần này giá trị của tài sản addProduct
là một chức năng. Chúng ta cũng có thể viết đối tượng trước đó bằng cách sử dụng một dạng ngắn hơn gọi là cú pháp phương thức tốc ký.
const cartPrototype = {
addProduct(product){/*code*/},
getTotalPrice(){/*code*/}
}
Các cartPrototype
là đối tượng nguyên mẫu giữ hành vi chung được biểu thị bằng hai phương thức, addProduct
và getTotalPrice
. Nó có thể được sử dụng để xây dựng các đối tượng khác kế thừa hành vi này.
const cart = Object.create(cartPrototype);
cart.addProduct({name: 'orange', price: 1.25});
cart.addProduct({name: 'lemon', price: 1.75});
console.log(cart.getTotalPrice());
//3
Các cart
đối tượng có cartPrototype
như nguyên mẫu của nó. Nó kế thừa hành vi từ nó. cart
có một thuộc tính ẩn trỏ đến đối tượng nguyên mẫu.
Khi chúng ta sử dụng một phương thức trên một đối tượng, phương thức đó trước tiên được tìm kiếm trên chính đối tượng đó hơn là trên nguyên mẫu của nó.
cái này
Lưu ý rằng chúng tôi đang sử dụng một từ khóa đặc biệt được gọi là this
để truy cập và sửa đổi dữ liệu trên đối tượng.
Hãy nhớ rằng hàm là đơn vị hành vi độc lập trong JavaScript. Chúng không nhất thiết phải là một phần của đối tượng. Khi có, chúng ta cần có một tham chiếu cho phép hàm truy cập các thành viên khác trên cùng một đối tượng. this
là bối cảnh chức năng. Nó cho phép truy cập vào các thuộc tính khác.
Dữ liệu
Bạn có thể thắc mắc tại sao chúng ta chưa định nghĩa và khởi tạo products
thuộc tính trên chính đối tượng nguyên mẫu.
Chúng ta không nên làm điều đó. Nguyên mẫu nên được sử dụng để chia sẻ hành vi chứ không phải dữ liệu. Chia sẻ dữ liệu sẽ dẫn đến việc có các sản phẩm giống nhau trên một số đối tượng giỏ hàng. Hãy xem xét đoạn mã dưới đây:
const cartPrototype = {
products:[],
addProduct: function(product){
this.products.push(product);
},
getTotalPrice: function(){}
}
const cart1 = Object.create(cartPrototype);
cart1.addProduct({name: 'orange', price: 1.25});
cart1.addProduct({name: 'lemon', price: 1.75});
console.log(cart1.getTotalPrice());
//3
const cart2 = Object.create(cartPrototype);
console.log(cart2.getTotalPrice());
//3
cả hai cart1
và cart2
các đối tượng kế thừa các hành vi phổ biến từ cartPrototype
cũng chia sẻ cùng một dữ liệu. Chúng tôi không muốn điều đó. Nguyên mẫu nên được sử dụng để chia sẻ hành vi chứ không phải dữ liệu.
Lớp
Hệ thống nguyên mẫu không phải là cách phổ biến để xây dựng các đối tượng. Các nhà phát triển quen thuộc hơn với việc xây dựng các đối tượng ngoài các lớp.
Cú pháp lớp cho phép một cách quen thuộc hơn để tạo các đối tượng chia sẻ một hành vi chung. Nó vẫn tạo ra cùng một nguyên mẫu nhưng cú pháp rõ ràng hơn và chúng tôi cũng tránh được vấn đề liên quan đến dữ liệu trước đó. Lớp cung cấp một vị trí cụ thể để xác định dữ liệu riêng biệt cho từng đối tượng.
Đây là cùng một đối tượng được tạo bằng cú pháp lớp đường:
class Cart{
constructor(){
this.products = [];
}
addProduct(product){
this.products.push(product);
}
getTotalPrice(){
return this.products.reduce((total, p) => total + p.price, 0);
}
}
const cart = new Cart();
cart.addProduct({name: 'orange', price: 1.25});
cart.addProduct({name: 'lemon', price: 1.75});
console.log(cart.getTotalPrice());
//3
const cart2 = new Cart();
console.log(cart2.getTotalPrice());
//0
Lưu ý rằng lớp có một phương thức khởi tạo khởi tạo dữ liệu riêng biệt cho từng đối tượng mới. Dữ liệu trong hàm tạo không được chia sẻ giữa các phiên bản. Để tạo một thể hiện mới, chúng tôi sử dụng new
từ khóa.
Tôi nghĩ rằng cú pháp lớp rõ ràng và quen thuộc hơn đối với hầu hết các nhà phát triển. Tuy nhiên, nó cũng làm một việc tương tự, nó tạo ra một nguyên mẫu với tất cả các phương thức và sử dụng nó để định nghĩa các đối tượng mới. Nguyên mẫu có thể được truy cập với Cart.prototype
.
Hóa ra hệ thống nguyên mẫu đủ linh hoạt để cho phép cú pháp lớp. Vì vậy, hệ thống lớp học có thể được mô phỏng bằng hệ thống nguyên mẫu.
Thuộc tính riêng tư
Điều duy nhất là products
thuộc tính trên đối tượng mới là công khai theo mặc định.
console.log(cart.products);
//[{name: "orange", price: 1.25}
// {name: "lemon", price: 1.75}]
Chúng ta có thể đặt nó ở chế độ riêng tư bằng cách sử dụng hàm băm #
tiếp đầu ngữ.
Thuộc tính riêng được khai báo với #name
cú pháp. #
là một phần của chính tên thuộc tính và nên được sử dụng để khai báo và truy cập thuộc tính. Đây là một ví dụ khai báo products
như một tài sản riêng:
class Cart{
#products
constructor(){
this.#products = [];
}
addProduct(product){
this.#products.push(product);
}
getTotalPrice(){
return this.#products.reduce((total, p) => total + p.price, 0);
}
}
console.log(cart.#products);
//Uncaught SyntaxError: Private field '#products' must be declared in an enclosing class
Chức năng nhà máy
Một tùy chọn khác là tạo các đối tượng dưới dạng tập hợp các bao đóng.
Bao đóng là khả năng một hàm truy cập các biến và tham số từ hàm kia ngay cả sau khi hàm bên ngoài đã thực thi. Hãy nhìn vào cart
đối tượng được xây dựng với cái được gọi là hàm xuất xưởng.
function Cart() {
const products = [];
function addProduct(product){
products.push(product);
}
function getTotalPrice(){
return products.reduce((total, p) => total + p.price, 0);
}
return {
addProduct,
getTotalPrice
}
}
const cart = Cart();
cart.addProduct({name: 'orange', price: 1.25});
cart.addProduct({name: 'lemon', price: 1.75});
console.log(cart.getTotalPrice());
//3
addProduct
và getTotalPrice
là hai hàm bên trong truy cập vào biến products
từ cha mẹ của họ. Họ có quyền truy cập vào products
sự kiện biến sau cha mẹ Cart
đã thực thi. addProduct
và getTotalPrice
là hai lần đóng chia sẻ cùng một biến riêng tư.
Cart
là một chức năng nhà máy.
đối tượng mới cart
được tạo bằng hàm xuất xưởng có products
biến riêng tư. Nó không thể được truy cập từ bên ngoài.
console.log(cart.products);
//undefined
Các chức năng của nhà máy không cần new
từ khóa nhưng bạn có thể sử dụng nó nếu muốn. Nó sẽ trả về cùng một đối tượng cho dù bạn có sử dụng nó hay không.
Tóm tắt lại
Thông thường, chúng tôi làm việc với hai loại đối tượng, cấu trúc dữ liệu có dữ liệu công khai và không có hành vi và đối tượng hướng đối tượng có dữ liệu riêng tư và hành vi công khai.
Cấu trúc dữ liệu có thể được xây dựng dễ dàng bằng cách sử dụng cú pháp ký tự đối tượng.
JavaScript cung cấp hai cách sáng tạo để tạo các đối tượng hướng đối tượng. Đầu tiên là sử dụng một đối tượng nguyên mẫu để chia sẻ hành vi chung. Các đối tượng kế thừa từ các đối tượng khác. Các lớp cung cấp một cú pháp dễ hiểu để tạo các đối tượng như vậy.
Tùy chọn khác là xác định các đối tượng là tập hợp các bao đóng.
Để biết thêm về bao đóng và kỹ thuật lập trình hàm, hãy xem loạt sách Lập trình hàm với JavaScript và React của tôi.
Các Lập trình chức năng trong JavaScript sách sắp ra mắt.