WordPress 钩子体系:add_action 和 add_filter 到底差在哪

· wordpress · 阅读 17
摘要

两者共用 add_filter 底层,差别只在返回值去向:do_action 丢弃、apply_filters 传递,外加优先级、参数个数与移除钩子的坑。

很多人用了几年 WordPress,仍然分不清 add_action 和 add_filter。它们底层是同一套机制、同一张 $wp_filter 全局数组,唯一的区别在返回值怎么用。搞懂这一点,纠结就没了。

一、共同点:都是挂在钩子上

两个函数最终都调用 add_filter($tag, $callback, $priority, $accepted_args)。是的,add_action 内部就是 add_filter 的包装:

function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
    return add_filter($tag, $function_to_add, $priority, $accepted_args);
}

所以从「能不能挂」的角度讲,二者完全等价——你甚至可以用 add_action('the_content', ...) 挂一个本来属于 filter 的钩子,代码照样跑。区别只在触发方怎么处理你的返回值。

二、区别:do_action 丢弃返回值,apply_filters 拿走返回值

action 的场景

// 定义方
function my_theme_header() {
    do_action('my_after_header');
}

// 使用方
add_action('my_after_header', function () {
    echo '<div class="banner">广告位</div>';
});

do_action 只负责依次调用回调,回调返回什么它不管。你要产生影响,必须是回调内部的副作用——echo 输出、写数据库、发邮件、改全局变量。

filter 的场景

// 定义方
$title = apply_filters('my_title', $raw_title);

// 使用方
add_filter('my_title', function ($title) {
    return $title . ' | 我的站';  // 必须 return
});

apply_filters 把当前值传进第一个回调,接收回调的返回值作为新值,再传给下一个回调,最后返回最终值。这里忘记 return 就是灾难——返回 null,原来那串标题直接变空。

一句话记法:action 靠副作用,filter 靠返回值。

三、优先级和参数个数

第三个参数 $priority 越小越先执行,默认 10。想让自己的回调在别人之后跑,用 20;抢在前面用 5。同一优先级按注册顺序执行。

第四个参数 $accepted_args 老被忽略。回调能接几个参数,由它决定:

// the_content 实际传 1 个参数,这里写 3 也只会拿到 1 个
add_filter('the_content', 'my_filter', 10, 1);

// save_post 传 3 个:$post_id, $post, $update
add_action('save_post', 'my_save', 10, 3);
function my_save($post_id, $post, $update) {
    // 三个都能用
}

常见 bug:add_action('save_post', 'my_save', 10, 3) 写成了 4 个参数没声明,回调只能收 1 个,$update 永远是 null。

四、移除钩子必须参数对得上

remove_action / remove_filter 靠「回调 + 优先级」定位,优先级不同就删不掉:

// 注册
add_action('wp_head', 'my_meta', 20);

// 删不掉(优先级不匹配)
remove_action('wp_head', 'my_meta');

// 正确
remove_action('wp_head', 'my_meta', 20);

匿名函数(闭包)注册的钩子基本删不掉,除非你把闭包存进变量再传:

$cb = function () { echo 'x'; };
add_action('wp_head', $cb);
remove_action('wp_head', $cb);

这是做子主题覆盖父主题行为时最常见的翻车点。

五、怎么知道一个钩子存在不存在

has_action($tag) / has_filter($tag) 可以探测(两个函数通用,别被名字骗了)。查看某钩子上挂了什么:

global $wp_filter;
var_dump($wp_filter['the_content']);

输出里能看到每个回调、优先级、accepted_args。调试「为什么我的过滤器没生效」时,先确认它真的被注册了——有时候是条件判断 is_admin() 写反,压根没进注册分支。

六、到底该用哪个:判断标准

你的需求用哪个
在某个位置输出 HTMLaction
修改一段已有的文本/数组/对象filter
保存文章后做额外操作action
改变查询参数filter(pre_get_posts)
页面加载完执行 JSaction(wp_footer)

其实不用记表。看定义方的源码:写 do_action 的地方只能挂 action,写 apply_filters 的地方只能挂 filter。挂错了不报错,但大概率不生效——这是最隐蔽的一类问题。

小结

同源、同表、同参数,唯一分歧在返回值去向:do_action 丢弃,apply_filters 传递。filter 忘记 return 会清空数据,action 里 return 毫无意义。下一篇讲 wp_enqueue_script 的依赖顺序——为什么你的 jQuery 插件总说 $ 未定义。