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

简介: 面试官:你的项目有什么亮点?我:解决了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后端面试题库大全

相关实践学习
基于函数计算快速搭建Hexo博客系统
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
相关文章
|
1月前
|
开发框架 JavaScript 安全
js开发:请解释什么是Express框架,以及它在项目中的作用。
Express是Node.js的Web开发框架,简化路由管理,支持HTTP请求处理。它采用中间件系统增强功能,如日志和错误处理,集成多种模板引擎(EJS、Jade、Pug)用于HTML渲染,并提供安全中间件提升应用安全性。其可扩展性允许选用合适插件扩展功能,加速开发进程。
|
1月前
|
JavaScript 前端开发 测试技术
使用Selenium执行JavaScript脚本:探索Web自动化的新领域
本文介绍了如何在Selenium中使用JavaScript解决自动化测试中的复杂问题。Selenium的`execute_script`函数用于同步执行JS,例如滑动页面、操作时间控件等。在滑动操作示例中,通过JS将页面滚动到底部,点击下一页并获取页面信息。对于只读时间控件,利用JS去除readonly属性并设置新日期。使用JS扩展了Selenium的功能,提高了测试效率和精准度,适用于各种自动化测试场景。
47 1
|
1月前
|
JSON JavaScript 前端开发
解决js中Long类型数据在请求与响应过程精度丢失问题(springboot项目中)
解决js中Long类型数据在请求与响应过程精度丢失问题(springboot项目中)
42 0
|
3天前
|
缓存 JavaScript 前端开发
js开发:请解释什么是Webpack,以及它在项目中的作用。
Webpack是开源的JavaScript模块打包器,用于前端项目构建,整合并优化JavaScript、CSS、图片等资源。它实现模块打包、代码分割以提升加载速度,同时进行资源优化和缓存。Webpack的插件机制可扩展功能,支持热更新以加速开发流程。
13 2
|
11天前
|
JavaScript
node.js输入项目目录结构并展示
node.js输入项目目录结构并展示
5 0
|
16天前
|
Web App开发 缓存 JavaScript
|
19天前
|
JavaScript 前端开发
EasyUi js 加载数据表格DataGrid
EasyUi js 加载数据表格DataGrid
|
24天前
|
JavaScript
理解DOM树的加载过程(js的问题)
理解DOM树的加载过程(js的问题)
10 0
|
1月前
|
敏捷开发 安全 API
C/C++ 工程师面试:如何精彩展示你的项目经验并获得高分
C/C++ 工程师面试:如何精彩展示你的项目经验并获得高分
73 0
|
1月前
|
JavaScript 前端开发 编译器
js开发: 请解释什么是Babel,以及它在项目中的作用。
**Babel是JavaScript编译器,将ES6+代码转为向后兼容版本,确保在旧环境运行。它在前端构建中不可或缺,提供语法转换、插件机制、灵活配置及丰富的生态系统,支持代码兼容性和自定义编译任务。**
18 6