WordPress 条件判断函数实战指南:钩子时机、is_singular 边界、循环重置与 SEO 注入

WordPress 条件判断(Conditional Tags)的难点不在记函数列表,而在调用时机与组合使用。本文按真实开发场景:按页面类型加载 JS/CSS、SEO meta 注入、内容过滤、分页处理、循环嵌套重置,给出完整代码片段,并讲清 is_home vs is_front_page、is_single vs is_singular、in_category vs is_category 等高频混淆点。

张文保 更新 30 分钟阅读 1,164 阅读

WordPress 主题与插件开发里十有八九的代码分支判断都靠条件判断函数(Conditional Tags)。"只在首页加这段 JS"、"只在分类页 ID=15 时改样式"、"自定义文章类型的归档页 noindex"——所有这些需求的入口都是几个核心条件判断。本文不再罗列所有 is_xxx 函数(官方手册有完整列表),而是按真实开发场景把这些函数组合起来:钩子注册时机、模板树覆盖、SEO 控制、AB 测试、性能优化、缓存插件兼容,并补全每个函数的常见误用与边界条件。

条件判断函数的执行时机

必须在 WP_Query 完成后才能用

所有 is_xxx 函数底层依赖 $wp_query 全局对象。如果你在 WP_Query 解析当前请求之前调用(比如 plugins_loaded 钩子、init 钩子前期),返回值都是 false 或意外结果。安全的调用时机:

  • wp 钩子之后:WordPress 完成主查询后触发,is_xxx 全部可用。
  • template_redirect 钩子:在加载主题模板前触发,最常用的“按页面类型分支”的钩子位置。
  • 主题模板文件内部:header.php、index.php、single.php 等模板加载时主查询已经完成,可直接用。
  • 主循环内部:循环体里 is_single、has_excerpt、in_the_loop 等都是当前文章上下文。

不安全的调用时机

下面这些场景里调用 is_xxx 大概率拿到错误结果:

  • plugins_loaded、init、admin_init 钩子:太早,主查询还没解析完。
  • functions.php 顶层(不在钩子内):functions.php 在每次请求初期被加载,此时 $wp_query 未就绪。
  • REST API 端点内:REST API 走独立路由,不解析模板查询,is_xxx 全返回 false。
  • WP-CLI 命令内:CLI 不模拟 HTTP 请求上下文。

解决:把代码挪到 wp 或 template_redirect 钩子内:

add_action( 'template_redirect', function() {
    if ( is_home() ) {
        // 首页才执行的代码
    }
});

核心场景一:按页面类型加载不同 JS / CSS

典型需求

商品详情页加载产品图缩放库;首页加载轮播图库;联系页加载地图 SDK;其它页面什么都不加。这种场景每个站都遇到,错误做法是把所有 JS 都塞进 footer,正确做法是按条件 enqueue。

add_action( 'wp_enqueue_scripts', function() {
    // 首页:轮播图
    if ( is_front_page() ) {
        wp_enqueue_script( 'swiper', 'https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js', [], '11.0', true );
        wp_enqueue_style( 'swiper-css', 'https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css' );
    }

    // 单文章页:图片缩放 + 代码高亮
    if ( is_singular( 'post' ) ) {
        wp_enqueue_script( 'lightbox', get_template_directory_uri() . '/js/lightbox.js', ['jquery'], '2.11', true );
        wp_enqueue_style( 'prism', get_template_directory_uri() . '/css/prism.css' );
    }

    // 联系页(特定 page slug)
    if ( is_page( 'contact' ) ) {
        wp_enqueue_script( 'amap', 'https://webapi.amap.com/maps?v=2.0&key=YOUR_KEY', [], null, true );
    }
});

这种条件加载平均能减少 30-60% 的 JS 字节量,对 LCP 改善明显。

核心场景二:按页面类型注入 SEO 标签

列表页 noindex 防止索引膨胀

