add_filter 的第四个参数到底管什么?拆我自己那行 wp_title 代码,还拆出三个问题

作者: admin 分类: 运营、维护日志 发布时间: 2026-10-10 22:26

上一篇讲了 add_filter 是什么,这一篇拿我自己主题里真实在跑的一行代码开刀,把它彻底拆开。这行代码是那个文件里唯一把四个参数用满的一行,搞懂它,add_filter 就算过关了。

就是这行:

add_filter('wp_title', 'lingfeng_wp_title', 10, 2);

一句话概括它的意思:WordPress 每次要输出网页标题之前,先把它交给我的 lingfeng_wp_title 改一遍——并且我要接 2 个参数。

一、先搞清楚:这个钩子改的是什么标题

新手最容易在这里搞混。wp_title 这个钩子改的不是文章标题,而是浏览器标签页上显示的那行字,也就是 HTML 里的 <title> 标签内容。官方文档的原话是:过滤 the text appearing in the HTML <title> tag,也就是常说的”title tag”或”meta title”。

换句话说:您在后台给文章起的标题一动不动,变的只是搜索引擎和浏览器标签看到的那一行。

二、把四个参数拆开看

add_filter( 'wp_title', 'lingfeng_wp_title', 10, 2 );
//          ↑钩子名      ↑我的函数名        ↑优先级  ↑我要接几个参数
位置 值 含义
第 1 个 wp_title 挂在”生成网页标题”这个点上
第 2 个 lingfeng_wp_title 到点了让这个函数去干
第 3 个 10 优先级,默认值。写在这里其实是为了给第 4 个参数占位置
第 4 个 2 关键:告诉 WordPress”我这个函数要接 2 个变量”

三、第四个参数到底在决定什么

这是整行代码最值得讲的地方。WordPress 在这个点上,其实会递出 3 个值给所有登记过的函数,源码就这一句:

$title = apply_filters( 'wp_title', $title, $sep, $seplocation );
//                                  ↑标题文字  ↑分隔符  ↑分隔符位置

但您写的是 2,所以您的函数只能接到前两个,第三个 $seplocation(值是 'right')被放弃了。这不是写错,函数里没用它,写 2 反而更省事——但规矩必须清楚:

add_filter('wp_title', 'f', 10, 1);  // 只能写 function f($title)
add_filter('wp_title', 'f', 10, 2);  // 可以写 function f($title, $sep)        ← 我这里
add_filter('wp_title', 'f', 10, 3);  // 可以写 function f($title, $sep, $seplocation)

如果第四个参数不写(默认 1),你函数里却写了第二个变量,那个变量就是 undefined。 这是新手最常踩的坑,也正是这行代码里 2 存在的唯一理由——为了让下面的函数签名能理直气壮地写成 ($title, $sep)。

四、整条调用链长什么样

这行代码不是一个孤零零的句子,它只是”登记”这个动作。真正跑起来是这样一条链:

  1. 主题的 header.php 里写着 <title><?php wp_title('', true, 'right'); ?></title>;
  2. WordPress 内核按当前页面(文章 / 分类 / 搜索 / 404)拼出原始标题,存进 $title;
  3. 内核执行 apply_filters('wp_title', ...),翻出所有在这个钩子上登记过的函数,逐个调用;
  4. 轮到我的 lingfeng_wp_title($title, $sep),接过来改一改,return 回去;
  5. 回到 header.php,把改完的结果 echo 进 <title> 标签。

注意第 4 步和第 5 步之间:您从来不主动调用自己的函数,是 WordPress 替您调的。这就是”钩子”这个词的来源——挂上去,等它被拉动。

五、函数体逐行拆

