+4

10 mẹo vặt JavaScript hữu ích cho lập trình viên

Bài viết này sẽ giới thiệu 10 mẹo và thủ thuật JavaScript đơn giản nhưng hiệu quả, giúp bạn viết code dễ hiểu và tối ưu hơn. Có thể bạn đã biết một vài trong số chúng, hoặc thậm chí là tất cả, nhưng hãy cùng ôn lại và khám phá những ứng dụng thú vị của chúng.

1. Giải cấu trúc (Destructuring) cho code gọn gàng hơn

Giải cấu trúc là một cú pháp cho phép bạn giải nén các giá trị từ mảng hoặc thuộc tính từ đối tượng thành các biến. Điều này làm giảm mã lặp lại và cải thiện khả năng đọc.

// Before destructuring
const user = { name: "Alice", age: 25 };
const name = user.name;
const age = user.age;

// Using destructuring
const { name, age } = user;
console.log(name, age); // Output: Alice 25

Nếu không giải cấu trúc, bạn phải tham chiếu rõ ràng từng thuộc tính, dẫn đến mã dài dòng và lặp lại. Với giải cấu trúc, bạn trích xuất name và age trực tiếp từ đối tượng user trong một câu lệnh duy nhất. Điều này đặc biệt hữu ích khi xử lý các đối tượng hoặc mảng lớn, vì nó đơn giản hóa quá trình gán.

2. Chuỗi tùy chọn (Optional Chaining) cho truy cập an toàn hơn

Chuỗi tùy chọn ?. cho phép bạn truy cập các thuộc tính lồng sâu mà không phải lo lắng về lỗi nếu một thuộc tính không tồn tại. Thay vì đưa ra lỗi, nó trả về undefined.

const user = { profile: { email: "user@example.com" } };

console.log(user?.profile?.email); // Output: user@example.com
console.log(user?.settings?.theme); // Output: undefined (no error)

Truy cập thuộc tính truyền thống sẽ yêu cầu kiểm tra như if (user && user.profile). Chuỗi tùy chọn loại bỏ sự phức tạp này, đảm bảo mã của bạn không bị lỗi nếu một thuộc tính trung gian (settings trong trường hợp này) là undefined hoặc null. Điều này giúp dễ dàng làm việc với dữ liệu không thể đoán trước hoặc không đầy đủ, đặc biệt là từ các API.

3. Tham số mặc định (Default Parameters)

Tham số mặc định cho phép bạn đặt giá trị mặc định cho các đối số hàm. Điều này đảm bảo rằng hàm của bạn hoạt động như dự đoán khi được gọi mà không có các tham số nhất định.

function greet(name = "Guest") {
  return `Hello, ${name}!`;
}

console.log(greet()); // Output: Hello, Guest!
console.log(greet("Alice")); // Output: Hello, Alice!

Trong ví dụ này, hàm greet sử dụng giá trị mặc định là "Guest" cho tham số name. Nếu không có đối số nào được truyền vào, nó sẽ sử dụng giá trị mặc định. Điều này đặc biệt hữu ích để xử lý các đầu vào tùy chọn, giảm nhu cầu kiểm tra thủ công hoặc logic dự phòng bên trong hàm.

4. Phương thức mảng: Map, Filter và Reduce

Các phương thức này cung cấp các cách khai báo mạnh mẽ để thao tác mảng mà không cần các vòng lặp truyền thống.

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

// Transform each element with map
const doubled = numbers.map(num => num * 2); // [2, 4, 6, 8]

// Filter elements based on a condition
const evens = numbers.filter(num => num % 2 === 0); // [2, 4]

// Aggregate values with reduce
const sum = numbers.reduce((acc, num) => acc + num, 0); // 10

Trong đó:

  • map() xử lý từng phần tử và trả về một mảng mới, rất phù hợp cho các phép biến đổi.
  • filter() tạo một tập hợp con của mảng dựa trên một điều kiện, hữu ích để loại bỏ các phần tử không mong muốn.
  • reduce() kết hợp tất cả các phần tử thành một giá trị duy nhất, làm cho nó lý tưởng cho các tác vụ như tính tổng hoặc xây dựng đối tượng.

Các phương thức này cho phép code rõ ràng hơn, biểu cảm hơn so với việc lặp lại thủ công trên mảng.

5. Template Literals