add_action( 'wp_head', function() {
    // 标签页、作者页、日期归档:noindex,follow
    if ( is_tag() || is_author() || is_date() ) {
        echo '<meta name="robots" content="noindex,follow">' . "\n";
        return;
    }
    // 搜索结果页、404:noindex,nofollow
    if ( is_search() || is_404() ) {
        echo '<meta name="robots" content="noindex,nofollow">' . "\n";
        return;
    }
    // 分页第 2 页之后:noindex,follow
    if ( is_paged() ) {
        echo '<meta name="robots" content="noindex,follow">' . "\n";
        return;
    }
});

不同模板下 canonical 指向

WordPress 自动生成的 canonical 在大多数场景正确,但某些 SEO 插件会盖掉。需要手动校正时:

add_action( 'wp_head', function() {
    if ( is_singular() ) {
        $canonical = get_permalink();
    } elseif ( is_home() || is_front_page() ) {
        $canonical = home_url( '/' );
    } elseif ( is_category() || is_tag() || is_tax() ) {
        $canonical = get_term_link( get_queried_object() );
    } else {
        return; // 其它情况让 WP 默认或 SEO 插件处理
    }
    echo '<link rel="canonical" href="' . esc_url( $canonical ) . '">' . "\n";
}, 5 );

核心场景三:内容输出过滤

只在文章页插入广告 / 订阅表单

add_filter( 'the_content', function( $content ) {
    // 仅在单文章页 + 主循环内 + 不是搜索结果
    if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
        return $content;
    }
    // 仅文章长度超过 1000 字才插入
    if ( str_word_count( strip_tags( $content ) ) < 1000 ) {
        return $content;
    }

    $cta = '<div class="newsletter-cta">订阅周刊获取更多内容</div>';
    // 在第二个 </p> 后插入
    $count = 0;
    return preg_replace_callback( '/<\/p>/', function( $m ) use ( $cta, &$count ) {
        $count++;
        return $count === 2 ? '</p>' . $cta : '</p>';
    }, $content );
});

这段代码合在一起做了五件防御性判断:is_singular('post') 限单文章;in_the_loop() 防止主循环外(比如相关文章 widget)也被改;is_main_query() 防止子查询命中;字数下限避免短文章被插得密密麻麻;只插一次而不是每个段落都插。

特定分类的文章追加版权声明

add_filter( 'the_content', function( $content ) {
    if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
        return $content;
    }
    if ( in_category( 'translation' ) ) {
        $content .= '<p class="translation-credit">本文为翻译文章,原文链接见文末。</p>';
    }
    if ( in_category( ['premium', 'paid-content'] ) ) {
        $content .= '<p class="paid-notice">此为付费内容,禁止转载。</p>';
    }
    return $content;
});

条件函数详细用法

is_home() vs is_front_page() 的关键差异

这是新手最常踩的坑。两者都是“首页判断”但行为不同:

  • is_home():返回 True 的条件是“文章列表页”。如果后台“设置-阅读”选了“最新文章”作为首页,is_home() 在首页返回 True;如果选了静态页面作为首页,is_home() 在那个静态页面返回 False,反而在“文章列表页”(设置里指定的那个 page)返回 True。
  • is_front_page():返回 True 的条件是“站点首页”。无论后台设置“最新文章”还是“静态页”,访问域名根目录就 True。

实战经验:要"在域名根目录页面执行某代码",永远用 is_front_page();要"在文章流页面执行",用 is_home()。

is_single 与 is_singular 的差异

  • is_single():仅匹配“文章 post 类型”与“自定义文章类型”(不含 page、attachment)。
  • is_page():仅匹配“页面 page 类型”。
  • is_singular():is_single OR is_page OR is_attachment 的并集。
  • is_attachment():仅匹配“附件页”。

如果你想"任意单页面(文章/页面/附件)都执行某代码",is_singular() 最简洁。

is_single() 参数的歧义

is_single( 17 ) 既匹配 ID=17 也匹配 slug='17',因为 WordPress 不区分。如果你的某篇文章的 slug 真的是数字字符串"17",会被误命中。规避:

// 严格按 ID 判断
if ( is_single() && get_the_ID() === 17 ) { ... }

is_category vs in_category

  • is_category( 'news' ):当前是不是“news 这个分类的归档页”。在分类列表页有效。
  • in_category( 'news' ):当前文章是不是属于 news 分类。在文章页有效,必须在循环内。

常见混用:用 is_category 判断单文章,永远 false;用 in_category 判断分类页,逻辑也错。两者用途完全不同。

is_tax 与自定义分类

WP 自带的分类(category)与标签(tag)有专门的 is_category / is_tag 函数。但如果你注册了自定义分类(custom taxonomy),比如“品牌 brand”“产品系列 series”,需要用 is_tax:

// 当前是 brand 自定义分类的归档页
if ( is_tax( 'brand' ) ) { ... }

// 当前是 brand=apple 的归档页
if ( is_tax( 'brand', 'apple' ) ) { ... }

// 当前是 brand=apple/samsung/huawei 任一的归档页
if ( is_tax( 'brand', ['apple', 'samsung', 'huawei'] ) ) { ... }

对应的 is_category 不能传自定义 taxonomy slug,传了无效。

is_post_type_archive 与自定义文章类型

注册了 product、portfolio、event 这种自定义文章类型时,对应的归档页(example.com/product/)用 is_post_type_archive:

if ( is_post_type_archive( 'product' ) ) { ... }
if ( is_post_type_archive( ['product', 'event'] ) ) { ... }

is_paged 与 get_query_var('paged')

is_paged() 在第 2 页及之后返回 True,第 1 页返回 False。但很多场景需要知道具体是第几页:

$current_page = max( 1, get_query_var( 'paged' ) );
if ( $current_page > 1 ) {
    // 分页第 2 页之后的逻辑
}

get_query_var('paged') 在第 1 页返回 0(不是 1),所以要 max(1, ...) 兜底。

is_archive 包含哪些情况

is_archive() 是个并集函数,下面任一为真它就为真:

  • is_category
  • is_tag
  • is_tax
  • is_author
  • is_date(含 is_year、is_month、is_day)
  • is_post_type_archive

实际开发里 is_archive 用得不多,多数场景需要更具体的子判断。

has_excerpt 与 has_post_thumbnail

这两个函数判断当前文章的字段:

if ( has_post_thumbnail() ) {
    the_post_thumbnail( 'medium' );
} else {
    echo '<img src="' . get_template_directory_uri() . '/images/placeholder.jpg">';
}

if ( has_excerpt() ) {
    the_excerpt(); // 用手动 excerpt 字段
} else {
    echo wp_trim_words( get_the_content(), 50 ); // 自动截断
}

循环相关:is_main_query、in_the_loop、wp_reset

主循环 vs 副循环

WordPress 一个页面可以有多个 WP_Query。主查询是 URL 自动触发的(比如分类页的文章列表),副查询是你在主题里 new WP_Query() 创建的(比如侧栏“热门文章”)。

区别它们:

add_filter( 'pre_get_posts', function( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return; // 只改主查询
    }
    if ( is_home() ) {
        $query->set( 'posts_per_page', 20 ); // 首页每页 20 篇
    }
});

in_the_loop() 与循环重置

循环嵌套时如果不重置全局 $post,外层循环会拿到内层最后一次 the_post() 的状态。三个重置函数:

  • wp_reset_postdata():恢复全局 $post 到主查询当前文章。new WP_Query 后用这个。
  • wp_reset_query():恢复全局 $post + 销毁 $wp_query。query_posts() 后用(不推荐用 query_posts 本身)。
  • rewind_posts():重置当前查询的指针,可以重新跑一遍同一查询。

实战建议:永远用 new WP_Query + wp_reset_postdata 组合,不要用 query_posts。后者修改主查询全局变量,会引发各种诡异问题。

