钩子不是广播,是点名:apply_filters 到底是怎么找到你的函数的

作者: admin 分类: wordpress主题制作教程 发布时间: 2026-10-14 12:11

上一篇拆了 add_filter('wp_title', 'lingfeng_wp_title', 10, 2) 那行代码,有位读者在评论里问了我一个把我问住的问题:

$title = apply_filters( 'wp_title', $title, $sep, $seplocation ); 这行是 WordPress 核心运行时自动调用所有钩子,还是只调用 wp_title 这一个?

这个问题问得很关键——因为如果我连这点都含糊,那前面讲的”钩子”就全是空中楼阁。这篇文章把它彻底讲清楚。

一、先给答案:只调用 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 里埋了上千个钩子点,每次都把所有函数跑一遍,一个页面要执行几千次;
  • 会报错:钩子点递给函数的参数类型五花八门,有的给标题字符串,有的给评论数组,给错对象函数直接崩;
  • 会乱套:给正文加版权声明的函数,没道理在”保存文章”时也跑一遍。

所以真实机制是点位预埋 + 按名查表:WordPress 在代码里合适的位置埋下一个个 apply_filters() 调用点,每个点写死一个名字。内核执行到哪一行,才触发那一行写的那个名字。不到那一行,就算钩子存在也静默无声。

四、一次执行到底发生了什么

光说不够直观,把上面那个 my_hook 例子的执行过程画出来:

apply_filters 执行流程四步图:收到值、按名字查表、依次调用登记的函数、返回最终结果
一次 apply_filters 的执行全过程:值像接力棒依次穿过每个登记的函数

注意第 ③ 步: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', '原文' );

输出:

原文【刘老师批注】【王老师批注】

发生了什么,一步一步:

  1. apply_filters 拿到初始值 '原文' 和钩子名 'my_hook';
  2. 翻到 my_hook 这一格,发现登记了两个人,按优先级从小到大排队;
  3. 先叫 teacher_liu,它接过 '原文',返回 '原文【刘老师批注】';
  4. 把这个结果当新输入,再叫 teacher_wang,返回 '原文【刘老师批注】【王老师批注】';
  5. 全部跑完,把最后的值还给你。

六、名字对不上会怎样?完全没反应,也不报错

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 的第四个参数到底管什么。)

相关文章

搜索文章:
联系方式:
Powered by 草根站长笔记 © 2015-2020 草根站长笔记 Inc.版权所有,禁止转载