各位观众,各位听众,晚上好!我是今天的主讲人,江湖人称“代码老司机”,很高兴能和大家一起扒一扒WordPress的“老司机”函数——wp_nav_menu()
,看看它到底是怎么把一个看起来简单的导航菜单给“揉”出来的。
今天咱们就来一次深度的代码解剖,保证让你看完之后,也能成为“菜单老司机”。咱们尽量用通俗易懂的语言,加上大量的代码示例,让大家彻底搞明白wp_nav_menu()
的运行机制。
一、wp_nav_menu()
是个什么玩意儿?
首先,咱们得搞清楚wp_nav_menu()
是干嘛的。简单来说,它就是一个函数,负责根据你在WordPress后台设置的导航菜单,生成HTML代码,并在你的网站前端显示出来。
就像你点外卖,你点的是“红烧肉盖饭”,外卖小哥送来的就是一份热气腾腾的“红烧肉盖饭”。wp_nav_menu()
就相当于外卖小哥,你告诉它你要显示哪个菜单,它就给你生成对应的HTML代码。
二、参数大揭秘:wp_nav_menu()
都吃些什么?
wp_nav_menu()
函数接受一个数组作为参数,这个数组里包含了各种选项,告诉函数你想怎么定制这个菜单。 就像你点外卖的时候可以备注“不要香菜”、“多放辣椒”一样。
常用的参数如下表所示:
参数名 | 类型 | 描述 | 默认值 |
---|---|---|---|
menu |
string/int | 指定要显示的菜单。可以是菜单的名称、ID或slug。 | 空 |
menu_class |
string | 指定<ul> 元素的CSS class。 |
menu |
menu_id |
string | 指定<ul> 元素的ID。 |
menu-{menu slug} |
container |
string | 指定包裹菜单的容器元素。可以是div 、nav 等。 |
div |
container_class |
string | 指定容器元素的CSS class。 | menu-{menu slug}-container |
container_id |
string | 指定容器元素的ID。 | 空 |
before |
string | 在每个菜单项链接之前添加的内容。 | 空 |
after |
string | 在每个菜单项链接之后添加的内容。 | 空 |
link_before |
string | 在菜单项链接文本之前添加的内容。 | 空 |
link_after |
string | 在菜单项链接文本之后添加的内容。 | 空 |
echo |
bool | 是否直接输出HTML代码。如果设置为false ,则返回HTML代码字符串。 |
true |
fallback_cb |
string | 如果指定的菜单不存在,则调用的回调函数。 | wp_page_menu |
items_wrap |
string | 用于包裹菜单项的HTML代码。使用%1$s 表示菜单项的开始标记,%2$s 表示菜单项的结束标记。 |
<ul id="%1$s" class="%2$s">%3$s</ul> |
depth |
int | 菜单的深度。0 表示显示所有层级。 |
0 |
walker |
object | 用于遍历菜单项的对象。可以自定义walker来控制菜单的渲染方式。 | 空 |
theme_location |
string | 主题中注册的菜单位置。如果设置了此参数,则menu 参数将被忽略。 |
空 |
举个例子:
<?php
$args = array(
'theme_location' => 'primary',
'menu_class' => 'nav-menu',
'container' => 'nav',
'container_class' => 'main-nav',
'fallback_cb' => 'wp_page_menu',
);
wp_nav_menu( $args );
?>
这段代码的意思是:
- 使用主题位置为
primary
的菜单。 <ul>
元素的CSS class为nav-menu
。- 使用
<nav>
元素包裹菜单。 <nav>
元素的CSS class为main-nav
。- 如果找不到指定的菜单,则调用
wp_page_menu
函数。
三、源码剖析:wp_nav_menu()
到底是怎么工作的?
好了,参数咱们都了解了,现在咱们来深入源码,看看wp_nav_menu()
是如何一步一步把菜单“揉”出来的。
wp_nav_menu()
函数的源码位于wp-includes/nav-menu-template.php
文件中。 咱们简化一下,只保留核心逻辑,一步步分析:
-
参数合并与处理:
wp_nav_menu()
首先会把你传递的参数和默认参数合并,形成一个完整的参数数组。 这样可以确保所有需要的参数都有值。$defaults = array( 'menu' => '', 'container' => 'div', 'container_class' => 'menu-{menu slug}-container', 'container_id' => '', 'menu_class' => 'menu', 'menu_id' => '', 'echo' => true, 'fallback_cb' => 'wp_page_menu', 'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>', 'depth' => 0, 'walker' => '', 'theme_location' => '' ); $args = wp_parse_args( $args, $defaults ); //合并参数
-
确定要显示的菜单:
wp_nav_menu()
会根据你提供的menu
参数或theme_location
参数,来确定要显示哪个菜单。 如果你设置了theme_location
,它会优先使用theme_location
指定的菜单。 如果没有设置,或者指定的菜单不存在,它会尝试使用menu
参数指定的菜单。 如果还是找不到菜单,它会调用fallback_cb
参数指定的回调函数,通常是wp_page_menu
,用于显示页面菜单。if ( ! empty( $args['theme_location'] ) ) { $locations = get_nav_menu_locations(); if ( isset( $locations[ $args['theme_location'] ] ) ) { $menu = wp_get_nav_menu_object( $locations[ $args['theme_location'] ] ); } } elseif ( ! empty( $args['menu'] ) ) { $menu = wp_get_nav_menu_object( $args['menu'] ); }
-
获取菜单项:
确定了要显示的菜单之后,
wp_nav_menu()
会调用wp_get_nav_menu_items()
函数,获取菜单的所有菜单项。wp_get_nav_menu_items()
会从数据库中读取菜单项的信息,包括菜单项的标题、链接、父级ID等等。$menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
-
使用 Walker 类遍历菜单项并生成HTML:
这才是
wp_nav_menu()
最核心的部分。它会使用一个名为Walker
的类,来遍历菜单项,并根据菜单项的信息生成HTML代码。Walker
类是一个抽象类,你需要继承它,并重写它的方法,才能自定义菜单的渲染方式。 WordPress默认提供了一个Walker_Nav_Menu
类,用于生成标准的导航菜单。if ( empty( $args['walker'] ) ) { $walker = new Walker_Nav_Menu; } else { $walker = $args['walker']; } $items = walk_nav_menu_tree( $menu_items, $args['depth'], $args );
walk_nav_menu_tree()
函数会递归地遍历菜单项,并调用Walker
类的方法,生成HTML代码。Walker
类主要有以下几个方法:start_lvl()
:在开始一个子菜单时调用。end_lvl()
:在结束一个子菜单时调用。start_el()
:在开始一个菜单项时调用。end_el()
:在结束一个菜单项时调用。
咱们来看一下
Walker_Nav_Menu
类的start_el()
方法的简化版:function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) { $indent = ( $depth ) ? str_repeat( "t", $depth ) : ''; $class_names = $value = ''; $classes = empty( $item->classes ) ? array() : (array) $item->classes; $classes[] = 'menu-item-' . $item->ID; $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) ); $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : ''; $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth ); $id = $id ? ' id="' . esc_attr( $id ) . '"' : ''; $output .= $indent . '<li' . $id . $value . $class_names .'>'; $atts = array(); $atts['title'] = ! empty( $item->attr_title ) ? $item->attr_title : ''; $atts['target'] = ! empty( $item->target ) ? $item->target : ''; $atts['rel'] = ! empty( $item->xfn ) ? $item->xfn : ''; $atts['href'] = ! empty( $item->url ) ? $item->url : ''; $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth ); $attributes = ''; foreach ( $atts as $attr => $value ) { if ( ! empty( $value ) ) { $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value ); $attributes .= ' ' . $attr . '="' . $value . '"'; } } $title = apply_filters( 'the_title', $item->title, $item->ID ); $item_output = $args->before; $item_output .= '<a'. $attributes .'>'; $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after; $item_output .= '</a>'; $item_output .= $args->after; $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args ); }
这段代码的作用是:
- 根据菜单项的属性,生成
<li>
元素的CSS class和ID。 - 生成
<a>
元素的href、title、target等属性。 - 将菜单项的标题包裹在
<a>
元素中。 - 将所有的HTML代码拼接起来,并添加到
$output
变量中。
end_el()
方法则负责关闭<li>
元素。通过
Walker
类的遍历,最终会生成一个完整的HTML字符串,包含了所有的菜单项。 -
输出HTML:
最后,
wp_nav_menu()
会根据echo
参数的值,决定是直接输出HTML代码,还是返回HTML代码字符串。 如果echo
参数为true
,则直接输出HTML代码。 如果echo
参数为false
,则返回HTML代码字符串。$output = apply_filters( 'wp_nav_menu', $items, $args ); if ( $args['echo'] ) { echo $output; } else { return $output; }
四、自定义菜单:玩转 Walker 类
wp_nav_menu()
的强大之处在于它的可定制性。 你可以通过自定义Walker
类,来控制菜单的渲染方式。 就像你可以自己设计外卖的包装盒一样。
例如,你可以创建一个自定义的Walker
类,来给每个菜单项添加一个图标:
class My_Walker_Nav_Menu extends Walker_Nav_Menu {
function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
parent::start_el( $output, $item, $depth, $args, $id );
$output = str_replace( '<a', '<a><i class="fa fa-home"></i> ', $output ); // 添加图标
}
}
$args = array(
'theme_location' => 'primary',
'walker' => new My_Walker_Nav_Menu(),
);
wp_nav_menu( $args );
这段代码的意思是:
- 创建一个名为
My_Walker_Nav_Menu
的类,继承自Walker_Nav_Menu
。 - 重写
start_el()
方法,在每个菜单项的链接之前添加一个Font Awesome的home图标。 - 在调用
wp_nav_menu()
时,将walker
参数设置为My_Walker_Nav_Menu
类的实例。
通过这种方式,你可以完全控制菜单的HTML结构,实现各种各样的定制效果。
五、案例分析:一个完整的自定义菜单
为了让大家更好地理解wp_nav_menu()
的用法,咱们来分析一个完整的自定义菜单的例子。
假设我们需要创建一个如下的菜单:
<nav class="custom-menu">
<ul>
<li class="menu-item menu-item-home current-menu-item"><a href="/">首页</a></li>
<li class="menu-item menu-item-about"><a href="/about">关于我们</a></li>
<li class="menu-item menu-item-products"><a href="/products">产品</a>
<ul class="sub-menu">
<li class="menu-item menu-item-product-1"><a href="/products/product-1">产品1</a></li>
<li class="menu-item menu-item-product-2"><a href="/products/product-2">产品2</a></li>
</ul>
</li>
<li class="menu-item menu-item-contact"><a href="/contact">联系我们</a></li>
</ul>
</nav>
我们可以使用以下代码来实现:
<?php
class Custom_Walker_Nav_Menu extends Walker_Nav_Menu {
function start_lvl( &$output, $depth = 0, $args = array() ) {
$indent = str_repeat( "t", $depth );
$output .= "n$indent<ul class="sub-menu">n";
}
function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
$indent = ( $depth ) ? str_repeat( "t", $depth ) : '';
$class_names = $value = '';
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$classes[] = 'menu-item-' . $item->ID;
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
$id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth );
$id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
$output .= $indent . '<li' . $id . $value . $class_names .'>';
$atts = array();
$atts['title'] = ! empty( $item->attr_title ) ? $item->attr_title : '';
$atts['target'] = ! empty( $item->target ) ? $item->target : '';
$atts['rel'] = ! empty( $item->xfn ) ? $item->xfn : '';
$atts['href'] = ! empty( $item->url ) ? $item->url : '';
$atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );
$attributes = '';
foreach ( $atts as $attr => $value ) {
if ( ! empty( $value ) ) {
$value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
$attributes .= ' ' . $attr . '="' . $value . '"';
}
}
$item_output = $args->before;
$item_output .= '<a'. $attributes .'>';
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</a>';
$item_output .= $args->after;
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
$args = array(
'theme_location' => 'primary',
'container' => 'nav',
'container_class' => 'custom-menu',
'menu_class' => '', //不再需要默认的'menu'类
'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>', //自定义ul的结构
'walker' => new Custom_Walker_Nav_Menu(),
);
wp_nav_menu( $args );
?>
这段代码的关键在于:
- 自定义
Walker
类: 我们创建了一个Custom_Walker_Nav_Menu
类,继承自Walker_Nav_Menu
,并重写了start_lvl()
和start_el()
方法,用于控制菜单的HTML结构。 - 设置
container
和container_class
参数: 我们将container
参数设置为nav
,并将container_class
参数设置为custom-menu
,用于指定包裹菜单的容器元素和CSS class。 - 设置
menu_class
参数为空: 我们将menu_class
参数设置为空,因为我们不需要默认的menu
类。 - 设置
items_wrap
参数: 我们自定义了items_wrap
参数,修改ul的结构。 - 将
walker
参数设置为自定义的Walker
类: 我们将walker
参数设置为Custom_Walker_Nav_Menu
类的实例,以便使用自定义的Walker
类来渲染菜单。
六、总结:wp_nav_menu()
的奥秘
通过以上的分析,相信大家对wp_nav_menu()
的运行机制有了更深入的了解。 简单总结一下:
wp_nav_menu()
是一个用于生成导航菜单的函数。- 它接受一个数组作为参数,用于定制菜单的各种选项。
- 它会根据你提供的参数,确定要显示的菜单,并获取菜单的所有菜单项。
- 它使用
Walker
类遍历菜单项,并生成HTML代码。 - 你可以通过自定义
Walker
类,来控制菜单的渲染方式。
掌握了wp_nav_menu()
,你就掌握了WordPress菜单的“命脉”,可以轻松地创建各种各样的自定义菜单,让你的网站更加个性化。
好了,今天的讲座就到这里。 希望大家都能成为“菜单老司机”,在WordPress的世界里自由驰骋! 感谢大家的聆听!