2019独角兽企业重金招聘Python工程师标准>>>

本文由小芭乐发表

前端的同学如果用 window.onerror 事件做过监控,应该知道,跨域的脚本会给出 "Script Error." 提示,拿不到具体的错误信息和堆栈信息。

这里读者可以跟我一起做一个实验,来深入了解这个事情。先做一下实验准备:

app.js

创建一个 Node APP,只做静态服务器,提供两个端口用于做跨域实验。

const express = require('express');const app = express();app.use(express.static('./public'));app.listen(3000);
app.listen(4000);

public/index.html

创建一个静态页面,监听 window.onerror 事件,并且输出事件的堆栈。同时分别加载两个域的 JS 文件。

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>Script Error Test</title><meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body><button id="btn-3000">3000</button><button id="btn-4000">4000</button><div><pre id="info"></pre></div>
</body>
<script>
window.addEventListener('error', evt => {const info = evt.error ? evt.error.stack : evt.message;document.querySelector('#info').textContent = info;
});
</script>
<script src="http://127.0.0.1:3000/at3000.js"></script>
<script src="http://127.0.0.1:4000/at4000.js"></script>
</html>

public/at3000.js

创建一个在 3000 端口执行的脚本,监听 3000 按钮的点击事件,并且抛出一个异常:

const btn3k = document.querySelector('#btn-3000');
btn3k.addEventListener('click', () => {throw new Error('Fail 3000');
});

public/at4000.js

同样的,创建一个在 4000 端口执行的脚本:

const btn4k = document.querySelector('#btn-4000');
btn4k.addEventListener('click', () => {throw new Error('Fail 4000');
});

复现 Script Error

这个时候,我们启动 Node APP:node app.js,然后访问 http://127.0.0.1:3000

分别点击按钮 3000 和 4000,我们发现,同域下面的 3000 按钮点击后,异常消息可以捕获到。而跨域的 4000 按钮,只有一个 Script Error。

点击 3000 按钮

点击 4000 按钮

我们复现了 "Script Error."!

有同学举手,我知道,只要加一个跨域头就可以了!

Access-Control-Allow-Origin

没错,我们可以给静态文件服务器加上跨域协议头:

app.use(express.static('./public', {setHeaders(res) {res.set('access-control-allow-origin', res.req.get('origin'));res.set('access-control-allow-credentials', 'true');}
}));

同时,加载 JS 的时候,加上跨域声明:

<script src="http://127.0.0.1:4000/at4000.js" crossorigin="anonymous"></script>

这样,无论 3000 还是 4000 按钮,我们点击都能获得异常信息。

但是,这个方案有两个致命的弱点:

  • 如果 JS 声明了 crossorigin="anonymous" 但是响应头没有正确,JS 会直接无法执行
  • 我们并不总是有静态服务器的配置权限,跨域头不是想加就能加

声明了 crossorigin 但是没有响应跨域头的 JS

另类思路

如果我告诉你,可以不加跨域头,只是在 JS 文件加载之前加载一个「特别的」JS,一样可以达到目的,你信不信?

<script src="http://127.0.0.1:3000/inject-event-target.js"></script>
<script src="http://127.0.0.1:3000/at3000.js"></script>
<script src="http://127.0.0.1:4000/at4000.js"></script>

这个神奇的 inject-event-target.js 可以让我们在没有跨域头的情况下,拿到 4000 按钮事件处理器的执行异常信息。

点击 3000

点击 4000

如果你觉得神奇,请点赞后,继续往下阅读。这个魔法 JS,其实也很简单:

const originAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, listener, options) {const wrappedListener = function (...args) {try {return listener.apply(this, args);}catch (err) {throw err;}}return originAddEventListener.call(this, type, wrappedListener, options);
}

原理也非笔者原创,而是从这篇文章学习而来。

简单解释一下:

  • 改写了 EventTarget 的 addEventListener 方法;
  • 对传入的 listener 进行包装,返回包装过的 listener,对其执行进行 try-catch;
  • 浏览器不会对 try-catch 起来的异常进行跨域拦截,所以 catch 到的时候,是有堆栈信息的;
  • 重新 throw 出来异常的时候,执行的是同域代码,所以 window.onerror 捕获的时候不会丢失堆栈信息;

实际上,利用包装 addEventListener,我们还可以达到「扩展堆栈」的效果:

堆栈扩展效果

我们不仅知道异常堆栈,而且还知道导致该异常的事件处理器,是在何处添加进去的。实现这个效果,也很简单:

 (() => {const originAddEventListener = EventTarget.prototype.addEventListener;EventTarget.prototype.addEventListener = function (type, listener, options) {
+    // 捕获添加事件时的堆栈
+    const addStack = new Error(`Event (${type})`).stack;const wrappedListener = function (...args) {try {return listener.apply(this, args);}catch (err) {
+        // 异常发生时,扩展堆栈
+        err.stack += '\n' + addStack;throw err;}}return originAddEventListener.call(this, type, wrappedListener, options);}})();

