+1

10 Thủ thuật JavaScript mà bạn ước rằng giá như biết được sớm hơn (P2)

Tiếp nối bài viết Phần 1: 10 Thủ thuật JavaScript mà bạn ước rằng giá như biết được sớm hơn (P1) Chúng ta hãy cùng tiếp tục với những thủ thuật hữu ích khi sử dụng JavaScript nhé!!!

6. Tagged Template Literals - Hỗ trợ tăng tốc chuỗi của bạn

Vấn đề ở đây: Bạn muốn tạo ra khả năng định dạng chuỗi mạnh mẽ và linh hoạt hơn ngoài những gì được cung cấp bởi các ký tự mẫu cơ bản. Bạn có thể cần phân tích cú pháp tùy chỉnh, thoát hoặc chuyển đổi dữ liệu trong cấu trúc chuỗi của mình.

Giải pháp cũ thường làm: Bạn sẽ phải dựa vào sự kết hợp của nối chuỗi, các hàm trợ giúp và logic phức tạp để đạt được kết quả mong muốn. VD:

function highlight(text, name) {
    // Find the index of the placeholder within the text
    const placeholderIndex = text.indexOf("%name%"); 

    if (placeholderIndex !== -1) {
        // Replace the placeholder with the actual name
        return text.substring(0, placeholderIndex) + name + text.substring(placeholderIndex + 6);
      } else {
        return text;
    }
}

const name = "Alice";
const message = highlight("Welcome, %name%!", name);

console.log(message); // "Welcome, Alice!"

Giải pháp mới khuyến khích: Các chuỗi ký tự mẫu được gắn thẻ cho phép bạn xác định các hàm tùy chỉnh (gọi là “hàm thẻ”) có thể xử lý các chuỗi ký tự mẫu trước khi chúng được nội suy. VD:

function highlight(strings, ...values) {
    let result = '';
    for (let i = 0; i < strings.length; i++) {
        result += strings[I];
        if (values[i]) {
          result += `<span class="highlight">${values[i]}</span>`;
        }
    }
    return result;
}

const name = "Alice";
const message = highlight`Welcome, ${name}!`;

console.log(message); // "Welcome, <span class="highlight">Alice</span>!"
  • Đối với cách làm cũ: Tôi dựa vào một hàm riêng biệt (highlight) để lấy văn bản và giá trị cần chèn làm đối số riêng biệt. Tôi cũng đã tìm kiếm thủ công một trình giữ chỗ (%name%) và thay thế nó. Cách tiếp cận này vì thế mà trở nên kém linh hoạt hơn, dễ xảy ra lỗi hơn (nếu trình giữ chỗ sai thì sao?) và không mở rộng tốt cho định dạng phức tạp hơn.
  • Đối với cách làm mới: Với các ký tự mẫu được gắn thẻ, hàm highlight nhận các phần chuỗi và các giá trị nội suy dưới dạng các đối số riêng biệt. Điều này cho phép bạn có thể thao tác và chuyển đổi chuỗi sạch hơn nhiều dựa trên cấu trúc của nó và các giá trị được cung cấp. Vô cùng tiện phải không nào?

Ứng dụng thực tế:

  • Tạo ngôn ngữ dành riêng cho miền (DSL): Xây dựng công cụ tạo mẫu tùy chỉnh, trình xây dựng truy vấn hoặc thậm chí là ngôn ngữ nhỏ trong mã JavaScript của bạn.
  • Quốc tế hóa (i18n): Xử lý bản dịch và định dạng chuỗi bản địa hóa dựa trên tùy chọn của người dùng.
  • Bảo mật: Triển khai cơ chế khử trùng và thoát mạnh mẽ cho nội dung do người dùng tạo trong chuỗi.

7. Sử dụng các Proxy Objects - Giúp chặn và kiểm soát tốt hơn

Vấn đề ở đây: Bạn cần kiểm soát chi tiết các hoạt động của các đối tượng, chẳng hạn như truy cập thuộc tính, gán, gọi hàm hoặc thậm chí là xây dựng đối tượng. Bạn có thể muốn triển khai xác thực tùy chỉnh, ghi nhật ký hoặc thậm chí sửa đổi hành vi của các đối tượng hiện có mà không cần thay đổi trực tiếp mã của chúng.