function lingfeng_wp_title($title, $sep) {
    global $paged, $page;                    // ① 开门:拿外面的分页号

    if ( is_feed() ) {                       // ② RSS 输出时原样还回去
        return $title;
    }

    $title = $title;                         // ③ 自己赋值给自己(废代码)

    $site_description = get_bloginfo( 'description', 'display' );
    if ( $site_description && ( is_home() || is_front_page() ) )
        $title .= get_bloginfo( 'name' );     // ④ 首页追加站点名

    if ( $paged >= 2 || $page >= 2 )          // ⑤ 第 2 页起,拼上"第 X 页"
        $title = "$title $sep " . sprintf( '第%s页', max( $paged, $page ) );

    $search = array('&#8211;', ' ');          // ⑥ 去掉短横线实体和空格
    $replace = array('-', '');
    $title = str_replace($search, $replace, $title);

    return $title;                            // ⑦ filter 的命根子
}
  • ① 函数内部默认拿不到外面的全局变量,global 这行是把 $paged(列表分页号)和 $page(文章内部分页号)请进来。
  • ② 防御性写法:RSS 订阅输出时不要动标题,原样还回。注意这里也是 return——filter 的每条出口都要还值。
  • ④ 首页/ front page 时,把站点名追加到标题后面。
  • ⑤ 翻到第 2 页及以后,标题后面补上”第 X 页”。
  • ⑥ 用 str_replace 做两个替换:&#8211;(短横线的 HTML 实体)换成 -,空格换成空。
  • ⑦ 没有这一行,网页标题就是空的。

六、拆完之后,我发现自己代码里有三处问题

写这篇文章的最大收获不是讲懂了参数,而是把代码摊开看时发现它有三处毛病。这三处都不影响网站运行,所以一直没暴露,但它们确实存在。

问题 1:分隔符其实是空的,”第 2 页”前面什么都没有

回看 header.php 里那句调用:

wp_title('', true, 'right');
//       ↑ 第一个参数就是 $sep,我传了空字符串

wp_title() 的完整签名是 wp_title( $sep = '&raquo;', $display = true, $seplocation = '' ),第一个参数正是分隔符,默认是 »。我在这里传了 '',于是函数里的 $sep 拿到的是空字符串。

结果就是第 ⑤ 步 "$title $sep " 拼出来是”标题 + 两个空格 + 第2页”,紧接着第 ⑥ 步又把空格全删掉——最终显示成 某某文章第2页。

修法:把 header.php 里改成 wp_title('|', true, 'right'),或者在函数里写死分隔符,不依赖 $sep。

问题 2:第 ⑥ 步会把标题里所有空格删光,英文标题会粘成一团

$search  = array('&#8211;', ' ');
$replace = array('-', '');        // 空格 → 空,而且是全部空格

这正是我上一篇文章里讲的 str_replace“全部替换”——在这里它变成了副作用:文章标题里原本的空格也被一起干掉了。标题是 Hello World,输出就变成 HelloWorld。

中文标题不带空格,所以我的站从没出过问题;但只要有一篇英文标题的文章,就会露出马脚。这也说明一件事:一个函数在 A 处是解法,在 B 处可能就是 bug,得看它作用的字符串里到底有什么。

问题 3:$title = $title; 是废代码

自己赋值给自己,什么也没发生,直接删掉即可。估计是当初写着写着留下的痕迹。

七、顺带说一件更要紧的:wp_title() 已经是老写法了

wp_title() 是 WordPress 4.4 之前的标准做法。现在推荐的做法是在 functions.php 里声明一句:

add_theme_support( 'title-tag' );

然后删掉 header.php 里手写的 <title> 标签,让 WordPress 核心接管标题输出。

好处有两个:一是标题格式由核心统一维护,不用自己拼;二是 Yoast、Rank Math 这类 SEO 插件才能正常接管标题。主题手写 <title> 的话,插件和主题会各输出一套,互相打架。

我这个主题没声明 title-tag,目前跑得好好的,但属于遗留写法——哪天想装 SEO 插件,就得先动这一处。

八、收个尾

回头看,add_filter('wp_title', 'lingfeng_wp_title', 10, 2) 这行代码里藏着四层信息:挂哪个点、谁去干、什么时候轮到我、我能拿几个参数。搞懂它之后再看主题里其他 add_filter,就都是同一个模子了。

但比参数更值钱的是第六条那三处问题——它们都不是靠”学语法”发现的,是靠把代码摊开、一行一行追问”这个变量此刻到底等于什么”挖出来的。 $sep 看着是个分隔符,实际是空字符串;str_replace 看着是清理格式,实际连标题里的空格一起吃了。代码不会报错,它就安安静静地错着,直到某天你翻到第 2 页、或者发了一篇英文标题的文章。

所以我的习惯改了:看到一段能跑的旧代码,别急着信,问一句”它真的在做我以为的那件事吗“。这次的答案是:三处里有一处半不是。

(这是 WordPress 踩坑系列的第五篇,上一篇是 add_filter 是 WordPress 独有的吗。)

相关文章

Powered by 草根站长笔记 © 2015-2020 草根站长笔记 Inc.版权所有,禁止转载