caogenbj 的 single.php 里,“相关文章”是这么写的:
$args = array(
'post_status' => 'publish',
'tag__in' => explode(',', $tags),
'post__not_in' => explode(',', $exclude_id),
'posts_per_page' => 10,
);
query_posts($args);
while( have_posts() ) { the_post(); ?>
<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php } wp_reset_query();
这段代码能跑,效果也对。但它用的是 query_posts(),而 WordPress 官方文档里明确写了:不要用 query_posts。
为什么?
它的名字叫“query posts”,但它干的不是“新查一次”,而是把主查询整个替换掉。
打个比方:主查询是你手里的那本书。query_posts 不是“借另一本书来看看”,而是把你手里这本书直接换成了另一本。换完之后,你原来读到第几页、书签夹在哪,全没了。
具体后果有三个:
调用 query_posts 之后,全局 $wp_query 变成了新的查询。原来那个“这篇文章”的主查询,没了。
这会导致:
is_single() 可能变味);主查询已经查过一次了。你 query_posts 又查一次。一个页面两次查库,白烧性能。
代码最后那句 wp_reset_query(),是为了把主查询“恢复”回去。但它恢复的只是查询对象,已经浪费的性能、已经被打乱的上下文,回不来。
用 query_posts 再 reset,本质是“先弄坏再修好”,不如一开始就别弄坏。
同样的事,用 WP_Query 写:
$related = new WP_Query( array(
'post_status' => 'publish',
'tag__in' => explode(',', $tags),
'post__not_in' => explode(',', $exclude_id),
'ignore_sticky_posts' => 1,
'posts_per_page' => 10,
) );
while ( $related->have_posts() ) {
$related->the_post(); ?>
<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php }
wp_reset_postdata();
差别在哪:
| 对比 | query_posts | new WP_Query |
|---|---|---|
| 对主查询的影响 | 覆盖 | 不影响 |
| 数据库查询次数 | 多一次 | 多一次(但只查自己) |
| 分页 | 会被搞乱 | 不影响 |
| 恢复方式 | wp_reset_query | wp_reset_postdata |
关键区别:WP_Query 是一个独立的查询对象,不碰主查询。主查询还在那儿,分页、上下文都不受影响。
循环结束后要调 wp_reset_postdata(),因为 $related->the_post() 把全局 $post 改成了相关文章。不恢复的话,后面的代码读到的就是最后一篇相关文章,不是当前文章。
下一篇专门讲这个。
caogenbj 原来的参数里有个 'caller_get_posts' => 1。这是个已经废弃的参数,WordPress 早就不认了。正确写法是 'ignore_sticky_posts' => 1(忽略置顶文章)。
我把这个也顺手改了。这种废弃参数不报错,只是“写了等于没写”,很容易一直留着。
一句话记住:query_posts 会覆盖主查询,别用;要查文章就用 new WP_Query,查完 wp_reset_postdata。
下一篇,把 WP_Query 的常用参数系统过一遍。