面试官:你的项目有什么亮点?我:解决了JS脚本加载失败的问题!

本文涉及的产品
.cn 域名,1个 12个月
简介: 面试官:你的项目有什么亮点?我:解决了JS脚本加载失败的问题!

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库  web前端面试题库 VS java后端面试题库大全

面试官:你的项目有什么亮点?解决了什么问题?

你:嗯......

面试官:回去等通知吧。

上面的对话真可谓是面试环节最令人揪心的场景了,如果你想要从容面对面试时的项目提问环节,那么你一定要看完今天这篇文章。

我是渡一子辰老师,今天带你解决js脚本加载失败的问题。

JS 加载失败的危害

我们都知道,现代的网页离不开 JS,它可以让页面变得更加动态和交互。

但是,JS 也有可能加载失败,导致页面样式错乱,甚至白屏无法使用。

这对用户体验是非常不利的,尤其是对于单页应用,如果 JS 加载不出来,用户就无法继续浏览页面了。

那么,JS 加载失败的原因有哪些呢?

可能是网络不稳定,可能是服务器出错,可能是跨域问题,也可能是其他未知的因素。

我们无法控制这些原因,但我们可以通过一个简单的方法来解决加载失败的问题:重试!

JS 加载失败的解决方案

重试就是当 JS 加载失败时,重新请求一次或多次,直到成功为止。

这样可以增加加载成功的概率,也可以避免用户看到错误的页面。

那么,怎么实现重试呢?其实只需要解决两个问题:

  1. 什么时候重试?
  2. 如何重试?

什么时候重试?

要知道什么时候重试,我们就要知道什么时候 JS 加载失败。

最简单的就是给 script 标签,加一个 onerror 事件。当出现错误的时候 script 会触发这个事件。

为了方便测试,我们本地有三个 JS,名字分别为 1、2、3,分别输出 1、2、3。

<script onerror="console.log(123)" src="http://127.0.0.1:5500/js/1.js"></script>
<script onerror="console.log(123)" src="http://other-domain-one.com/js/2.js"></script>
<script onerror="console.log(123)" src="http://127.0.0.1:5500/js/3.js"></script>

这样做虽然可以,但不是最好的,会比较麻烦,又特别是在工程化的环境里边,这些 script 都是自动生成的,要加上 onerror 事件的话就会很复杂。

那么有没有更好的方法呢?当然有!我们可以利用事件委托的原理,在 window 上监听 error 事件,然后判断是否是 script 标签引起的错误。

注意:这里我们要在第三个参数传入 true,表示在捕获阶段触发事件,因为 error 事件不会冒泡。

<script src="http://127.0.0.1:5500/js/1.js"></script>
<script src="http://other-domain-one.com/js/2.js"></script>
<script src="http://127.0.0.1:5500/js/3.js"></script>
<script>
  window.addEventListener('error', (event) => {
    console.log('有错误!');
  }, true)
</script>

但是我们能这么写吗?同学们思考几秒钟。

其实是不行的,因为当前面的 JS 失败的时候,error 事件还没有注册,所以应该在最上方。

<script>
  window.addEventListener('error', (event) => {
    console.log('有错误!');
  }, true) 
</script>
<script src="http://127.0.0.1:5500/js/1.js"></script>
<script src="http://other-domain-one.com/js/2.js"></script>
<script src="http://127.0.0.1:5500/js/3.js"></script>

可以看到,我们已经触发 error 事件了。

但是这样还不够精确,因为 error 事件可能由其他原因引起,比如图片加载失败或者 JS 代码中抛出异常。

我们怎么区分呢?我们打印一下 error 的 event 值,看看它们有什么区别。

可以看到,图片和 script 引起的错误都是 Event 对象,而 JS 代码中抛出的错误是 ErrorEvent 对象。

并且 Event 对象中有一个 target 属性,指向触发错误的元素。

所以我们可以根据这两个特征来判断是否是 script 标签引起的错误。

<script>
  window.addEventListener('error', (event) => {
    // 拿到触发错误的标签
    const tag = event.target;
    // 便签的名称必须是 'SCRIPT' 与 event 错误的类型不能是 ErrorEvent
    if (tag.tagName === 'SCRIPT' && !(event instanceof ErrorEvent)) {
      console.log('script 加载错误');
    }
  }, true) 