同样的道理,我们也可以对 setTimeout、setInterval、requestAnimationFrame 甚至 XMLHttpRequest 做这样的拦截,得到一些我们本来得不到的信息。

此文已由作者授权腾讯云+社区发布,更多原文请点击

搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!

转载于:https://my.oschina.net/qcloudcommunity/blog/2963894

解决 Script Error 的另类思路相关推荐

  1. java script error_java script error 错误解决方法

    用了动易这么久了,有时一不小心改错模版文件或者 修改相关的JS,就会导致页面出现一些奇怪了 的弹出框 说什么 java script error 错误,烦死了. 今天特别研究了下,找到了解决这类问题的 ...

  2. 成功解决Fatal error in launcher: Unable to create process using ‘“f:\program files\python\python36\pytho

    成功解决Fatal error in launcher: Unable to create process using '"f:\program files\python\python36\ ...

  3. 成功解决Value Error: Unable to add relationship because child variable ‘name‘ in ‘cats_df‘ is also its i

    成功解决Value Error: Unable to add relationship because child variable 'name' in 'cats_df' is also its i ...

  4. 成功解决Type Error: can‘t multiply sequence by non-int of type ‘float‘

    成功解决Type Error: can't multiply sequence by non-int of type 'float' 目录 解决问题 解决思路 解决方法 解决问题 File " ...

  5. 成功解决cv2.error: OpenCV(4.1.2) C:\projects\opencv-python\opencv\modules\imgproc\src\color.cpp:182: err

    成功解决cv2.error: OpenCV(4.1.2) C:\projects\opencv-python\opencv\modules\imgproc\src\color.cpp:182: err ...

  6. 成功解决An error ocurred while starting the kernel

    成功解决An error ocurred while starting the kernel 目录 解决问题 解决思路 解决方法 解决问题 An error ocurred while startin ...

  7. 成功解决CondaError: Error reading file, file should be a text file containing packages conda create --he

    成功解决CondaError: Error reading file, file should be a text file containing packages conda create --he ...

  8. 成功解决urllib.error.URLError urlopen error Errno 11004 getaddrinfo failed

    成功解决urllib.error.URLError: <urlopen error [Errno 11004] getaddrinfo failed> 目录 解决问题 解决思路 解决方法 ...

  9. 成功解决OpenCV Error: Assertion failed (ssize.width 0 ssize.height 0) in cv::resize, file C:\proj

    成功解决OpenCV Error: Assertion failed (ssize.width > 0 && ssize.height > 0) in cv::resize ...

最新文章

  1. ValueTransformer
  2. python requests text content_python request text 和 content的区别
  3. 数据竞赛:如何小号作弊
  4. Doug Cutting—搜索之父
  5. 链表的基本操作 java_详细实现单链表的基本操作【Java版】
  6. 编译apache过程中出现如下错误及解决办法
  7. 区块链浏览器_如何用区块链浏览器实现链上数据追踪?
  8. boost 单io_serverce 异步多线程资源保护代码
  9. Oracle 索引(转)
  10. css怎么写链接到图片和地址
  11. HDU 2186--
  12. Mac电脑:Android Studio 连接 MUMU 网易模拟器
  13. 怎么制作游戏脚本_自动玩游戏,手游脚本究竟是怎么做的?
  14. 阿里云OCR图片文字识别使用教程
  15. TextView控件的使用(KongJian)
  16. codeforces C. Team
  17. 【python数据分析】对淘商品类母婴购物数据进行分析(含完整源码)
  18. ADO.Net 之手机通讯录
  19. 腾讯会议看不到别人的共享屏幕,共享屏幕是黑色解决方法
  20. 自我激励的有效方法20个(推荐)

热门文章

  1. linux php oauth安装,Linux php 扩展安装 mongo ,redis ,soap,imap,pdo_mysql,oauth
  2. 主网已经上线的币有哪些_什么是主网币圈主网上线意味着什么?
  3. mysql 查看集群状态_MySQL数据库集群正确配置步骤
  4. Oracle中通过Function,存储过程,触发器,调用实现解析Clob字段中存在的xml字符串...
  5. leetcode74. 搜索二维矩阵 ,你见过吗
  6. 我对STL的一些看法(二)认识vector容器
  7. STL源码剖析 迭代器的概念和traits编程技法
  8. 加入初创企业需要想清楚的几个问题
  9. [Git高级教程(二)] 远程仓库版本回退方法
  10. Failed to install Tomcat7 service 解决