解決 Script Error 的另類思路

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

本文由小芭樂發表

前端的同學如果用 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。

img點擊 3000 按鈕

img點擊 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 會直接無法執行
  • 我們并不總是有靜態服務器的配置權限,跨域頭不是想加就能加

img聲明了 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 按鈕事件處理器的執行異常信息。

img點擊 3000

img點擊 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,我們還可以達到「擴展堆棧」的效果:

img堆棧擴展效果

我們不僅知道異常堆棧,而且還知道導致該異常的事件處理器,是在何處添加進去的。實現這個效果,也很簡單:

 (() => {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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/450123.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/450123.shtml
英文地址,請注明出處:http://en.pswp.cn/news/450123.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

大平臺的局限

這篇文章算是二稿。初稿使的是慣用的賣弄筆法&#xff0c;寫到盡興時去查了查資料&#xff0c;哦草&#xff0c;錯了好多。悶悶不樂。后來就不敢再鬼扯&#xff0c;老老實實干巴巴地講觀點。 做產品的人都喜歡大平臺&#xff0c;好像男人都喜歡大胸脯女郎&#xff0c;但是胸脯大…

Lisenter筆記

EventListener與EventObject要完成在線用戶列表的監聽器&#xff0c;需要使用如下幾個接口&#xff1a;ServletContextListener接口&#xff1a;在上下文初始化時設置一個空的集合到application之中&#xff1b;HttpSessionAttributeListener接口&#xff1a;用戶增加session屬…

Android應用開發—重載fragment構造函數導致的lint errors

背景&#xff1a;在一次release打包中發現lint報以下錯誤&#xff1a; Error: Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment] 根據后面的log提示是由于重載了fragment的構造函數&…

迅雷影音怎樣 1.5倍速度播放

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 看視頻 覺得播放速度太慢&#xff0c;想讓1.5速度播放可以這樣設置&#xff1a; 點擊快進按鈕&#xff0c;點一次變為1.1倍&#xff0c…

【Java】Mybatis mapper動態代理方式

前言 我們在使用Mybatis的時候&#xff0c;獲取需要執行的SQL語句的時候&#xff0c;都是通過調用xml文件來獲取&#xff0c;例如&#xff1a;User user (User) sqlSession.selectOne("cn.ddnd.www.Entity.User.getUser", "xue8qq.com");。這種方式是通過…

git pull時沖突的幾種解決方式

僅結合本人使用場景&#xff0c;方法可能不是最優的 1. 忽略本地修改&#xff0c;強制拉取遠程到本地 主要是項目中的文檔目錄&#xff0c;看的時候可能多了些標注&#xff0c;現在遠程文檔更新&#xff0c;本地的版本已無用&#xff0c;可以強拉 git fetch --allgit reset --h…

Android應用開發—eventBus發布事件和事件處理的時序關系

占坑&#xff0c;簡單說明下eventBus發布事件和事件處理的時序關系。 什么時候使用sticky&#xff1a; 當你希望你的事件不被馬上處理的時候&#xff0c;舉個栗子&#xff0c;比如說&#xff0c;在一個詳情頁點贊之后&#xff0c;產生一個VoteEvent&#xff0c;VoteEvent并不立…

grep命令 解說

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 grep&#xff08;global search regular expression(RE) and print out the line&#xff0c;全面搜索正則表達式并把行打印出來&#x…

創業第一桶金怎么來

文章摘要&#xff1a;資金是創業要具備的一個必要條件&#xff0c;那么對于創業者來說&#xff0c;第一桶金如何取得&#xff1f;資金是創業要具備的一個必要條件&#xff0c;那么對于創業者來說&#xff0c;第一桶金如何取得&#xff1f;   一、一門手藝   都說擁有萬貫…

4001.基于雙向鏈表的雙向冒泡排序法

基于雙向鏈表的雙向冒泡排序法 發布時間: 2018年11月26日 10:09 時間限制: 1000ms 內存限制: 128M 習題集源碼中出現了 temp->next->prior p; 本人推斷這里缺少預先的對temp->nextNULL這種情況的判定&#xff0c;所以需加入一個判斷語句解決。 此為非循環的雙向鏈…

頁面向上滾動

#頁面或者div向上無縫滾動 1.css: body {margin: 0;padding: 0;overflow: hidden;}.container {position: relative;top: 0;}.container div {width: 500px;height: 500px;border: 1px solid chartreuse;font-size: 100px;line-height: 500px;font-weight: bold;color: black;t…

叨逼叨

此處記錄點零散的小idea&#xff0c;為了避免把csdn當微博&#xff0c;開一篇&#xff0c;都記在這里吧。 感覺服務注冊機制&#xff0c;貌似也是一種依賴注入。&#xff08;雖然我還沒完全搞懂依賴注入&#xff09;&#xff0c;理由呢&#xff1a;你需要一個模塊的功能&#x…

Linux:echo命令詳解

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 echo命令 用于字符串的輸出 格式 echo string使用echo實現更復雜的輸出格式控制 1.顯示普通字符串: echo "It is a test"這里…

看年輕人如何賺第一桶金

上世紀90年代&#xff0c;成為百萬富翁&#xff0c;對很多人只是個夢想。不過如今&#xff0c;隨著經濟飛速發展&#xff0c;擁有百萬資產已經不再是神話&#xff0c;放眼望去&#xff0c;我們身邊的百萬富翁比比皆是&#xff0c;甚至很多初入社會、白手起家的年輕人&#xff0…

跨越解決方案之nginx

這里是修真院前端小課堂&#xff0c;每篇分享文從 【背景介紹】【知識剖析】【常見問題】【解決方案】【編碼實戰】【擴展思考】【更多討論】【參考文獻】 八個方面深度解析前端知識/技能&#xff0c;本篇分享的是&#xff1a; 【跨越解決方案之nginx】 1.背景介紹 跨域&#x…

學習 shell腳本之前的基礎知識

見 : http://www.92csz.com/study/linux/12.htm【什么是shell】 簡單點理解&#xff0c;就是系統跟計算機硬件交互時使用的中間介質&#xff0c;它只是系統的一個工具。實際上&#xff0c;在shell和計算機硬件之間還有一層東西那就是系統內核了。打個比方&#xff0c;如果把計算…

「分塊系列」數列分塊入門3 解題報告

數列分塊入門3 題意概括 區間加法&#xff0c;區間求前驅。 寫在前面 這題的方法與分塊2方法極其類似&#xff0c;建議自行解決。 正題 和上一題類似&#xff0c;但是二分不是用來計數的&#xff0c;而是用來求小于c的最大值的。然后對于不完整快&#xff0c;將小于c的值求最大…

創業者自述:我的第一桶金是如何來的

記者采訪王宏筠的當天&#xff0c;北京氣溫已達到30℃&#xff0c;王宏筠從他的鐵灰色奧迪A6車上下來&#xff0c;一身挺括的西裝&#xff0c;打著領帶&#xff0c;肩上背著一個超大的牛皮包。后來他對記者說&#xff0c;穿西服是因為多年在外企養成的習慣&#xff0c;一年中至…

Git cherry-pick后再merge出現一個“奇怪”的現象

背景描述&#xff1a;有的時候基于一個master branch拉出一個獨立feature分支做開發時&#xff0c;兩條分支都在并行開發&#xff0c;如果master分支增加了某些功能&#xff0c;解決了某些關鍵bug&#xff0c;而獨立feature分支不需要所有的增加的commit&#xff0c;只需要某一…

inux系統中如何進入退出vim編輯器

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 VIM編輯器&#xff0c;可以新建文件也可以修改文件&#xff0c;命令為&#xff1a;vim AAA 。AAA就是文件名。 如果這個文件&#xff…