</script>

这样我们就可以准确地捕获到 script 加载失败的情况了。

如何重试?

实现重试,我们就要重新创建一个 script 元素,并且修改它的 src 属性为一个新的域名。

为什么要修改域名呢?因为之前加载失败的域名可能已经失效了,所以我们需要准备一些备用域名,在加载失败时依次尝试。

那么我们需要记录以下三个信息:

  1. 备用域名列表
  2. 要重试的 script 的路径
  3. 已经重试过几次( 为了知道下一次要重试的备用域名是什么 )。

根据这些信息,我们可以写出以下代码:

<script>
  // 备用域名列表
  const domains = [
    'other-domain-two.com',
    'other-domain-three.com',
    'other-domain-four.com',
    '127.0.0.1:5500',
  ];
  // 重试的信息
  const retryInfo = {};
  window.addEventListener('error', (event) => {
    const tag = event.target;
    if (tag.tagName === 'SCRIPT' && !(event instanceof ErrorEvent)) {
      // 首先我们要知道是谁失败了,他请求的 js 是什么
      // 可以通过 url.pathnam 得到请求的 js 的名字
      const url = new URL(tag.src);
      // 我们判断一下重发的信息里有没有重试过这个 js
      if (!retryInfo[url.pathname]) {
        // 没重试过就给它添加一个
        retryInfo[url.pathname] = {
          times: 0, // 第几次重试从 0 开始
          nextIndex: 0, // 重试的域名也从 0 开始
        };
      }
      // 取出要重试的信息
      const info = retryInfo[url.pathname];
      // 这里我们要判断一下,重试的次数是否小于域名的列表长度,防止所有域名都失败时一直重复重试
      if (info.times < domains.length) {
        // 重试就要生成一个新的元素
        const script = document.createElement('script')
        // 那我们要重试呢就是替换一下失败的域名,所以可以利用 url.host,把要重试的域名替换它,
        url.host = domains[info.nextIndex]
        // 然后将新的 url 添加到新的 script 的 src 里
        script.src = url.toString()
        // 将新的 script 呢加入到失败的 script 之前
        document.body.insertBefore(script, tag)
        // 最后不要忘记重试信息的索引都要加 1
        info.times++
        info.nextIndex++;
      }
    }
  }, true) 
</script>

可以看到 2 已经输出了,但是顺序不对,应该是 1、2、3 的顺序,JS 的执行顺序是很重要的,因为他们之间可能有依赖关系,比如说 3 里有依赖 2 的东西,那么先加载 3 就会出现问题了。

出现这个问题的原因就在于新加入的这个元素没有阻塞后续的加载,也就是说我们创建的这个元素必须要它阻塞页面后续的加载。

这里就用到了一个同学们一定接触过,但是早就不使用的东西,同学思考一下,看能不能想到。

其实它叫做 document.write(),这个就会阻塞页面的加载。

<script>
  const domains = [
    'other-domain-two.com',
    'other-domain-three.com',
    '127.0.0.1:5500',
  ];
  const retryInfo = {};
  window.addEventListener('error', (event) => {
    const tag = event.target;
    if (tag.tagName === 'SCRIPT' && !(event instanceof ErrorEvent)) {
      const url = new URL(tag.src);
      if (!retryInfo[url.pathname]) {
        // 没重试过就给它添加一个
        retryInfo[url.pathname] = {
          times: 0, // 第几次重试从 0 开始
          nextIndex: 0, // 重试的域名也从 0 开始
        };
      }
      const info = retryInfo[url.pathname];
      if (info.times < domains.length) {
        const script = document.createElement('script')
        url.host = domains[info.nextIndex]
        // 阻塞页面后续的加载
        // 因为我们是写在 script 标签里 所以要转译一下,否则会被认为是 script 标签的结束
        document.write(`<script src="${url.toString()}"></script>`)
        info.times++
        info.nextIndex++;
      }
    }
  }, true) 
</script>

现在再看顺序就正常了,这里的警告是因为 document.write() 有阻塞,但是我们要的就是阻塞,所以就不用管他了。

