Viết chương trình bằng JavaScript dễ tiếp cận ngay từ đầu. Ngôn ngữ này dễ tính và bạn sẽ quen với khả năng của nó. Theo thời gian và kinh nghiệm làm việc trên các dự án phức tạp, bạn bắt đầu đánh giá cao những thứ như khả năng kiểm soát và độ chính xác trong luồng phát triển.
Một điều khác mà bạn có thể bắt đầu đánh giá cao là khả năng dự đoán, nhưng điều đó không được đảm bảo nhiều trong JavaScript. Trong khi các giá trị nguyên thủy đủ khả năng dự đoán, thì các đối tượng thì không. Khi bạn nhận được một đối tượng làm đầu vào, bạn cần kiểm tra mọi thứ:
Bằng cách biến các đối tượng thành nguyên thủy, nhiều điểm lỗi tiềm ẩn sẽ được chuyển đến một nơi duy nhất — nơi các đối tượng được khởi tạo. Nếu bạn có thể đảm bảo rằng các đối tượng của mình được khởi tạo bằng một tập hợp các thuộc tính nhất định và các thuộc tính đó giữ các giá trị nhất định, bạn không phải kiểm tra những thứ như sự tồn tại của các thuộc tính ở bất kỳ nơi nào khác trong chương trình của mình. Bạn có thể đảm bảo rằng
Hãy xem xét một trong những cách chúng ta có thể tạo các đối tượng nguyên thủy. Đây không phải là cách duy nhất hoặc thậm chí là cách thú vị nhất. Thay vào đó, mục đích của nó là chứng minh rằng làm việc với các đối tượng chỉ đọc không nhất thiết phải cồng kềnh hay khó khăn.
Lưu ý: Tôi cũng khuyên bạn nên xem phần đầu tiên của loạt bài này, trong đó tôi đã đề cập đến một số khía cạnh của JavaScript giúp đưa các đối tượng gần hơn với các giá trị nguyên thủy, đổi lại cho phép chúng ta hưởng lợi từ các tính năng ngôn ngữ chung thường không liên quan đến một đối tượng, như phép so sánh và toán tử số học.
Dòng đơn này tạo ra một đối tượng có thể biểu diễn bất cứ thứ gì. Ví dụ, bạn có thể triển khai giao diện có tab bằng cách sử dụng một đối tượng rỗng cho mỗi tab.
Nếu bạn giống tôi, phần tử
Điều đó thực hiện những gì chúng ta cần, nhưng nó dài dòng. Cách tiếp cận mà chúng ta sẽ xem xét bây giờ thường được sử dụng để ẩn các hoạt động lặp lại nhằm giảm mã xuống chỉ còn dữ liệu. Theo cách đó, dữ liệu sẽ rõ ràng hơn khi không chính xác. Điều chúng ta cũng muốn là
Để khởi tạo các mảng đối tượng nguyên thủy một cách dễ dàng và nhất quán, tôi sử dụng hàm
Nếu bạn thấy khó hiểu, thì đây là một hàm dễ đọc hơn:
Với hàm như vậy, chúng ta có thể tạo cùng một mảng các đối tượng có tab như sau:
Mỗi mảng trong lệnh gọi thứ hai biểu diễn các giá trị của các đối tượng kết quả. Bây giờ giả sử chúng ta muốn thêm nhiều thuộc tính hơn. Chúng ta cần thêm tên mới vào lệnh gọi đầu tiên và giá trị vào mỗi mảng trong lệnh gọi thứ hai.
Với một số khoảng trắng, bạn có thể làm cho nó trông giống như một bảng. Theo cách đó, sẽ dễ dàng phát hiện lỗi hơn nhiều trong các định nghĩa lớn.
Bạn có thể nhận thấy rằng
Hãy quay lại ví dụ và xem chúng ta đã đạt được gì với hàm
Theo cách này, chúng ta thay thế
Vì đây là logic cho nút radio, nên chỉ có thể chọn một phần tử duy nhất tại một thời điểm. Vì vậy, trước khi đặt một phần tử để chọn, trước tiên chúng ta cần đảm bảo rằng tất cả các phần tử khác đều không được chọn. Đúng, thật ngớ ngẩn khi làm như vậy với một mảng chỉ có hai phần tử, nhưng thế giới thực có rất nhiều danh sách dài hơn ví dụ này.
Với một đối tượng nguyên thủy, chúng ta cần một biến duy nhất biểu diễn trạng thái đã chọn. Tôi đề xuất đặt biến trên một trong các phần tử để biến nó thành phần tử hiện đang được chọn hoặc đặt thành
Với các phần tử có nhiều lựa chọn như hộp kiểm, cách tiếp cận gần như giống nhau. Chúng ta thay thế biến lựa chọn bằng một mảng. Mỗi khi một phần tử được chọn, chúng ta đẩy phần tử đó vào mảng đó hoặc trong trường hợp của Redux, chúng ta tạo một mảng mới có phần tử đó. Để bỏ chọn, chúng ta có thể ghép phần tử đó hoặc lọc phần tử đó ra.
Một lần nữa, điều này rất đơn giản và ngắn gọn. Bạn không cần phải nhớ thuộc tính được gọi là
Cuối cùng, không phải nhiệm vụ của phần tử danh sách là quyết định xem nó có được chọn hay không. Nó không nên giữ thông tin này ở trạng thái của nó. Ví dụ, nếu nó được chọn đồng thời và không được chọn trong nhiều danh sách cùng một lúc thì sao?
Văn bản là một sự đánh đổi tốt cho khả năng tương tác. Bạn định nghĩa một thứ gì đó là một chuỗi và ngay lập tức nhận được biểu diễn của một ngữ cảnh. Giống như việc có được một luồng năng lượng tức thời khi ăn đường. Cũng giống như đường, trường hợp tốt nhất là bạn sẽ không nhận được gì trong thời gian dài. Tuy nhiên, nó không thỏa mãn và bạn chắc chắn sẽ lại đói.
Vấn đề với chuỗi là chúng dành cho con người. Chúng ta thường phân biệt mọi thứ bằng cách đặt tên cho chúng. Nhưng chương trình không hiểu ý nghĩa của những cái tên đó.
Đối tượng cung cấp nhiều cách hơn để xem có điều gì đó không ổn trước khi bạn chạy chương trình. Vì bạn không thể có các giá trị theo nghĩa đen cho các đối tượng nguyên thủy, nên bạn sẽ phải lấy tham chiếu từ đâu đó. Ví dụ, nếu đó là một biến và bạn đánh máy sai, bạn sẽ nhận được lỗi tham chiếu. Có những công cụ có thể phát hiện ra lỗi đó trước khi tệp được lưu.
Nếu bạn lấy các đối tượng của mình từ một mảng hoặc một đối tượng khác, thì JavaScript sẽ không cung cấp cho bạn lỗi khi thuộc tính hoặc chỉ mục không tồn tại. Những gì bạn nhận được là
Một cách sử dụng chuỗi khác mà tôi cố gắng tránh là kiểm tra xem chúng ta có nhận được đối tượng mình muốn hay không. Thông thường, điều này được thực hiện bằng cách lưu trữ một chuỗi trong thuộc tính có tên
Sau đây là cách chúng ta có thể làm tương tự với các đối tượng nguyên thủy:
Lợi ích của chuỗi là chúng là một thứ duy nhất có thể được sử dụng để nhận dạng nội bộ và có thể nhận dạng ngay lập tức trong nhật ký. Chúng chắc chắn dễ sử dụng ngay khi cài đặt, nhưng chúng không phải là bạn của bạn khi độ phức tạp của một dự án tăng lên.
Tôi thấy có rất ít lợi ích khi dựa vào chuỗi cho bất kỳ mục đích nào khác ngoài việc xuất ra cho người dùng. Việc thiếu khả năng tương tác của chuỗi trong các đối tượng nguyên thủy có thể được giải quyết dần dần và không cần phải thay đổi cách bạn xử lý các thao tác cơ bản, như so sánh.
Một điều khác mà bạn có thể bắt đầu đánh giá cao là khả năng dự đoán, nhưng điều đó không được đảm bảo nhiều trong JavaScript. Trong khi các giá trị nguyên thủy đủ khả năng dự đoán, thì các đối tượng thì không. Khi bạn nhận được một đối tượng làm đầu vào, bạn cần kiểm tra mọi thứ:
- Nó có phải là một đối tượng không?
- Nó có thuộc tính mà bạn đang tìm kiếm không?
- Khi một thuộc tính giữ
undefined
, đó có phải là giá trị của nó hay chính thuộc tính đó bị thiếu?
Bằng cách biến các đối tượng thành nguyên thủy, nhiều điểm lỗi tiềm ẩn sẽ được chuyển đến một nơi duy nhất — nơi các đối tượng được khởi tạo. Nếu bạn có thể đảm bảo rằng các đối tượng của mình được khởi tạo bằng một tập hợp các thuộc tính nhất định và các thuộc tính đó giữ các giá trị nhất định, bạn không phải kiểm tra những thứ như sự tồn tại của các thuộc tính ở bất kỳ nơi nào khác trong chương trình của mình. Bạn có thể đảm bảo rằng
undefined
là một giá trị nếu cần.Hãy xem xét một trong những cách chúng ta có thể tạo các đối tượng nguyên thủy. Đây không phải là cách duy nhất hoặc thậm chí là cách thú vị nhất. Thay vào đó, mục đích của nó là chứng minh rằng làm việc với các đối tượng chỉ đọc không nhất thiết phải cồng kềnh hay khó khăn.
Lưu ý: Tôi cũng khuyên bạn nên xem phần đầu tiên của loạt bài này, trong đó tôi đã đề cập đến một số khía cạnh của JavaScript giúp đưa các đối tượng gần hơn với các giá trị nguyên thủy, đổi lại cho phép chúng ta hưởng lợi từ các tính năng ngôn ngữ chung thường không liên quan đến một đối tượng, như phép so sánh và toán tử số học.
Tạo các đối tượng nguyên thủy hàng loạt
Cách đơn giản nhất, nguyên thủy nhất (chơi chữ có chủ ý) để tạo một đối tượng nguyên thủy là như sau:
Mã:
const my_object = Object.freeze({});
Mã:
import React, { useState } from "react";const summary_tab = Object.freeze({});const details_tab = Object.freeze({});function TabbedContainer({ summary_children, details_children }) { const [ active, setActive ] = useState(summary_tab); trả về ( { setActive(summary_tab); }} > Tóm tắt { setActive(details_tab); }} > Chi tiết {active === summary_tab && summary_children} {active === details_tab && details_children} );}export default TabbedContainer;
tabs
đó chỉ cần được làm lại. Nhìn kỹ, bạn sẽ thấy rằng các phần tử tab tương tự nhau và cần hai thứ, chẳng hạn như tham chiếu đối tượng và chuỗi nhãn. Hãy bao gồm thuộc tính label
trong các đối tượng tabs
và di chuyển chính các đối tượng đó vào một mảng. Và vì chúng ta không có kế hoạch thay đổi tabs
theo bất kỳ cách nào, chúng ta cũng hãy biến mảng đó thành chỉ đọc trong khi chúng ta đang thực hiện.
Mã:
const tab_kinds = Object.freeze([ Object.freeze({ label: "Summary" }), Object.freeze({ label: "Details" })]);
đóng băng
các đối tượng (bao gồm cả mảng) theo mặc định thay vì phải nhớ nhập. Vì lý do tương tự, thực tế là chúng ta phải chỉ định tên thuộc tính mỗi lần để lại chỗ cho lỗi, chẳng hạn như lỗi đánh máy.Để khởi tạo các mảng đối tượng nguyên thủy một cách dễ dàng và nhất quán, tôi sử dụng hàm
populate
. Trên thực tế, tôi không có một hàm nào thực hiện được công việc này. Tôi thường tạo một hàm mỗi lần dựa trên những gì tôi cần tại thời điểm đó. Trong trường hợp cụ thể của bài viết này, đây là một trong những hàm đơn giản hơn. Sau đây là cách chúng ta sẽ thực hiện:
Mã:
function populate(...names) { return function(...elements) { return Object.freeze( elements.map(function (values) { return Object.freeze(names.reduce( function (result, name, index) { result[name] = values[index]; return result; }, Object.create(null) )); }) ); };}
Mã:
function populate(...names) { return function(...elements) { const objects = []; elements.forEach(function (values) { const object = Object.create(null); names.forEach(function (name, index) { object[name] = values[index]; }); objects.push(Object.freeze(object)); }); return Object.freeze(objects); };}
Mã:
const tab_kinds = populate( "label")( [ "Summary" ], [ "Details" ]);
Mã:
const tab_kinds = populate( "label", "color", "icon")( [ "Summary", colors.midnight_pink, "💡" ], [ "Details", colors.navi_white, "🔬" ]);
Bạn có thể nhận thấy rằng
populate
trả về một hàm khác. Có một vài lý do để giữ nó trong hai lệnh gọi hàm. Đầu tiên, tôi thích cách hai lệnh gọi liền kề tạo ra một dòng trống phân tách các khóa và giá trị. Thứ hai, tôi thích có thể tạo các loại trình tạo này cho các đối tượng tương tự. Ví dụ, giả sử chúng ta cần tạo các đối tượng nhãn đó cho các thành phần khác nhau và muốn lưu trữ chúng trong các mảng khác nhau.Hãy quay lại ví dụ và xem chúng ta đã đạt được gì với hàm
populate
:
Mã:
import React, { useState } from "react";import populate_label from "./populate_label";const tabs = populate_label( [ "Summary" ], [ "Details" ]);const [ summary_tab, details_tab ] = tabs;function TabbedContainer({ summary_children, details_children }) { const [ active, setActive ] = useState(summary_tab); trả về ( {tabs.map((tab) => ( { setActive(tab); }} > {tab.label} )} {summary_tab === active && summary_children} {details_tab === active && details_children} );}export default TabbedContainer;
Sử dụng các hàm như
populate
ít cồng kềnh hơn khi tạo các đối tượng này và xem dữ liệu trông như thế nào.Kiểm tra Radio đó
Một trong những phương án thay thế cho cách tiếp cận trên mà tôi đã gặp là giữ nguyên Trạng tháiactive
— cho dù tab có được chọn hay không — được lưu trữ dưới dạng thuộc tính của đối tượng tabs
:
Mã:
const tabs = [ { label: "Summary", selected: true }, { label: "Details", selected: false },];
tab === active
bằng tab.selected
. Có vẻ như đây là một cải tiến, nhưng hãy xem cách chúng ta phải thay đổi tab đã chọn:
Mã:
function select_tab(tab, tabs) { tabs.forEach((tab) => tab.selected = false); tab.selected = true;}
Với một đối tượng nguyên thủy, chúng ta cần một biến duy nhất biểu diễn trạng thái đã chọn. Tôi đề xuất đặt biến trên một trong các phần tử để biến nó thành phần tử hiện đang được chọn hoặc đặt thành
undefined
nếu triển khai của bạn cho phép không chọn.Với các phần tử có nhiều lựa chọn như hộp kiểm, cách tiếp cận gần như giống nhau. Chúng ta thay thế biến lựa chọn bằng một mảng. Mỗi khi một phần tử được chọn, chúng ta đẩy phần tử đó vào mảng đó hoặc trong trường hợp của Redux, chúng ta tạo một mảng mới có phần tử đó. Để bỏ chọn, chúng ta có thể ghép phần tử đó hoặc lọc phần tử đó ra.
Mã:
let selected = []; // Không có gì được chọn.// Chọn.selected = selected.concat([ to_be_selected ]);// Bỏ chọn.selected = selected.filter((element) => element !== to_be_unselected);// Kiểm tra xem một phần tử có được chọn không.selected.includes(element);
selected
hay active
; bạn sử dụng chính đối tượng để xác định điều đó. Khi chương trình của bạn trở nên phức tạp hơn, những dòng đó sẽ ít có khả năng được tái cấu trúc nhất.Cuối cùng, không phải nhiệm vụ của phần tử danh sách là quyết định xem nó có được chọn hay không. Nó không nên giữ thông tin này ở trạng thái của nó. Ví dụ, nếu nó được chọn đồng thời và không được chọn trong nhiều danh sách cùng một lúc thì sao?
Alternative To Strings
Điều cuối cùng tôi muốn đề cập đến là một ví dụ về cách sử dụng chuỗi mà tôi thường gặp.Văn bản là một sự đánh đổi tốt cho khả năng tương tác. Bạn định nghĩa một thứ gì đó là một chuỗi và ngay lập tức nhận được biểu diễn của một ngữ cảnh. Giống như việc có được một luồng năng lượng tức thời khi ăn đường. Cũng giống như đường, trường hợp tốt nhất là bạn sẽ không nhận được gì trong thời gian dài. Tuy nhiên, nó không thỏa mãn và bạn chắc chắn sẽ lại đói.
Vấn đề với chuỗi là chúng dành cho con người. Chúng ta thường phân biệt mọi thứ bằng cách đặt tên cho chúng. Nhưng chương trình không hiểu ý nghĩa của những cái tên đó.
Chương trình của bạn chỉ biết liệu hai chuỗi có bằng nhau hay không. Và ngay cả khi đó, việc cho biết liệu các chuỗi có bằng nhau hay không cũng không nhất thiết cung cấp thông tin chi tiết về việc liệu bất kỳ chuỗi nào trong số các chuỗi đó có lỗi đánh máy hay không.
Đối tượng cung cấp nhiều cách hơn để xem có điều gì đó không ổn trước khi bạn chạy chương trình. Vì bạn không thể có các giá trị theo nghĩa đen cho các đối tượng nguyên thủy, nên bạn sẽ phải lấy tham chiếu từ đâu đó. Ví dụ, nếu đó là một biến và bạn đánh máy sai, bạn sẽ nhận được lỗi tham chiếu. Có những công cụ có thể phát hiện ra lỗi đó trước khi tệp được lưu.
Nếu bạn lấy các đối tượng của mình từ một mảng hoặc một đối tượng khác, thì JavaScript sẽ không cung cấp cho bạn lỗi khi thuộc tính hoặc chỉ mục không tồn tại. Những gì bạn nhận được là
undefined
và đó là điều bạn có thể kiểm tra. Bạn chỉ có một điều để kiểm tra. Với chuỗi, bạn có những điều bất ngờ mà bạn có thể muốn tránh, chẳng hạn như khi chúng rỗng.Một cách sử dụng chuỗi khác mà tôi cố gắng tránh là kiểm tra xem chúng ta có nhận được đối tượng mình muốn hay không. Thông thường, điều này được thực hiện bằng cách lưu trữ một chuỗi trong thuộc tính có tên
id
. Ví dụ, giả sử chúng ta có một biến. Để kiểm tra xem nó có chứa đối tượng chúng ta muốn hay không, chúng ta có thể cần kiểm tra xem chuỗi trong thuộc tính id
có khớp với chuỗi mà chúng ta mong đợi hay không. Để thực hiện điều đó, trước tiên chúng ta sẽ kiểm tra xem biến có chứa đối tượng hay không. Nếu biến có chứa đối tượng, nhưng đối tượng không có thuộc tính id
, thì chúng ta sẽ nhận được undefined
và chúng ta ổn. Tuy nhiên, nếu chúng ta có một trong các giá trị dưới cùng trong biến đó, thì chúng ta không thể yêu cầu trực tiếp thuộc tính. Thay vào đó, chúng ta phải làm gì đó để đảm bảo rằng chỉ có các đối tượng đến được điểm này hoặc thực hiện cả hai lần kiểm tra tại chỗ.
Mã:
const myID = "Ồ, thật độc đáo";function magnification(value) { if (value && typeof value === "object" && value.id === myID) { // do magic }}
Mã:
import data from "./the file where data is stored";function magnification(value) { if (value === data.myObject) { // do magic }}
Tôi thấy có rất ít lợi ích khi dựa vào chuỗi cho bất kỳ mục đích nào khác ngoài việc xuất ra cho người dùng. Việc thiếu khả năng tương tác của chuỗi trong các đối tượng nguyên thủy có thể được giải quyết dần dần và không cần phải thay đổi cách bạn xử lý các thao tác cơ bản, như so sánh.
Kết thúc
Làm việc trực tiếp với các đối tượng giúp chúng ta thoát khỏi những cạm bẫy đi kèm với các phương pháp khác. Mã của chúng ta trở nên đơn giản hơn vì chúng ta viết những gì chương trình của bạn cần làm. Bằng cách tổ chức mã của bạn với các đối tượng nguyên thủy, chúng ta ít bị ảnh hưởng bởi bản chất động của JavaScript và một số hành lý của nó. Các đối tượng nguyên thủy cung cấp cho chúng ta nhiều sự đảm bảo hơn và mức độ dự đoán cao hơn.Đọc thêm trên SmashingMag
- JavaScript API mà bạn chưa biết, Juan Diego Rodríguez
- Thư viện lưới dữ liệu JavaScript hữu ích, Zara Cooper
- Tạo thành phần biểu đồ Gantt tương tác bằng JavaScript gốc (Phần 1), Anna Prenzel
- Nhìn vào Remix và sự khác biệt với Next.js, Facundo Giuliani