上一篇说过,caogenbj 的 single.php 原本有两个循环:一个输出作者,一个输出正文。
按理说,第一个循环跑完,主查询的指针就到头了。第二个循环的 have_posts() 应该返回假,正文应该不显示。
但页面上正文明明是正常的。为什么?
我翻 WordPress 源码,找到了答案。have_posts() 里有一段:
if ( $this->current_post + 1 < $this->post_count ) {
return true;
} elseif ( $this->current_post + 1 == $this->post_count && $this->post_count > 0 ) {
do_action_ref_array( 'loop_end', array( &$this ) );
$this->rewind_posts(); // ← 关键:读到最后一条时,自动倒带
}
return false;
注意那句 rewind_posts():当指针走到最后一条时,have_posts() 会先把指针倒回开头,再返回假。
所以你看到的现象是:
have_posts() 真 → the_post() → 输出作者 → 再 have_posts() 假(但内部已经倒带);have_posts() 又变真了(因为倒带了)→ the_post() → 输出正文。两次循环都能跑,靠的是这个“自动倒带”。
这个机制本来是为了方便(比如你想在同一个页面里重复遍历),但它也掩盖了错误写法。
问题在哪?
$wp_query 被换掉,第二次循环就读到别的东西了;所以我这次重构,把两个循环合并成一个,问题就消失了。
有一种情况确实需要:你查了一组文章,想把它们分两段输出(比如前 3 篇大图,后面小图列表)。
做法是用 rewind_posts() 手动倒带:
<?php
$q = new WP_Query( array( 'posts_per_page' => 9 ) );
// 第一段:前 3 篇
$i = 0;
while ( $q->have_posts() ) {
$q->the_post();
if ( $i < 3 ) { /* 大图 */ }
$i++;
}
$q->rewind_posts(); // 倒带
// 第二段:全部 9 篇
while ( $q->have_posts() ) {
$q->the_post();
/* 小图列表 */
}
wp_reset_postdata();
?>
注意:这里用的是自己的 WP_Query,不是主查询。rewind_posts() 是它自己的方法,倒带的是它自己。
主查询($wp_query)是 WordPress 自动帮你查的;自定义查询(new WP_Query)是你自己 new 出来的。
两者都有 rewind_posts(),但混着用就会出事:你倒带了主查询,可能影响别的模板逻辑;你倒带了自己的查询,主查询不受影响。
所以规则是:自己的查询,自己倒带;不要动主查询。
一句话记住:have_posts() 走到头会自动倒带,所以“两次循环”能跑,但那是靠容错;正确做法是一个循环包完,需要多次遍历就用自定义 WP_Query + rewind_posts。
下一篇讲一个更常见的坏习惯:query_posts 为什么不能用。