总结

现在我们的问题已经解决了,但其实仍然可以再深入的去挖掘,比如 script 元素有 defer 怎么办?有 async 怎么办?这里就不展开叙述了。

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库  web前端面试题库 VS java后端面试题库大全

相关文章
|
11天前
|
JSON 移动开发 JavaScript
在浏览器执行js脚本的两种方式
【10月更文挑战第20天】本文介绍了在浏览器中执行HTTP请求的两种方式:`fetch`和`XMLHttpRequest`。`fetch`支持GET和POST请求,返回Promise对象,可以方便地处理异步操作。`XMLHttpRequest`则通过回调函数处理请求结果,适用于需要兼容旧浏览器的场景。文中还提供了具体的代码示例。
在浏览器执行js脚本的两种方式
|
10天前
|
JSON JavaScript 前端开发
[JS]面试官:你的简历上写着熟悉jsonp,那你说说它的底层逻辑是怎样的?
本文介绍了JSONP的工作原理及其在解决跨域请求中的应用。首先解释了同源策略的概念,然后通过多个示例详细阐述了JSONP如何通过动态解释服务端返回的JavaScript脚本来实现跨域数据交互。文章还探讨了使用jQuery的`$.ajax`方法封装JSONP请求的方式,并提供了具体的代码示例。最后,通过一个更复杂的示例展示了如何处理JSON格式的响应数据。
23 2
[JS]面试官:你的简历上写着熟悉jsonp,那你说说它的底层逻辑是怎样的?
|
1天前
|
缓存 前端开发 JavaScript
优化CSS和JavaScript加载
Next.js和Nuxt.js在优化CSS和JavaScript加载方面提供了多种策略和工具。Next.js通过代码拆分、图片优化和特定的CSS/JavaScript优化措施提升性能;Nuxt.js则通过代码分割、懒加载、预渲染静态页面、Webpack配置和服务端缓存来实现优化。两者均能有效提高应用性能。
|
22天前
|
JavaScript 测试技术 API
跟随通义灵码一步步升级vue2(js)项目到vue3版本
Vue 3 相较于 Vue 2 在性能、特性和开发体验上都有显著提升。本文介绍了如何利用通义灵码逐步将 Vue 2 项目升级到 Vue 3,包括备份项目、了解新特性、选择升级方式、升级依赖、迁移组件和全局 API、调整测试代码等步骤,并提供了注意事项和常见问题的解决方案。
|
2月前
|
算法 JavaScript 前端开发
第一个算法项目 | JS实现并查集迷宫算法Demo学习
本文是关于使用JavaScript实现并查集迷宫算法的中国象棋demo的学习记录,包括项目运行方法、知识点梳理、代码赏析以及相关CSS样式表文件的介绍。
第一个算法项目 | JS实现并查集迷宫算法Demo学习
|
25天前
|
JavaScript 前端开发 测试技术
JavaScript与TypeScript:为何TypeScript成为大型项目的首选
JavaScript与TypeScript:为何TypeScript成为大型项目的首选
28 1
|
26天前
|
人工智能 JavaScript 网络安全
ToB项目身份认证AD集成(三完):利用ldap.js实现与windows AD对接实现用户搜索、认证、密码修改等功能 - 以及针对中文转义问题的补丁方法
本文详细介绍了如何使用 `ldapjs` 库在 Node.js 中实现与 Windows AD 的交互,包括用户搜索、身份验证、密码修改和重置等功能。通过创建 `LdapService` 类,提供了与 AD 服务器通信的完整解决方案,同时解决了中文字段在 LDAP 操作中被转义的问题。
|
28天前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
2月前
vite.config.js中vite.defineConfig is not defined以及创建最新版本的vite项目
本文讨论了在配置Vite项目时遇到的`vite.defineConfig is not defined`错误,这通常是由于缺少必要的导入语句导致的。文章还涉及了如何创建最新版本的Vite项目以及如何处理`configEnv is not defined`的问题。
100 3
vite.config.js中vite.defineConfig is not defined以及创建最新版本的vite项目
|
1月前
|
JavaScript 前端开发
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
39 0
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】