本文约 5,009 字,预计阅读 13 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:把 <script> 粘进 header.php 之后
最常见的做法是打开 header.php,在 </head> 前粘一段 <script>,或者干脆用某个”插入页头页脚”的插件贴进去。短期能跑,长期代价明显:主题一升级代码没了;脚本在 jQuery 之前执行,控制台满屏报错;同一个脚本被主题和插件各加载一次,事件被绑定两遍;想给脚本传一个 PHP 变量,只能用 echo 拼字符串,一不小心就写出 XSS。
规范的注入方式其实核心只涉及四个函数,而且它们互相配合能解决上面所有问题:wp_register_script() 注册、wp_enqueue_script() 排队、wp_add_inline_script() 追加内联代码或参数、wp_localize_script() 传递数据。
原理分析:排队机制解决了什么
WordPress 的脚本管理由 WP_Scripts(wp_scripts() 全局实例)负责,本质是”依赖图 + 去重”。wp_enqueue_script( $handle, $src, $deps, $ver, $args ) 里:
$handle是唯一标识,同一个 handle 只会输出一次,天然去重;$deps声明依赖(如array( 'jquery' )),核心会自动保证顺序;$ver用于缓存控制;$args或第五参为true时输出在wp_footer()处,避免阻塞渲染。
wp_add_inline_script( $handle, $data, $position ) 把一段内联代码挂在某个已注册脚本的前面(before)或后面(after),从而保证它在该脚本加载之后(或之前)执行——这比直接往 wp_head echo 安全得多,也不会因为脚本未加载而报错。
传参要避免字符串拼接。正确做法是用 wp_localize_script() 输出一个 JSON 对象(内部用 json_encode 并做转义),前端通过全局变量读取。
| 需求 | 推荐做法 | 不要这样做 |
|---|---|---|
| 加一段自己的脚本 | wp_enqueue_script 指向子主题文件 |
粘进 header.php |
| 依赖 jQuery | $deps = array('jquery') |
假设 jQuery 一定在 |
| 传 PHP 变量给 JS | wp_localize_script |
echo 拼进 script |
| 追加几行初始化代码 | wp_add_inline_script(..., 'after') |
再排一个空脚本 |
| 只在某页加载 | is_singular() 等条件判断 |
全站无条件加载 |
保姆级实操
- 把脚本放进子主题目录:例如
assets/js/theme-extra.js,不要内联在模板里。 - 注册并排队:在
wp_enqueue_scripts钩子上注册,声明依赖与版本,最后一个参数设为true输出到页脚。 - 不要在主题目录里改文件:即使只是加一段统计代码,也应通过子主题或钩子注入。
- 传参用
wp_localize_script:把 AJAX 地址、nonce、文章 ID 等一次性输出,前端从全局对象读取。 - 初始化代码用
wp_add_inline_script:挂在目标 handle 的after位置,保证顺序。 - 做条件加载:只在需要的页面排队,减少无关页面的请求与潜在冲突。
- 输出前转义:任何进入 JS 上下文的 PHP 值都要
esc_js()/wp_json_encode(),URL 用esc_url(),nonce 用wp_create_nonce()。
<?php
// 子主题 functions.php:注册 + 排队 + 传参 + 内联初始化
add_action( 'wp_enqueue_scripts', 'mimangfei_enqueue_js' );
function mimangfei_enqueue_js() {
$file = get_stylesheet_directory() . '/assets/js/theme-extra.js';
if ( ! file_exists( $file ) ) {
return;
}
wp_register_script(
'mf-theme-extra',
get_stylesheet_directory_uri() . '/assets/js/theme-extra.js',
array( 'jquery' ), // 声明依赖,核心保证加载顺序
filemtime( $file ), // 内容变化即更新版本
true // 输出到 wp_footer()
);
wp_enqueue_script( 'mf-theme-extra' );
// 安全传参:不要 echo 拼接字符串
wp_localize_script( 'mf-theme-extra', 'mfData', array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'mf_ajax_nonce' ),
'postId' => is_singular() ? get_the_ID() : 0,
'isHome' => is_front_page(),
) );
// 初始化代码挂在 after,保证脚本已加载
wp_add_inline_script(
'mf-theme-extra',
'window.mfBoot = function(){ if ( window.mfThemeExtra ) { window.mfThemeExtra.init( window.mfData ); } };',
'after'
);
}
<?php
// 条件加载:只在文章详情页与商品页排队,其余页面不加载
add_action( 'wp_enqueue_scripts', 'mimangfei_conditional_js' );
function mimangfei_conditional_js() {
if ( ! is_singular( array( 'post', 'product' ) ) ) {
return;
}
wp_enqueue_script(
'mf-single',
get_stylesheet_directory_uri() . '/assets/js/single.js',
array(),
wp_get_theme()->get( 'Version' ),
true
);
}
// 需要给脚本标签加属性时(如 async),用 script_loader_tag 过滤
add_filter( 'script_loader_tag', 'mimangfei_async_single', 10, 2 );
function mimangfei_async_single( $tag, $handle ) {
if ( 'mf-single' === $handle ) {
return str_replace( ' src=', ' async src=', $tag );
}
return $tag;
}
// assets/js/theme-extra.js
// 注意:所有 PHP 数据统一从 mfData 读取,脚本里不出现 PHP 拼接
window.mfThemeExtra = ( function () {
function init( data ) {
if ( ! data ) {
return;
}
document.querySelectorAll( '.mf-ajax-btn' ).forEach( function ( btn ) {
btn.addEventListener( 'click', function () {
fetch( data.ajaxUrl, {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams( {
action: 'mf_track',
post_id: data.postId,
nonce: data.nonce
} )
} );
} );
} );
}
return { init: init };
} )();
常见踩坑
- 直接在
header.php里贴<script>,主题升级后丢失,且无法控制加载顺序。 - 没有声明
jquery依赖,脚本先于库执行,报jQuery is not defined或$ is not a function。 - 用
echo把 PHP 变量拼进 JS 字符串,值里带引号就破坏语法,严重时会造成 XSS。 - 同一段脚本被主题和插件各加载一次,事件重复绑定(点击一次触发两次)。
- 案例:某站在
footer.php里直接 echo 了一段统计代码,并把文章标题拼进 JS 参数。某篇文章标题里带单引号,整个脚本语法错误,导致同页面后续所有脚本(含移动端菜单)全部失效。改用wp_localize_script+wp_json_encode传参后彻底解决。 - 脚本无条件全站排队,落地页也加载商品页脚本,白白增加体积与报错面。
- 用了缓存插件后,nonce 被缓存,导致 AJAX 请求一直返回
-1或403。
总结
添加自定义 JS 的标准链路是:子主题文件 → wp_register_script 声明依赖 → wp_enqueue_script 排队到页脚 → wp_localize_script 传参 → wp_add_inline_script 追加初始化。判断标准是”脚本在 Network 里只出现一次、在依赖之后执行、页面源码里没有 PHP 拼接的 JS 字符串”。行动清单:① 绝不在模板里贴 script 标签;② 显式声明依赖;③ 传参一律用 wp_localize_script;④ 按页面条件加载;⑤ 涉及 nonce 的页面排除缓存。延伸阅读 lumen-mimangfei 主题自定义 CSS 规范、主题自定义 CSS 放置位置 与 lumen-mimangfei 主题缓存兼容。