Giải pháp cũ thường làm: Bạn thường dùng đến:

  • Hàm bao bọc: Tạo các hàm đóng gói các tương tác của đối tượng, tăng thêm chi phí và có khả năng làm mờ giao diện của đối tượng cơ bản.
  • Ghi đè phương thức: Sửa đổi nguyên mẫu đối tượng, có thể dẫn đến các tác dụng phụ và xung đột không mong muốn, đặc biệt là trong các cơ sở mã lớn hơn. VD:
const user = {
    name: "Alice",
    age: 30,
};

function validateAge(age) {
    if (age < 0 || age > 120) {
        throw new Error("Invalid age value!");
    }
      return age;
}

// Using a wrapper function to enforce validation
function setUserAge(user, newAge) {
    user.age = validateAge(newAge);
}

setUserAge(user, 35); // Works
setUserAge(user, -5); // Throws an error

Giải pháp khuyến khích nên làm: Sử dụng Proxy Objects hoạt động như trung gian, chặn các hoạt động cơ bản trên một đối tượng và cung cấp cho bạn khả năng tùy chỉnh cách xử lý các hoạt động đó. VD:

const user = {
    name: "Alice",
    age: 30,
};

const userProxy = new Proxy(user, {
    set: function (target, property, value) {
        if (property === "age") {
          if (value < 0 || value > 120) {
            throw new Error("Invalid age value!");
          }
        }
        // Update the original object's property
        target[property] = value;
        return true; // Indicate success
    },
});

userProxy.age = 35; // Works
userProxy.age = -5; // Throws an error

Chúng ta tạo một Proxy object, truyền vào đối tượng mục tiêu (người dùng) và một đối tượng xử lý.

Đối tượng xử lý định nghĩa “traps” cho nhiều hoạt động khác nhau. Trong trường hợp này, chúng ta sử dụng set trap để chặn các phép gán thuộc tính.

Bên trong set trap, tôi thực hiện xác thực tùy chỉnh cho thuộc tính tuổi.

Nếu xác thực thành công, chúng ta sẽ cập nhật thuộc tính của đối tượng gốc bằng cách sử dụng target[property] = value.

Ứng dụng thực tế:

  • Xác thực và vệ sinh dữ liệu: Thực thi các quy tắc toàn vẹn dữ liệu trước khi lưu đối tượng vào cơ sở dữ liệu hoặc gửi chúng qua mạng.
  • Theo dõi thay đổi: Ghi lại hoặc phản ứng với những thay đổi được thực hiện đối với thuộc tính của đối tượng.
  • Tải chậm: Hoãn tải các thuộc tính đối tượng tốn kém cho đến khi chúng thực sự được truy cập.

8. Sức mạnh to lớn của reduce() - Vượt xa các phép tính tổng mảng đơn giản

Vấn đề ở đây: Bạn cần thực hiện các phép biến đổi hoặc tính toán phức tạp trên mảng, vượt ra ngoài phạm vi tổng hợp đơn giản như tìm tổng hoặc giá trị lớn nhất.

Giải pháp cũ thường làm: Bạn có thể dùng đến:

  • Vòng lặp bắt buộc: Viết các vòng lặp for hoặc while dài dòng, thường có logic lồng nhau và các biến tạm thời, khiến mã khó đọc và bảo trì hơn.
  • Các hàm chuyên biệt: Tạo các hàm riêng biệt cho mỗi phép biến đổi mảng cụ thể, dẫn đến trùng lặp mã. VD:
const orders = [
    { product: "Shirt", quantity: 2, price: 15 },
    { product: "Shoes", quantity: 1, price: 50 },
    { product: "Hat", quantity: 3, price: 10 },
];

// Calculate the total value of all orders (imperative approach)
let totalValue = 0;
for (let i = 0; i < orders.length; i++) {
    totalValue += orders[i].quantity * orders[i].price;
}

console.log(totalValue); // Output: 110

