JavaScript(JS)中回调函数的学习笔记

回调函数JavaScript 中一项核心且强大的机制,它允许我们将一个函数作为参数传递给另一个函数,并在特定时机(通常是异步操作完成后)执行它。其精髓在于控制反转——我们调用一个外部函数,并委托它在未来某个时刻回调我们的逻辑。

核心原理:一个生活化的比喻

想象这样一个场景:“我现在出发,到达目的地后通知你。”

  • “我出发”:这是你调用的主函数(如 doSomething),它开始执行一个可能耗时的任务(如网络请求、文件读取)。
  • “你在此期间”:调用者(主程序)无需阻塞等待,可以继续执行其他代码。
  • “到达后通知你”:当主函数完成任务(或到达某个节点)时,它会调用你预先提供的那回调函数,并可能将结果(如数据、状态)作为参数传递进去,从而继续后续流程。

这完美诠释了异步非阻塞的编程模型。


一、基础用法:传递与执行

回调最基本的形式就是将一个函数(命名函数或匿名函数)作为参数传入。

1. 传递命名函数

javascript
function doSomething(callback) {
    // ... 执行某些操作
    // 在适当时机调用回调,并传递参数
    callback('stuff', 'goes', 'here');
}

function foo(a, b, c) {
    alert(a + " " + b + " " + c);
}

doSomething(foo); // 将 foo 函数作为参数传入

2. 使用匿名函数(更常见)

javascript
function doSomething(msg, callback) {
    alert(msg);
    // 安全地检查回调是否为函数
    if (typeof callback === 'function') {
        callback();
    }
}

doSomething("回调函数示例", function() {
    alert("执行完成!");
});

二、进阶控制:call / apply 与参数传递

有时我们需要在回调执行时,控制其内部的 this 指向,或动态传递一系列参数,此时 callapply 就派上用场了。

1. 使用 call 绑定 this 并传参

javascript
function Thing(name) {
    this.name = name;
}
Thing.prototype.doSomething = function(callback) {
    // 使用 call 调用回调,并将当前实例 (this) 作为回调的 this 值
    callback.call(this);
};

function foo() {
    alert(this.name); // 这里的 this 指向 Thing 实例
}

var t = new Thing('Joe');
t.doSomething(foo); // 弹出 "Joe"

2. 使用 call 传递多个独立参数

javascript
Thing.prototype.doSomething = function(callback, salutation) {
    callback.call(this, salutation); // 将 salutation 作为第一个参数传给回调
};

function foo(salutation) {
    alert(salutation + " " + this.name);
}

t.doSomething(foo, 'Hi'); // 弹出 "Hi Joe"

3. 使用 apply 传递数组形式的参数

javascript
Thing.prototype.doSomething = function(callback) {
    // apply 第二个参数必须是数组,数组元素将逐个作为回调的参数
    callback.apply(this, ['Hi', 3, 2, 1]);
};

function foo(salutation, three, two, one) {
    alert(salutation + " " + this.name + " – " + three + " " + two + " " + one);
}

t.doSomething(foo); // 弹出 "Hi Joe – 3 2 1"

三、实战案例:条件分支中的回调

这个例子清晰地展示了回调如何用于分离关注点处理异步分支

1. 底层逻辑 (1.js)

javascript
function evaluateScore(num, callback) {
    if (num < 0) {
        alert("底层处理:分数不能为负,输入错误!");
    } else if (num === 0) {
        alert("底层处理:该学生可能未参加考试!");
    } else {
        alert("底层处理完成,移交高层处理。");
        callback(); // 执行高层回调
    }
}

2. 页面与调用 (test.html)

html
<!DOCTYPE html>
<html>
<head>
    <script src="1.js"></script>
    <script>
        function test() {
            var num = document.getElementById("score").value;
            var resultP = document.getElementById("result");

            evaluateScore(num, function() {
                // 这是“高层处理”的回调函数,仅对 num > 0 的情况有效
                if (num < 60) alert("未及格!");
                else if (num <= 90) alert("该生成绩优良!");
                else alert("该生成绩优秀!");
            });
            resultP.innerText = "by since1978 qq558064!";
        }
    </script>
</head>
<body>
    <p>回调示例:当成绩 ≤ 0 分时由底层处理;当成绩 > 0 时,由高层回调处理。</p>
    请输入学生成绩:<input type="text" id="score">
    <input type="button" onclick="test()" value="查看结果">
    <p id="result"></p>
</body>
</html>

关键点evaluateScore 只负责验证与分发,具体的“成绩评定”逻辑被注入为回调,实现了业务解耦


四、设计模式视角:用回调重构代码

考虑一个低效的初始实现:一个函数查找节点,另一个函数隐藏节点,两者分离导致重复遍历

问题代码(冗余遍历)

javascript
// 1. 查找所有节点(耗时操作)
var findNodes = function() {
    var nodes = [];
    // ... 模拟大量循环查找
    return nodes; // 返回整个数组
};

// 2. 遍历返回的数组来隐藏节点
var hide = function(nodes) {
    for (var i = 0; i < nodes.length; i++) {
        nodes[i].style.display = 'none';
    }
};

hide(findNodes()); // 先完整找出数组,再遍历隐藏 -> 两次循环

重构:使用回调避免冗余

javascript
// 重构后的查找函数,接受一个回调,在找到每个节点时立即处理
var findNodes = function(callback) {
    if (typeof callback !== 'function') {
        callback = function() {}; // 安全默认值
    }
    var i = 100000; // 模拟大量查找
    while (i--) {
        var found = /* 模拟找到一个节点 */;
        // 在查找过程中立即调用回调处理当前节点,而非堆积到数组
        callback(found);
    }
};

// 定义单一职责的回调:隐藏单个节点
var hide = function(node) {
    node.style.display = 'none';
};

// 一次循环内同时完成“查找”和“隐藏”,效率更高
findNodes(hide);
// 或直接使用匿名函数:findNodes(function(node){ node.style.display = 'none'; });

优势

  1. 性能提升:将“查找”与“处理”合并为单次迭代,避免了中间数组的创建和第二次遍历。
  2. 职责清晰findNodes 只关心“如何找”,把“找到后做什么”完全交给回调,符合单一职责原则
  3. 灵活性:回调可替换为 highlightlog 等任何处理逻辑,使 findNodes 成为通用函数。

总结

回调函数是 JavaScript 异步编程和函数式思想的基石。掌握它意味着你能:

  • 编写非阻塞、响应迅速的应用。
  • 通过依赖注入的方式,实现模块间的解耦与复用。
  • 灵活控制函数执行上下文(this)与参数传递。

从简单的通知机制到复杂的事件驱动架构,回调无处不在。理解其原理与模式,是通往高级 JS 开发的关键一步。

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发

请登录后发表评论