上一篇拆了 add_filter('wp_title', 'lingfeng_wp_title', 10, 2) 那行代码,有位读者在评论里问了我一个把我问住的问题:
$title = apply_filters( 'wp_title', $title, $sep, $seplocation );这行是 WordPress 核心运行时自动调用所有钩子,还是只调用wp_title这一个?
这个问题问得很关键——因为如果我连这点都含糊,那前面讲的”钩子”就全是空中楼阁。这篇文章把它彻底讲清楚。
这不是我的推测,源码写得明明白白(`wp-includes/plugin.php`):
function apply_filters( $hook_name, $value, ...$args ) {
global $wp_filter; // ← 一本大登记簿
if ( ! isset( $wp_filter[ $hook_name ] ) ) { // ← 按名字查表,查不到就走人
return $value; // ← 原样还回去,什么也不改
}
$filtered = $wp_filter[ $hook_name ]->apply_filters( $value, $args );
return $filtered;
}
请注意中间那句“查不到就原样还回去”——这就是全部答案:它拿着名字去查,只叫这一格下面登记的人,其他格子一概不碰。
换句话说:钩子名就是唯一索引。叫什么名字,就叫谁。
想象学校收发室的墙上有一块大板子,分成很多格,每格贴一个名字:语文组、数学组、物理组……这个板子就是 `$wp_filter`,一个 PHP 全局数组,键是钩子名。
add_filter = 把自己的名字写到某一格下面。我的代码写的是”wp_title”这一格。apply_filters('wp_title', ...) = 管理员收到一封信,找到”wp_title”那一格,照着上面的名单逐个通知。于是事情就清楚了:
| 动作 | 结果 |
|---|---|
我登记在 wp_title 格下 |
有 wp_title 的信到了 → 叫我 |
内核执行 apply_filters('the_content', ...) |
找的是 the_content 那一格 → 不叫我 |
wp_title 格下一个人都没有 |
信原样放回去,什么也不发生 |
我主题里那行 add_action('save_post', 'auto_add_tags') 登记的就是另一格——只有”文章保存”这一刻才会被叫到,平时它安静地躺着。
刚接触钩子的人容易这么想,但只要推演一下就发现行不通:
所以真实机制是点位预埋 + 按名查表:WordPress 在代码里合适的位置埋下一个个 apply_filters() 调用点,每个点写死一个名字。内核执行到哪一行,才触发那一行写的那个名字。不到那一行,就算钩子存在也静默无声。
光说不够直观,把上面那个 my_hook 例子的执行过程画出来:

注意第 ③ 步:add_b 拿到的是 add_a 改完的结果,不是最初的 '开始-'。值像接力棒一样依次往下传,每个函数接过来、改一下、再交回给下一个。
这就解释了为什么 filter 必须 return——你不交棒,后面的人接到的是空的,页面内容就少一块,而且不报错。
function teacher_liu( $text ) {
return $text . '【刘老师批注】';
}
function teacher_wang( $text ) {
return $text . '【王老师批注】';
}
add_filter( 'my_hook', 'teacher_liu', 10 ); // 登记在第 10 位
add_filter( 'my_hook', 'teacher_wang', 20 ); // 登记在第 20 位
echo apply_filters( 'my_hook', '原文' );
输出:
原文【刘老师批注】【王老师批注】
发生了什么,一步一步:
apply_filters 拿到初始值 '原文' 和钩子名 'my_hook';my_hook 这一格,发现登记了两个人,按优先级从小到大排队;teacher_liu,它接过 '原文',返回 '原文【刘老师批注】';teacher_wang,返回 '原文【刘老师批注】【王老师批注】';add_filter( 'wp_title', 'my_func', 10, 2 );
apply_filters( 'the_content', '正文' ); // my_func 不会被调用
apply_filters( 'wp_title', '标题', '-' ); // 只有这句才会调用 my_func
这是新手最常遇到的困惑之一:”我明明加了 filter,怎么没效果?” 十有八九是名字写错了——而且 WordPress 不会给你任何报错提示,它只会安静地什么都不做。所以排查这类问题,第一步永远是核对名字,一个字母一个字母地对。
$title = apply_filters( 'wp_title', $title, $sep, $seplocation );
它位于内核 wp_title() 函数的倒数第二步。翻译成人话:
“标题我拼好了,现在谁登记在
wp_title这一格,就来改一下;改完把结果存回$title,我准备输出了。”
而主题 functions.php 里的 add_filter('wp_title', 'lingfeng_wp_title', 10, 2) 就是提前在那个格子里签了名。之所以签得上,是因为 functions.php 在内核输出页面之前就加载完毕了——所以签名总是赶得上。
这也解释了一个初看很神奇的现象:你从没在代码里调用过 lingfeng_wp_title(),它却执行了。 不是你调用的,是内核翻到那一格时代替你叫的。
钩子不是广播,是点名。你挂在哪一格,就只在那格被叫到。
理解了这一点,回头看 WordPress 的扩展方式就顺了:它不改自己的核心代码,而是在几千个关键位置留下”叫名”的口子;我们呢,把想改的东西提前登记在对应格子里。双方不见面、不耦合,改功能不用动核心,升级也不会被覆盖。
下次再遇到”我加了 filter 却没生效”,按这个顺序查:名字对不对 → 优先级会不会被别人抢了 → 有没有 return → 函数名是不是撞了。四个查完,基本没有查不出来的。
(这是 WordPress 踩坑系列的第六篇,前一篇是 add_filter 的第四个参数到底管什么。)