IE11下polyfill严格模式报错问题总结

问题现象

项目运行在IE11中产生报错,错误如下:
在这里插入图片描述

该错误会阻塞页面加载,导致页面空白。

问题分析

根据报错的行和列信息找到引发错误的代码

// core-js@3.6.5
var TypedArrayConstructorsList = {
  Int8Array: 1,
  Uint8Array: 1,
  Uint8ClampedArray: 1,
  Int16Array: 2,
  Uint16Array: 2,
  Int32Array: 4,
  Uint32Array: 4,
  Float32Array: 4,
  Float64Array: 8
};

var exportTypedArrayMethod = function (KEY, property, forced) {
  if (!DESCRIPTORS) return;
  // 当 KEY 为 slice 方法时,forced 值为true
  if (forced) for (var ARRAY in TypedArrayConstructorsList) {
    var TypedArrayConstructor = global[ARRAY];
    if (TypedArrayConstructor && has(TypedArrayConstructor.prototype, KEY)) {
      delete TypedArrayConstructor.prototype[KEY]; // 报错行
    }
  }
  if (!TypedArrayPrototype[KEY] || forced) {
    redefine(TypedArrayPrototype, KEY, forced ? property
      : NATIVE_ARRAY_BUFFER_VIEWS && Int8ArrayPrototype[KEY] || property);
  }
};

给报错行打上断点得到:
在这里插入图片描述

现在已明确在具体执行下面这行代码时报错

// 这行代码执行的前提是 Float32Array 存在并且 Float32Array 的原型上存在 slice 方法
delete Float32Array.prototype['slice'];

而在 use strict 严格模式下对象属性删除是有限制的,只有configurable设置为true的对象属性,才能被删除。

'use strict'; 
Object.defineProperty(Array.prototype, 'fn', { value: function() {return 1;} }); // configurable 默认为false
delete Array.prototype.fn; // 报错:strict 模式下不允许对“fn”调用 Delete

设置 configurationtrue

'use strict'; 
Object.defineProperty(Array.prototype, 'fn', { configurable: true , value: function() {return 1;} });
delete Array.prototype.fn; // 删除成功

至此找到了报错的真正原因。

上面还有一个容易忽略的点,就是IE11下 Float32Array 的原型上的 slice 方法是谁给添加的?

正常情况下应该是不存在 slice 方法的
在这里插入图片描述

下面主要围绕这个问题进行排查

问题排查

排查项目中页面

逐个注释掉 router/config.tsx 中的路由,观察是否报错,经过排查发现,当项目中没有页面时依旧报错,因此排除页面引发该错误的可能。

第三方依赖排查

逐个注释掉 pages/main/index.html 中依赖的第三方依赖资源文件,发现当去掉高德地图js资源的时候,页面可以正常加载并且无报错

<script
    crossorigin="anonymous"
    type="text/javascript"
    src="https://webapi.amap.com/maps?v=2.0&key=xxx"
  ></script>

定位高德地图返回的资源内容,定位关键词 Float32Array.prototype['slice'],得到

// 注意 defineProperty 未配置 configurable 属性,那么默认为 false
Float32Array.prototype.slice || Object.defineProperty(Float32Array.prototype, "slice", {
  value: function(t, e) {
    return new Float32Array(Array.prototype.slice.call(this, t, e))
  }
})

执行上述代码将为 Float32Array.prototype 上添加自定义的 slice 方法,添加后如果进入到 polyfill-vendor 里面,那么由于 Float32Array && has(Float32Array.prototype, 'slice') 条件返回为 true,必然会执行删除逻辑 delete Float32Array.prototype['slice']; ,由于在严格模式下 slice 方法并未设置 configurable ,因此导致最终的报错。

上面加粗部分,从代码执行结果上来看确实是进入了 polyfill-vendor 里面,那么在代码执行流程上如何证明?

先看一下项目中中资源加载的顺序(已删除无关资源的引入)

  <script
    crossorigin="anonymous"
    src="//static.resource.com/project/js/polyfill-vendors.js"
  ></script>
  <script
    crossorigin="anonymous"
    type="text/javascript"
    src="https://webapi.amap.com/maps?v=2.0&key=xxx"
  ></script>
  <script
    crossorigin="anonymous"
    src="//static.resource.com/project/js/pages/main.js"
  ></script>

加载polyfill-vendors.js 后,将会自动执行 slicepolyfill 代码

"use strict";
var $slice = [].slice;

var FORCED = fails(function () {
  // eslint-disable-next-line no-undef
  new Int8Array(1).slice();
});

// `%TypedArray%.prototype.slice` method
// https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.slice
exportTypedArrayMethod('slice', function slice(start, end) { // 处理 slice 方法的polyfill
  var list = $slice.call(aTypedArray(this), start, end);
  var C = speciesConstructor(this, this.constructor);
  var index = 0;
  var length = list.length;
  var result = new (aTypedArrayConstructor(C))(length);
  while (length > index) result[index] = list[index++];
  return result;
}, FORCED);

那么如果 amap 先于 polyfill-vendors 加载完毕,那么在 polyfill-vendors 执行的情况下,就会触发报错。

解决方案

  1. AMap 资源加载设置 defer 属性,如果存在多个 defer script 标签,浏览器(IE9及以下除外)会保证它们按照在 HTML 中出现的顺序执行,不会破坏 JS 脚本之间的依赖关系。
    在这里插入图片描述

  2. 由于 AMap 目前只在个别页面使用,因此只需要在实际用到的地方按需加载即可,不需要跟随页面强依赖资源一起加载。

    // 异步加载远程资源
    function loadScript(url) {
      return new Promise((resolve, reject) => {
        var script = document.createElement('script');
        script.type = 'text/javascript';
    
        if (script.readyState) {
          script.onreadystatechange = function () {
            if (script.readyState == 'complete' || script.readyState == 'loaded') {
              resolve();
            }
          };
        } else {
          script.onload = function () {
            resolve();
          };
        }
        // 开始异步下载的js文件
        script.src = url;
        document.head.appendChild(script);
      });
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值