Giải pháp mới nên khuyến khích: Phương thức reduce() cung cấp một cách linh hoạt để lặp qua một mảng và "giảm" nó thành một giá trị duy nhất, áp dụng hàm gọi lại cho từng phần tử và tích lũy kết quả.

const orders = [
    { product: "Shirt", quantity: 2, price: 15 },
    { product: "Shoes", quantity: 1, price: 50 },
    { product: "Hat", quantity: 3, price: 10 },
];

// Calculate the total value of all orders using reduce
const totalValue = orders.reduce((accumulator, order) => {
    return accumulator + order.quantity * order.price;
}, 0); // Initial value of the accumulator

console.log(totalValue); // Output: 110

reduce() sử dụng hai đối số: một hàm gọi lại và một giá trị khởi tạo tùy chọn cho bộ tích lũy.

Hàm gọi lại nhận giá trị tích lũy (bắt đầu bằng giá trị ban đầu hoặc phần tử đầu tiên) và phần tử hiện tại.

Ở mỗi lần lặp lại, lệnh gọi lại trả về bộ tích lũy đã cập nhật, sau đó được chuyển sang lần lặp lại tiếp theo.

Giá trị cuối cùng được trả về bởi reduce() là kết quả tích lũy.

Ứng dụng thực tế:

  1. Nhóm dữ liệu: Chuyển đổi một mảng đối tượng thành một đối tượng được nhóm dựa trên một thuộc tính cụ thể.
const products = [
    { name: "Apple", category: "Fruit" },
    { name: "Banana", category: "Fruit" },
    { name: "Carrot", category: "Vegetable" },
];

const groupedProducts = products.reduce((groups, product) => {
    const category = product.category;
    if (!groups[category]) {
        groups[category] = [];
    }
    groups[category].push(product);
    return groups;
}, {});

console.log(groupedProducts); 
// Output: { Fruit: [{...}, {...}], Vegetable: [{...}] }
  1. Làm phẳng mảng: Gộp các mảng lồng nhau thành một mảng phẳng duy nhất.
const nestedArray = [1, [2, 3], [4, [5, 6]]];

const flatArray = nestedArray.reduce(
     (acc, current) => acc.concat(Array.isArray(current) ? current.flat() : current),[]);

console.log(flatArray); // Output: [1, 2, 3, 4, 5, 6]
  1. Tạo danh sách duy nhất: Trích xuất các giá trị duy nhất từ ​​một mảng.
const numbers = [1, 2, 2, 3, 4, 4, 5];

const uniqueNumbers = numbers.reduce((unique, number) => {
      return unique.includes(number) ? unique : [...unique, number];
}, []);

console.log(uniqueNumbers); // Output: [1, 2, 3, 4, 5]

Thành thạo reduce() sẽ mở ra một cấp độ thao tác mảng cao hơn, cho phép bạn thể hiện các phép biến đổi phức tạp một cách ngắn gọn và tinh tế.

9. Spread Syntax cho việc thao tác mảng và đối tượng dễ dàng

Vấn đề ở đây: Bạn cần sao chép các mảng, kết hợp chúng hoặc chèn các phần tử vào các vị trí cụ thể. Tương tự, bạn có thể muốn tạo các bản sao của các đối tượng có thuộc tính đã sửa đổi. Thực hiện thủ công có thể rất tẻ nhạt và liên quan đến các vòng lặp hoặc nhiều dòng mã.

Giải pháp cũ thường làm: Bạn sẽ sử dụng kết hợp slice(), concat() hoặc Object.assign() cho các tác vụ sau:

  1. Mảng
const numbers1 = [1, 2, 3];
const numbers2 = [4, 5, 6];

// Concatenating arrays
const combinedArray = numbers1.concat(numbers2); 

// Inserting the number 0 at index 2 (the old way)
const newArray = numbers1.slice(0, 2).concat([0], numbers1.slice(2));
  1. Đối tượng
const product = {
    name: "Phone",
    price: 499,
};

// Creating a modified copy
const updatedProduct = Object.assign({}, product, { price: 599 });

Giải pháp mới nên làm: Cú pháp lan truyền (...) cung cấp một cách ngắn gọn và linh hoạt hơn để làm việc với mảng và đối tượng:

  1. Mảng