Template literals sử dụng dấu backtick (`) và cho phép chuỗi nhiều dòng và nội suy biến inline, giúp thao tác chuỗi thuận tiện hơn.

const name = "Alice";
const greeting = `Hello, ${name}!
Welcome to JavaScript tips!`;

console.log(greeting);

Việc nối chuỗi truyền thống "Hello, " + name + "! là cồng kềnh và dễ bị lỗi. Template literals giúp dễ dàng nhúng các biến ($ {name}) trực tiếp vào chuỗi và định dạng văn bản nhiều dòng mà không cần các ký tự thoát \n. Điều này cải thiện khả năng đọc, đặc biệt là đối với các chuỗi động hoặc phức tạp.

6. Toán tử Spread (...)

Toán tử spread ... là một công cụ linh hoạt để sao chép, kết hợp hoặc mở rộng mảng và đối tượng.

const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4]

const user = { name: "Alice", age: 25 };
const updatedUser = { ...user, age: 26 }; // { name: "Alice", age: 26 }

Trong đó:

  • Đối với mảng, toán tử spread hợp nhất arr1 và arr2 thành một mảng mới.
  • Đối với đối tượng, nó tạo một bản sao nông của user trong khi ghi đè thuộc tính age.

Điều này tránh việc thay đổi (sửa đổi trực tiếp) cấu trúc dữ liệu ban đầu, đây là một cách thực hành tốt nhất để duy trì code sạch sẽ và có thể dự đoán được.

7. Đánh giá ngắn mạch (Short-Circuit Evaluation)

Các toán tử logic && và || có thể đơn giản hóa các biểu thức điều kiện bằng cách tận dụng hành vi ngắn mạch của chúng.

const isLoggedIn = true;
const welcomeMessage = isLoggedIn && "Welcome back!";
console.log(welcomeMessage); // Output: Welcome back!

const username = null;
const displayName = username || "Guest";
console.log(displayName); // Output: Guest

Trong đó:

  • && trả về giá trị thứ hai nếu giá trị đầu tiên là true. Nếu isLoggedIn là true, nó sẽ đánh giá và trả về "Welcome back!".
  • || trả về giá trị truthy đầu tiên. Nếu username là null, nó sẽ trả về "Guest".

Các phím tắt này loại bỏ nhu cầu sử dụng câu lệnh if trong các phép gán điều kiện đơn giản.

8. Debounce và Throttle

Debounce và throttle kiểm soát tần suất thực thi của một hàm trong các sự kiện diễn ra nhanh chóng như gõ phím hoặc cuộn trang.

function debounce(func, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => func(...args), delay);
  };
}

function throttle(func, interval) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      func(...args);
    }
  };
}

Trong đó:

  • Debounce đảm bảo hàm chỉ chạy sau khi người dùng ngừng kích hoạt nó trong một khoảng thời gian trễ nhất định. Hữu ích cho các input tìm kiếm.
  • Throttle giới hạn việc thực thi xuống một lần mỗi khoảng thời gian, bất kể sự kiện được kích hoạt bao nhiêu lần. Lý tưởng cho trình lắng nghe cuộn.

Các kỹ thuật này tối ưu hóa hiệu suất và ngăn chặn việc gọi hàm quá mức. Giả sử debounce rất phù hợp cho các trường hợp khi làm việc với input, bởi vì mỗi khi một ký tự được nhập, một yêu cầu sẽ được gửi đến máy chủ, trong khi throttle phù hợp để làm việc với việc cuộn hoặc di chuyển con trỏ trên màn hình.

9. Promise.all cho các hoạt động đồng thời

Promise.all cho phép bạn chạy nhiều promise đồng thời và đợi tất cả chúng được giải quyết.

const fetchData1 = fetch("/api/data1");
const fetchData2 = fetch("/api/data2");

Promise.all([fetchData1, fetchData2])
  .then(responses => Promise.all(responses.map(res => res.json())))
  .then(data => console.log(data))
  .catch(error => console.error(error));

Bằng cách kết hợp các promise, Promise.all đảm bảo rằng tất cả các tác vụ không đồng bộ hoàn thành trước khi tiếp tục. Trong ví dụ này, cả hai lệnh gọi API đều chạy song song và kết quả được xử lý cùng nhau, tiết kiệm thời gian so với các yêu cầu tuần tự.

10. Hàm mũi tên (Arrow Functions) và This

Hàm mũi tên không ràng buộc this của riêng chúng; chúng kế thừa nó từ phạm vi xung quanh.

function Timer() {
  this.seconds = 0;

  setInterval(() => {
    this.seconds++;
    console.log(this.seconds);
  }, 1000);
}

const timer = new Timer();

Trong ví dụ Timer, việc sử dụng hàm mũi tên bên trong setInterval đảm bảo rằng this tham chiếu đến instance Timer. Các hàm thông thường sẽ liên kết this một cách linh hoạt với phạm vi toàn cục hoặc ngữ cảnh gọi, dẫn đến lỗi. Hàm mũi tên đơn giản hóa việc xử lý this, làm cho chúng lý tưởng cho các hàm callback.

Hy vọng các mẹo vặt này sẽ giúp ích cho các bạn!


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.