搜索全站
主题 15 分钟阅读

wp_enqueue_script 正确加载 CSS 与 JS:依赖、版本、页脚

直接写 <script> 标签为什么会导致重复加载和冲突?讲清 wp_enqueue_script/wp_enqueue_style 的五个参数、依赖声明、页脚加载、WP 6.3 的 defer/async 策略与注册与队列的区别。

U
UFO_PF 2025-06-10 · 阅读 15 分钟
wp 主题
阅读提示
本文约 5,656 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:jQuery 被加载了三遍,滑块插件时好时坏

header.php 里手写了一段 <script src="...jquery.js">,主题又引了一次,某个插件又注册了一次。前台源码里 jQuery 出现三遍,导致绑定在 document 上的事件被重复注册,滑块点一次翻两页。更麻烦的是两个版本不一致,一个 3.6、一个 1.12,插件报 $(...).on is not a function。

WordPress 有一套完整的资源登记系统(WP_Dependencies),专门用来避免这类冲突。手写标签等于绕过了它。

原理分析:注册(register)与入队(enqueue)是两步

资源系统的核心是两个类:WP_Scripts 和 WP_Styles,各自维护一个”已登记资源列表”和”待输出队列”。

wp_register_script()  →  只登记:告诉系统"有这个资源,依赖谁,版本号多少"
wp_enqueue_script()   →  登记 + 加入输出队列(未注册的会顺带注册)
wp_script_is( $handle, 'enqueued' )  →  查询状态

分开两步的意义在于:可以提前注册好一批资源,由不同条件决定要不要入队,且同一个 handle 只会输出一次,重复 wp_enqueue_script() 同一 handle 不会产生两份 <script>。

依赖系统是第二层价值。声明 array( 'jquery' ) 后,系统会自动做拓扑排序,保证 jQuery 排在前面;如果依赖的 handle 从未注册,你的脚本就不会被输出(而不是报错或乱序执行)。

两个函数的完整签名:

wp_enqueue_style( string $handle, string $src = '', array $deps = array(), string|bool|null $ver = false, string $media = 'all' )
wp_enqueue_script( string $handle, string $src = '', array $deps = array(), string|bool|null $ver = false, array|bool $args = array() )

第五个参数在 WP 6.3 起了变化:原来只接受布尔值 $in_footer,现在接受数组 array( 'in_footer' => true, 'strategy' => 'defer' ),旧的布尔值仍兼容。

保姆级实操

第 1 步:按钩子加载,不要写在模板里

add_action( 'wp_enqueue_scripts', function () {
    $theme = wp_get_theme();
    $uri   = get_stylesheet_directory_uri();
    $path  = get_stylesheet_directory();

    // 主样式
    wp_enqueue_style(
        'mmf-main',
        get_stylesheet_uri(),
        array(),
        $theme->get( 'Version' )
    );

    // 主题脚本:依赖 jQuery,放页脚
    wp_enqueue_script(
        'mmf-main',
        $uri . '/assets/js/main.js',
        array( 'jquery' ),
        $theme->get( 'Version' ),
        true
    );
} );

要点:wp_enqueue_scripts(前台)、admin_enqueue_scripts(后台)、enqueue_block_editor_assets(块编辑器)、login_enqueue_scripts(登录页)四个钩子各管一段,不要混用。

第 2 步:依赖声明与版本号

// 滑块库依赖 jQuery;主题脚本依赖滑块库 → 输出顺序自动为 jquery → slick → theme
wp_register_script( 'slick', $uri . '/assets/js/slick.min.js', array( 'jquery' ), '1.8.1', true );
wp_enqueue_script( 'mmf-home', $uri . '/assets/js/home.js', array( 'slick' ), '1.0.0', true );

常用内置 handle 一览(不必自己带文件):

handle 内容
jquery WordPress 内置 jQuery(含 jQuery.noConflict() 模式)
jquery-ui-core / jquery-ui-sortable 等 jQuery UI 组件
underscore / backbone 前端工具库
wp-util 内含 wp.ajax 等工具
wp-api-fetch REST 请求封装
wp-i18n 前端翻译

版本号传 false 时会退化为 WordPress 版本号,等于长期缓存;传 null 则完全不输出 ?ver=,只在有外部缓存策略时使用。

第 3 步:WP 6.3+ 用 strategy 控制 defer/async

wp_enqueue_script(
    'mmf-main',
    $uri . '/assets/js/main.js',
    array(),
    '1.0.0',
    array(
        'in_footer' => true,
        'strategy'  => 'defer',   // 输出 <script defer>
    )
);

wp_register_script(
    'mmf-analytics',
    $uri . '/assets/js/analytics.js',
    array(),
    '1.0.0',
    array( 'strategy' => 'async' )   // 输出 <script async>
);

注意:strategy 为 async 时,脚本之间不保证执行顺序,任何”必须先执行 A 再执行 B”的组合都不要用 async;defer 会保持顺序,是可以放心用的默认项。有依赖关系的脚本会被强制降级为阻塞加载,这是核心的保护行为。

第 4 步:给内联脚本传数据

需要把 PHP 变量传给 JS 时,不要用 echo '<script>var x=...' 拼接字符串,用 wp_add_inline_script() 或 wp_localize_script():

wp_enqueue_script( 'mmf-main', $uri . '/assets/js/main.js', array( 'jquery' ), '1.0.0', true );

wp_add_inline_script(
    'mmf-main',
    'window.MMF = ' . wp_json_encode( array(
        'ajaxUrl' => admin_url( 'admin-ajax.php' ),
        'nonce'   => wp_create_nonce( 'mmf_nonce' ),
        'homeUrl' => home_url( '/' ),
        'isMobile'=> wp_is_mobile(),
    ) ) . ';',
    'before'   // 插在 main.js 之前,保证脚本一执行就能读到
);

wp_json_encode() 会做正确的转义,比手拼字符串安全得多。前端直接 MMF.ajaxUrl 即可。

第 5 步:条件加载,只在该出现的页面加载

add_action( 'wp_enqueue_scripts', function () {
    // 只在单篇文章加载评论回复脚本
    if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
        wp_enqueue_script( 'comment-reply' );
    }

    // 只在联系页加载地图脚本
    if ( is_page( 'contact' ) ) {
        wp_enqueue_script( 'mmf-map', get_stylesheet_directory_uri() . '/assets/js/map.js', array(), '1.0.0', true );
    }

    // 首页不需要的插件资源,直接注销
    if ( is_front_page() && ! is_user_logged_in() ) {
        wp_dequeue_style( 'contact-form-7' );
        wp_dequeue_script( 'contact-form-7' );
    }
} );

第 6 段这种”按需卸载”是前端性能优化里性价比最高的一招,前提是知道插件注册的 handle 名——翻插件源码里的 wp_enqueue_* 调用即可。

第 6 步:核对输出

wp eval 'wp_enqueue_script("jquery"); do_action("wp_enqueue_scripts"); global $wp_scripts; print_r($wp_scripts->queue);'

前台查看源码确认:每个 handle 只出现一次、?ver= 正确、页脚脚本确实在 </body> 前而非 <head> 里。

常见踩坑

  1. 在 header.php 里手写 <script> / <link>。 绕过依赖系统,无法去重、无法条件卸载,也无法让插件调整顺序。
  2. 直接 wp_enqueue_script('jquery') 换成 CDN 版本。 改的是核心 bundle,jQuery.noConflict() 模式与 jquery-migrate 的配套关系会乱,第三方插件大面积报错。要换版本请用 wp_register_script 覆盖 jquery handle 并保留同样的 bundle 结构。
  3. 把 wp_enqueue_scripts 写成 wp_enqueue_script。 少一个 s 是动作名与函数名的经典混淆,钩子写错等于代码从不执行。
  4. 在 functions.php 顶层直接调用 wp_enqueue_script()。 此时还没到 wp_enqueue_scripts 阶段,多数情况下不生效或状态异常。
  5. 内联脚本里直接 echo PHP 变量。 echo 'var id = ' . $_GET['id']; 是典型的 XSS 入口,一律用 wp_add_inline_script() + wp_json_encode(),转义规范见输出转义规范 esc_html esc_attr esc_url。
  6. 案例: 某站在 footer.php 里加了一段统计代码,又在插件里加载了同一套 SDK,导致 PV 翻倍、事件重复上报,数据部门排查了两周。后来统一改成在插件里 wp_register_script() 注册、主题只负责 wp_enqueue_script(),重复问题自然消失——因为 handle 相同只会输出一次。
  7. defer 之后脚本报 $ is not defined。 依赖没声明,脚本跑到时 jQuery 还没执行。永远通过第三个参数声明依赖,而不是靠加载顺序侥幸。版本号建议跟随主题版本自动变化,做法见主题版本号与子主题版本号的关系。

总结

所有前端资源都必须走 wp_enqueue_style() / wp_enqueue_script(),把依赖、版本、位置三件事交给核心去管。落地清单:钩子用对(wp_enqueue_scripts / admin_enqueue_scripts / enqueue_block_editor_assets);版本号要么用主题版本要么用 filemtime();有依赖就声明,别靠顺序;新项目统一用 array( 'in_footer' => true, 'strategy' => 'defer' );传数据用 wp_add_inline_script() 配 wp_json_encode();用不上的插件资源按页面 wp_dequeue_*() 卸载。做到这六条,前端资源冲突基本绝迹。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。