首页 小组 问答 话题 好文 素材 用户 唠叨 我的社区

[分享]动态script标签引入方式性能差异分析

天启Lv.1普通用户
2024-08-31 22:53:04
0
14

背景

在不支持打包构建的前端历史项目中,我们经常会有动态引入script的诉求,为了书写方便,开发的时候会习惯性的用jQuery生成script标签再append到body中,并将其视为和原生写法功能完全一致

// jQuery
var $script = $('<script src="xxx.js">');
$('body').append($script);

// ECMAScript
var script = document.createElement('script');
script.src = 'xxx.js';
document.body.appendChild(script);

但它们真的一致么?jQuery在这里什么都没做么?不同写法对我们又会有怎样的影响呢?

先说结论

jQuery写法与原生相比差异如下:

  1. 并非通过标准的jsonp方式加载JS内容,而是通过一个xhr请求

  2. 此xhr是同步的,而动态script则是强制async的

  3. 非法的type赋值会让xhr请求不发送( 不写type是合法的,要写就写对

除了那个同步异步的操作,是不是看起来对我们实际开发影响不大?如果真不大我也不会有心情探究这个问题了,这里需要额外补充一个xhr和jsonp请求JS内容的差异点:

  1. 重点:在iOS环境下,浏览器cache的JS请求并不会被用于xhr,缓存的方式包括刚请求过、提前prefetch等
    所以如果对同步插入没有诉求,能不用jQuery插入就不要用

实际场景

image.png
image.png
image.png

上面我提到了,iOS下的xhr请求JS是不会走缓存的,那么这个影响到底大不大呢?
我们的项目因为一些特殊原因,会在页面内容显示前动态插入7-9个script标签,且均是通过jQuery插入的,大小1KB - 200KB不等,不算小但也没有特别夸张,在PC模拟环境下加载和执行耗时如下:

如果允许xhr使用缓存的话,对应的耗时如下:

但这部分JS在移动端用户的实际使用场景下,加载和执行的实际统计时间如下:

样本里安卓和iOS用户数量基本一致,也没有太多极值,可以理解为比安卓多的部分就是本原因导致的。这可不是95线、75线那种较差环境下的指标,光正常的均值和中位数都能干到1s以上,要知道页面打开满共才2-3s,如果碰到网稍微差一点,那这个页面就奔着5s+去了

处理方案

理想的情况当然是把这些JS原位置替换成原生写法即可,但在我们项目里有两个困难点:

  1. 这些动态script是有同步要求的,必须在引入的地方执行,不然会缺失必要的上下文

  2. 引入的重复书写非常多(300+),而且因为历史原因,代码还存储在数据库而非代码库中,无法做到安全的全量修改
    最后我的方案是:

  3. 修改jQuery源码,拦截这些已确定的script,不让其走xhr请求

  4. 在原引入逻辑的上方通过script标签正常引入确认拦截的JS

  5. 将拦截的script的非Function声明部分都装在各自的init方法中,在原来引入的地方执行init。就类似于引入Vuex后需要在合适的时机执行Vue.ues原理一样,基于Vue的库都会有启动函数,我也将我们动态的几个JS的立即执行部分也分别装进了各自的启动函数,并在原位置执行,模拟同步执行的过程,以便减少逻辑影响
    最后的效果就是常见的动态script都会通过jsonp标准模式而非xhr的方式来请求下发,并顺利借道浏览器缓存,预期iOS加载时常可以降低到Android的水平

jQuery源码分析

上面我说针对动态script,jQuery会走xhr来进行请求,但细心的小伙伴在HTML里一搜,发现明明有对应<script>标签呀,但网络上也发了xhr的请求,这是怎么回事呢?这些就需要看看源码了

// 我们使用jQuery插入动态script

// 注意通过jQuery生成的这个script和原生是存在细微差异的
// 例如用原生方式插入本script标签也不会发jsonp请求,原因我还没找到
// 所以要走标准jsonp,就都需要用原生书写,不要偷懒
var $script = $('<script src="xxx.js">');
$('body').append($script);
// jQuery源码(截取并增加注释)

// append方法
append: function() {
    // 发xhr的逻辑在这个domManip中,我们等下看
    return domManip( this, arguments, function( elem ) {
      // 这里是回调函数,会在针对script标签进行加工后、发xhr前执行
      if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
        var target = manipulationTarget( this, elem );
        // 原生的插入动作,也正是有这句,才让我们可以在HTML中搜到script标签结构
        // 但为什么标签明明在,却没发jsonp请求呢?往下看
        target.appendChild( elem );
      }
    });
 },
 
 // domManip重要部分截取
 function domManip (...) {
 ...
 // 所有script标签都被disableScript方法加工了
 scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
 ...
 // 发送对应xhr请求
 jQuery._evalUrl( node.src );
 ...
天启
天启

52 天前

签名 : 大运河向南是我家   14       0
评论
站长交流