性能:避免不必要的条件判断

条件判断本身的开销

多数 is_xxx 函数 O(1) 复杂度(直接读 $wp_query 的属性),单次调用纳秒级。但 is_singular、is_post_type_archive 这种带参数版本会做字符串比较,参数为数组时遍历开销稍大。

避免在循环内反复调用相同判断

// 不好:循环 100 次每次都判断 is_singular
foreach ( $items as $item ) {
    if ( is_singular( 'post' ) ) {
        // ...
    }
}

// 好:循环外判断一次
$is_post = is_singular( 'post' );
foreach ( $items as $item ) {
    if ( $is_post ) {
        // ...
    }
}

缓存插件下条件判断的边界

WP Super Cache、W3 Total Cache 等基于“整页静态化”的缓存插件,第一次访问时执行 PHP 并写缓存文件,后续访问直接发缓存。如果你的条件判断结果依赖动态因素(用户登录态、cookie、A/B 实验组),缓存会让所有用户看到同一个版本。

规避:把动态判断挪到 JS 端(前端 fetch 当前用户状态再渲染),或者给缓存插件配置“按用户角色 / 按设备分桶缓存”。

钩子注册的时机选择

钩子触发时机is_xxx 是否可用
plugins_loaded插件加载后
initWP 核心初始化后
wp_loadedWP + 插件 + 主题全部加载否(主查询还没跑)
parse_request请求解析中
wp主查询完成
template_redirect主题模板加载前是(最常用)
wp_head模板 head 内
wp_enqueue_scripts队列脚本时
the_content(filter)正文输出

常见故障

故障 1:is_home 在首页返回 false

后台“设置-阅读”选了“静态页面作为首页”。is_home 在这种情况下不再指首页而是指文章流页。改用 is_front_page。

故障 2:is_category('news') 永远返回 false

三个排查:'news' 是不是分类的 slug 而不是名字(is_category 同时匹配 ID/slug/name 但拼写要严格);分类的 slug 是不是 URL 编码的中文;当前页面真的是分类归档页吗(is_archive 看一下)。

故障 3:in_category 在循环外永远 false

必须在主循环内(the_post() 已调用让 $post 全局可用)。在 widget、sidebar 那种循环外直接调用拿不到当前文章。

故障 4:has_term 找不到自定义分类的 term

has_term( 'apple', 'brand' ) 第一个参数应是 slug 或 ID,不是显示名。常见把 'Apple' 写成 'apple' 没注意大小写。

故障 5:循环嵌套后 is_singular 错乱

侧栏 widget 跑了 new WP_Query 没 wp_reset_postdata,全局 $post 指向 widget 的最后一篇文章,is_singular 判断的还是这篇而不是主查询。每个 new WP_Query 后强制 wp_reset_postdata。

故障 6:REST API 端点里 is_singular 不工作

REST API 不解析模板查询,所有 is_xxx 都是 false。如果你的端点需要类似的判断,从请求参数里自己解析。

故障 7:自定义文章类型的归档页 is_archive 命中但 is_post_type_archive 不命中

注册自定义文章类型时 has_archive 必须设为 true(默认 false)。如果设了 false,归档 URL 仍能访问但不算 post_type_archive。

实用代码片段集

仅在登录用户访问的页面执行某代码

if ( is_user_logged_in() && is_singular() ) {
    // 已登录用户在文章页执行
}

仅给未付费用户显示 paywall

add_filter( 'the_content', function( $content ) {
    if ( ! is_singular( 'post' ) || ! in_the_loop() ) return $content;
    if ( current_user_can( 'subscriber' ) || current_user_can( 'administrator' ) ) return $content;
    if ( ! has_term( 'premium', 'category' ) ) return $content;

    $words = str_word_count( strip_tags( $content ) );
    if ( $words > 200 ) {
        $excerpt = wp_trim_words( $content, 200, '...' );
        return $excerpt . '<div class="paywall"><p>此内容仅对订阅会员开放。</p><a href="/subscribe">立即订阅</a></div>';
    }
    return $content;
});