const numbers1 = [1, 2, 3];
const numbers2 = [4, 5, 6];

// Concatenating arrays
const combinedArray = [...numbers1, ...numbers2];

// Inserting an element
const newArray = [...numbers1.slice(0, 2), 0, ...numbers1.slice(2)];
  1. Đối tượng
const product = {
     name: "Phone",
     price: 499,
};

// Creating a modified copy
const updatedProduct = { ...product, price: 599 };

Cú pháp lan truyền với mảng: Khi sử dụng với mảng, ... sẽ mở rộng các phần tử của mảng tại chỗ.

Cú pháp lan truyền với các đối tượng: Khi sử dụng với các đối tượng, ... sẽ mở rộng các cặp khóa-giá trị của một đối tượng.

Tại sao nó lại trở nên dễ hơn:

  • Tính súc tích: Cú pháp lan truyền làm giảm đáng kể mã cần thiết cho các hoạt động mảng và đối tượng phổ biến.
  • Khả năng đọc: Mã trở nên rõ ràng hơn và dễ hiểu hơn.

Ứng dụng thực tế: Sửa đổi trạng thái trong React: Cú pháp Spread được sử dụng rộng rãi trong React và các thư viện UI khác để tạo các bản sao cập nhật của các đối tượng trạng thái mà không làm thay đổi trạng thái ban đầu:

// Example in a React component
this.setState(prevState => ({
    ...prevState,
    cartItems: [...prevState.cartItems, newItem], 
}));

Cú pháp Spread là một công cụ đa năng giúp đơn giản hóa thao tác với mảng và đối tượng, giúp mã của bạn ngắn gọn, dễ đọc và dễ bảo trì hơn.

10. Hàm Arrow - Cú pháp ngắn gọn, xúc tích cho hàm

Vấn đề ở đây: Bạn thường cần phải viết các hàm ẩn danh ngắn cho trình xử lý sự kiện, lệnh gọi lại hoặc phương thức mảng, nhưng cú pháp hàm truyền thống có vẻ hơi dài dòng trong những trường hợp này.

Giải pháp cũ thường làm: Bạn sẽ sử dụng từ khóa function để xác định các hàm ẩn danh: VD:

// Example with an array method
const numbers = [1, 2, 3, 4, 5];

const doubledNumbers = numbers.map(function(number) {
    return number * 2;
});

console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]

Giải pháp mới nên khuyến khích: Các hàm Arrow (=>) cung cấp cú pháp gọn hơn để viết hàm, đặc biệt đối với các thân hàm ngắn:

const numbers = [1, 2, 3, 4, 5];

const doubledNumbers = numbers.map((number) => number * 2);

console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]

Cú pháp: Hàm Arrow được định nghĩa bằng dấu ngoặc đơn cho các tham số (hoặc một tham số duy nhất không có dấu ngoặc đơn), theo sau là mũi tên (=>) và sau đó là thân hàm.

Trả về ngầm định: Nếu thân hàm chứa một biểu thức duy nhất, kết quả của biểu thức đó sẽ được trả về ngầm định mà không cần từ khóa return.

Tại sao nó lại trở nên dễ hơn:

  • Cú pháp ngắn hơn: Các hàm Arrow làm giảm đáng kể mã cần thiết để xác định các hàm đơn giản.
  • Cải thiện khả năng đọc: Mã trở nên ngắn gọn hơn và dễ theo dõi hơn, đặc biệt khi sử dụng với các phương thức mảng.

Ứng dụng thực tế:

Trình xử lý sự kiện: Các hàm Arrow rất phổ biến khi đính kèm trình xử lý sự kiện:

const button = document.getElementById("myButton");

button.addEventListener("click", () => {
    console.log("Button clicked!"); 
});

Kết luận

Những mẹo nhỏ trên hy vọng sẽ giúp ích rất nhiều cho các bạn trong việc khám phá thế giới rộng lớn của JavaScript nói chung. Đừng bao giờ sợ hãi, hãy dám thử nghiệm để có thể tìm ra được phương pháp phù hợp với bản thân nhất. Cảm ơn các bạn đã theo dõi bài viết.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí