回调函数是作为参数传递给另一个函数的函数。
// 回调函数:被作为参数传递的函数
function greet(name, callback) {
console.log("你好, " + name);
callback();
}
greet("东巴文", function() {
console.log("欢迎访问 db-w.cn");
});
// 输出:
// 你好, 东巴文
// 欢迎访问 db-w.cn
// 1. 函数作为参数
function processArray(arr, callback) {
const result = [];
for (const item of arr) {
result.push(callback(item));
}
return result;
}
const doubled = processArray([1, 2, 3], function(x) {
return x * 2;
});
console.log(doubled); // [2, 4, 6]
// 2. 可以有返回值
function calculate(a, b, operation) {
return operation(a, b);
}
const sum = calculate(5, 3, (a, b) => a + b);
const product = calculate(5, 3, (a, b) => a * b);
console.log(sum, product); // 8, 15
// 3. 可以是命名函数
function logItem(item) {
console.log(item);
}
[1, 2, 3].forEach(logItem);
回调函数的常见应用场景。
const numbers = [1, 2, 3, 4, 5];
// forEach
numbers.forEach(function(item, index, array) {
console.log(`索引${index}: ${item}`);
});
// map
const doubled = numbers.map(function(item) {
return item * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]
// filter
const evens = numbers.filter(function(item) {
return item % 2 === 0;
});
console.log(evens); // [2, 4]
// reduce
const sum = numbers.reduce(function(acc, item) {
return acc + item;
}, 0);
console.log(sum); // 15
// find
const found = numbers.find(function(item) {
return item > 3;
});
console.log(found); // 4
// some
const hasEven = numbers.some(function(item) {
return item % 2 === 0;
});
console.log(hasEven); // true
// every
const allPositive = numbers.every(function(item) {
return item > 0;
});
console.log(allPositive); // true
// sort
const sorted = [3, 1, 4, 1, 5].sort(function(a, b) {
return a - b;
});
console.log(sorted); // [1, 1, 3, 4, 5]
// DOM事件
document.querySelector("button").addEventListener("click", function(e) {
console.log("按钮被点击");
});
// 定时器回调
setTimeout(function() {
console.log("1秒后执行");
}, 1000);
setInterval(function() {
console.log("每秒执行");
}, 1000);
// 事件监听
const emitter = new EventTarget();
emitter.addEventListener("custom", function(e) {
console.log("自定义事件:", e.detail);
});
// 文件读取(Node.js)
const fs = require("fs");
fs.readFile("file.txt", "utf8", function(err, data) {
if (err) {
console.error("读取失败:", err);
return;
}
console.log("文件内容:", data);
});
// AJAX请求
function fetchData(url, callback) {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = function() {
if (xhr.status === 200) {
callback(null, xhr.responseText);
} else {
callback(new Error("请求失败"));
}
};
xhr.onerror = function() {
callback(new Error("网络错误"));
};
xhr.send();
}
fetchData("/api/data", function(err, data) {
if (err) {
console.error(err);
return;
}
console.log(data);
});
// 带回调的工具函数
function loadData(url, onSuccess, onError) {
fetch(url)
.then(response => response.json())
.then(data => onSuccess(data))
.catch(error => onError(error));
}
loadData(
"/api/users",
function(data) {
console.log("成功:", data);
},
function(error) {
console.error("失败:", error);
}
);
// 多个回调
function processFile(file, options) {
const {
onStart,
onProgress,
onComplete,
onError
} = options;
onStart && onStart(file);
// 模拟处理过程
let progress = 0;
const interval = setInterval(() => {
progress += 10;
onProgress && onProgress(progress);
if (progress >= 100) {
clearInterval(interval);
onComplete && onComplete();
}
}, 100);
}
processFile("data.txt", {
onStart: (file) => console.log("开始处理:", file),
onProgress: (p) => console.log("进度:", p + "%"),
onComplete: () => console.log("处理完成"),
onError: (e) => console.error("错误:", e)
});
多层嵌套回调导致的代码难以维护。
// 典型的回调地狱
getUser(userId, function(user) {
getPosts(user.id, function(posts) {
getComments(posts[0].id, function(comments) {
getAuthor(comments[0].authorId, function(author) {
console.log(author.name);
});
});
});
});
// 实际例子
fs.readFile("config.json", "utf8", function(err, configData) {
if (err) {
console.error(err);
return;
}
const config = JSON.parse(configData);
fs.readFile(config.dataFile, "utf8", function(err, data) {
if (err) {
console.error(err);
return;
}
processData(data, function(err, result) {
if (err) {
console.error(err);
return;
}
fs.writeFile(config.outputFile, result, function(err) {
if (err) {
console.error(err);
return;
}
console.log("处理完成");
});
});
});
});
// 问题1:代码难以阅读
// 金字塔形状的代码结构
// 问题2:错误处理复杂
async1(function(err, result1) {
if (err) return handleError(err);
async2(result1, function(err, result2) {
if (err) return handleError(err);
async3(result2, function(err, result3) {
if (err) return handleError(err);
// 每层都要处理错误
});
});
});
// 问题3:变量作用域混乱
async1(function(result1) {
var a = 1;
async2(function(result2) {
var b = 2;
// a和b都在这里可见
async3(function(result3) {
// 变量作用域难以追踪
});
});
});
// 问题4:难以复用
// 嵌套的回调难以提取和复用
// 方法1:拆分函数
function getAuthorName(userId, callback) {
getUser(userId, function(user) {
getPosts(user.id, function(posts) {
getComments(posts[0].id, function(comments) {
getAuthor(comments[0].authorId, function(author) {
callback(null, author.name);
});
});
});
});
}
// 方法2:命名函数
function step1(callback) {
getUser(userId, callback);
}
function step2(user, callback) {
getPosts(user.id, callback);
}
function step3(posts, callback) {
getComments(posts[0].id, callback);
}
function step4(comments, callback) {
getAuthor(comments[0].authorId, callback);
}
step1(function(user) {
step2(user, function(posts) {
step3(posts, function(comments) {
step4(comments, function(author) {
console.log(author.name);
});
});
});
});
// 方法3:使用Promise(推荐)
getUser(userId)
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0].id))
.then(comments => getAuthor(comments[0].authorId))
.then(author => console.log(author.name))
.catch(error => console.error(error));
// 方法4:使用async/await(最佳)
async function getAuthorName(userId) {
try {
const user = await getUser(userId);
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);
const author = await getAuthor(comments[0].authorId);
return author.name;
} catch (error) {
console.error(error);
}
}
Node.js中的回调约定。
// 错误优先回调:第一个参数是错误,第二个是结果
function readFile(path, callback) {
fs.readFile(path, "utf8", function(err, data) {
if (err) {
callback(err);
return;
}
try {
const result = JSON.parse(data);
callback(null, result);
} catch (e) {
callback(e);
}
});
}
// 使用
readFile("config.json", function(err, data) {
if (err) {
console.error("读取失败:", err);
return;
}
console.log("数据:", data);
});
// 约定:
// 1. 回调函数是最后一个参数
// 2. 错误是回调函数的第一个参数
// 3. 错误为null表示成功
// 正确示例
function asyncOperation(options, callback) {
if (!options) {
callback(new Error("options is required"));
return;
}
// 异步操作
setTimeout(function() {
callback(null, { success: true });
}, 1000);
}
// 使用
asyncOperation({ data: "东巴文" }, function(err, result) {
if (err) {
console.error(err);
return;
}
console.log(result);
});
// 将错误优先回调转为Promise
function promisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn.call(this, ...args, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
};
}
// 使用
const readFileAsync = promisify(fs.readFile);
async function readConfig() {
try {
const data = await readFileAsync("config.json", "utf8");
return JSON.parse(data);
} catch (err) {
console.error(err);
}
}
// Node.js内置promisify
const { promisify } = require("util");
const readFileAsync = promisify(fs.readFile);
掌握了回调函数后,让我们继续学习:
东巴文(db-w.cn) - 让编程学习更简单
🎯 东巴文寄语:回调函数是JavaScript异步编程的基础,但多层嵌套会导致回调地狱。理解回调模式有助于更好地使用Promise和async/await。在 db-w.cn,我们帮你掌握异步编程的精髓!