移动端单独样式

add_action( 'wp_enqueue_scripts', function() {
    if ( wp_is_mobile() && is_singular() ) {
        wp_enqueue_style( 'mobile-article', get_template_directory_uri() . '/css/mobile-article.css' );
    }
});

wp_is_mobile 用 UA 检测,不是 100% 可靠(部分平板会被误判),但够用于一般场景。

常见问题解答

is_home 与 is_front_page 究竟该用哪个?

判断"用户访问的是站点首页(域名根目录)"用 is_front_page,无论后台设置如何都可靠;判断"是文章流列表页"用 is_home,搭配 is_paged 处理分页。如果你的站点首页就是文章列表(默认配置),两个函数效果一致。

条件判断函数能否在 functions.php 顶层直接使用?

不能。functions.php 在每次请求初期被加载,主查询还没解析完,所有 is_xxx 都不可靠。必须挂到钩子里(最常用 wp、template_redirect、wp_enqueue_scripts、wp_head)。

多语言插件下 is_page('contact') 找不到联系页?

WPML、Polylang 给每种语言生成不同的 page,slug 可能是 contact、contact-en、contact-zh 等。判断时应该按 page ID 而不是 slug:if ( is_page( get_option('page_for_contact') ) ) 或用插件提供的 API(pll_current_language() 等)。

has_post_thumbnail 在自定义文章类型下不工作?

注册自定义文章类型时 supports 数组要包含 'thumbnail':register_post_type( 'product', ['supports' => ['title', 'editor', 'thumbnail'], ...] )。否则后台连“设置特色图”按钮都没有。

条件判断与 SEO 插件冲突?

Yoast SEO、Rank Math 都用钩子注入 robots、canonical 标签。如果你也注入会出现重复。建议:要么完全用 SEO 插件管,要么禁用插件对应模块再自己写。混着用会反复盖。

怎样调试当前页面命中了哪些条件?

add_action( 'wp_footer', function() {
    if ( ! current_user_can( 'manage_options' ) ) return;
    $checks = ['is_home','is_front_page','is_singular','is_single','is_page','is_archive','is_category','is_tag','is_tax','is_author','is_search','is_404','is_paged'];
    echo '<pre style="background:#000;color:#0f0;padding:10px">';
    foreach ( $checks as $c ) {
        if ( call_user_func( $c ) ) echo "$c: TRUE\n";
    }
    echo '</pre>';
});

登录管理员账号访问任意页面,footer 会列出所有命中的条件函数。调试完删掉。

条件函数的性能影响大吗?

极小。多数 is_xxx 是 O(1) 字段读取,单次调用几纳秒。即使 wp_head 里串十几个 if 判断,对页面渲染时间影响在毫秒级以下。性能瓶颈通常在数据库查询而不是条件判断。

is_admin 与 is_user_logged_in 的区别?

is_admin 判断"当前请求是不是 wp-admin 后台请求"(与用户角色无关);is_user_logged_in 判断"当前用户登录了没"。前台访问的登录管理员:is_admin=false,is_user_logged_in=true。

条件判断函数对 AMP 页面有效吗?

WP 的 AMP 插件在 AMP 模式下会调整模板加载流程,但 is_xxx 函数仍可用。AMP 模式有专门的 is_amp_endpoint() 函数判断当前请求是不是 AMP 版本。

条件判断结果能否缓存到对象缓存?

可以但意义不大。is_xxx 本身已经是读 $wp_query 字段,比从对象缓存(Redis/Memcached)读还快。缓存反而会引入序列化开销。

分享到
标签
版权声明

本文标题:《WordPress 条件判断函数实战指南:钩子时机、is_singular 边界、循环重置与 SEO 注入》

本文链接:https://zhangwenbao.com/use-the-wordpress-condition-to-determine-the-function-to-execute-specific-code-on-a-specific-page.html

版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交