Hàm bậc cao
Hàm bậc cao là những hàm có khả năng nhận một hàm khác làm đối số hoặc trả về một hàm khác. Trong JavaScript, hàm được coi là một kiểu dữ liệu, vì vậy chúng có thể được truyền như một tham số hoặc được trả về từ một hàm khác. Đây là một đặc tính cốt lõi của JavaScript, cho phép các mẫu thiết kế như hàm callback và hàm higher-order trở nên phổ biến.
Ví dụ, một hàm có thể nhận một hàm callback để thực hiện một hành động sau khi một tác vụ nhất định hoàn thành.
function processArray(arr, callback) {
const result = [];
for (let i = 0; i < arr.length; i++) {
result.push(callback(arr[i]));
}
return result;
}
const numbers = [1, 2, 3, 4];
const doubled = processArray(numbers, function(item) {
return item * 2;
});
console.log(doubled); // [2, 4, 6, 8]
Đóng gói (Closure)
Đóng gói (closure) là một hàm có khả năng truy cập vào các biến trong phạm vi của một hàm khác, ngay cả sau khi hàm bên ngoài đã hoàn thành việc thực thi. Nói cách khác, hàm bên trong "ghi nhớ" môi trường nơi nó được tạo ra.
Closure rất hữu ích để bảo vệ dữ liệu và tạo ra các hàm có trạng thái riêng.
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter1 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
const counter2 = createCounter();
console.log(counter2()); // 1
Đệ quy (Recursion)
Đệ quy là một kỹ thuật trong đó một hàm gọi chính nó để giải quyết một vấn đề. Để tránh vòng lặp vô hạn, hàm đệ quy phải có một điều kiện dừng (base case).
Ví dụ, chúng ta có thể sử dụng đệ quy để tính giai thừa của một số.
function factorial(n) {
// Điều kiện dừng
if (n === 0 || n === 1) {
return 1;
}
// Gọi chính nó
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
Sao chép nông (Shallow Copy)
Sao chép nông tạo ra một đối tượng mới, nhưng nó chỉ sao chép các giá trị ở cấp độ đầu tiên. Nếu một thuộc tính là một đối tượng hoặc mảng, nó sẽ sao chép tham chiếu đến đối tượng đó, không phải bản sao của nó. Điều này có nghĩa là thay đổi đối tượng lồng nhau trong bản sao sẽ ảnh hưởng đến đối tượng gốc.
Trong ES6, `Object.assign()` có thể được sử dụng để thực hiện sao chép nông.
const original = {
id: 101,
info: {
name: 'Alice',
age: 30
}
};
const shallowCopy = Object.assign({}, original);
// Thay đổi thuộc tính của đối tượng lồng nhau
shallowCopy.info.age = 31;
console.log(original.info.age); // 31 (đã bị thay đổi)
Sao chép sâu (Deep Copy)
Sao chép sâu tạo ra một bản sao hoàn toàn độc lập của một đối tượng, bao gồm cả tất cả các đối tượng lồng nhau. Thay đổi bất kỳ thuộc tính nào trong bản sao sẽ không ảnh hưởng đến đối tượng gốc.
Phương pháp 1: Sử dụng đệ quy
Chúng ta có thể viết một hàm đệ quy để sao chép sâu một cách thủ công.
function deepClone(source, target = {}) {
for (const key in source) {
const value = source[key];
if (Array.isArray(value)) {
target[key] = [];
deepClone(value, target[key]);
} else if (typeof value === 'object' && value !== null) {
target[key] = {};
deepClone(value, target[key]);
} else {
target[key] = value;
}
}
return target;
}
const originalObj = {
a: 1,
b: { c: 2, d: [3, 4] }
};
const deepCopiedObj = deepClone(originalObj);
deepCopiedObj.b.d.push(5);
console.log(originalObj.b.d); // [3, 4]
console.log(deepCopiedObj.b.d); // [3, 4, 5]
Phương pháp 2: Chuyển đổi JSON
Cách đơn giản là chuyển đổi đối tượng thành chuỗi JSON và sau đó phân tích cú pháp chuỗi đó trở lại thành một đối tượng.
const original = {
id: 202,
details: {
role: 'Developer'
}
};
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.details.role = 'Designer';
console.log(original.details.role); // 'Developer'
console.log(deepCopy.details.role); // 'Designer'
Lưu ý: Phương pháp JSON sẽ không sao chép các hàm, `undefined`, hoặc các thuộc tính Symbol.
Rò rỉ bộ nhớ (Memory Leak)
Rò rỉ bộ nhớ xảy ra khi bộ nhớ được phân bổ động không được giải phóng dù không còn được sử dụng, dẫn đến việc ứng dụng chiếm dụng bộ nhớ không cần thiết và có thể làm chậm hoặc sập ứng dụng.
Các nguyên nhân phổ biến
- Biến toàn cục không mong muốn: Khai báo biến mà không dùng `let`, `const`, hoặc `var` sẽ tạo ra một thuộc tính trên đối tượng `window`.
function leakGlobal() {
leakedVar = 'This will leak'; // Tạo biến toàn cục
}
leakGlobal();
const timerId = setInterval(() => {
console.log('This will run forever if not cleared');
}, 1000);
// Để ngăn rò rỉ, hãy gọi:
// clearInterval(timerId);
const element = document.getElementById('some-element');
document.body.removeChild(element);
// 'element' vẫn giữ tham chiếu đến nút, gây rò rỉ bộ nhớ
function createClosure() {
const largeObject = new Array(1000000).fill('data');
return function() {
console.log(largeObject); // largeObject sẽ không được giải phóng
};
}
const myClosure = createClosure();
Hết bộ nhớ (Out of Memory)
Hết bộ nhớ xảy ra khi một chương trình yêu cầu nhiều bộ nhớ hơn so với lượng bộ nhớ có sẵn trong hệ thống. Đây là một lỗi nghiêm trọng thường dẫn đến việc ứng dụng bị sập.
Hàm là công dân hạng nhất (First-class functions)
Trong JavaScript, hàm được coi là "công dân hạng nhất", có nghĩa là chúng có thể được xử lý như bất kỳ giá trị nào khác. Chúng có thể được gán cho biến, truyền như một đối số cho một hàm khác, và được trả về từ một hàm.
// Gán cho một biến
const greet = function(name) {
return `Hello, ${name}`;
};
// Truyền như một đối số
function execute(fn, value) {
return fn(value);
}
console.log(execute(greet, 'World')); // "Hello, World"
// Trả về từ một hàm
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
console.log(double(5)); // 10
Hàm thuần túy (Pure functions)
Hàm thuần túy là hàm luôn trả về cùng một kết quả cho cùng một đầu vào và không có tác dụng phụ (side effects). Chúng không thay đổi trạng thái bên ngoài và chỉ phụ thuộc vào các đối số đầu vào.
// Đây là một hàm thuần túy
function add(a, b) {
return a + b;
}
// Kết quả luôn giống nhau cho cùng các đầu vào
console.log(add(2, 3)); // 5
console.log(add(2, 3)); // 5
// Hàm này không phải là hàm thuần túy vì nó thay đổi một biến bên ngoài
let total = 0;
function addToTotal(value) {
total += value;
return total;
}
Khuếch đại (Currying)
Khuếch đại là một kỹ thuật biến đổi một hàm có nhiều đối số thành một chuỗi các hàm, mỗi hàm nhận một đối số duy nhất. Hàm đầu tiên nhận đối số đầu tiên và trả về một hàm mới, hàm đó nhận đối số thứ hai và trả về một hàm khác, v.v., cho đến khi tất cả các đối số được cung cấp.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
console.log(curriedMultiply(2, 3, 4)); // 24