【JavaScript】为什么是script error.


script error.的定义

script error.是一个常见的错误,它类似于b is not definedscript error.与这个错误不同的点就在于其并没有详细的错误信息

script error.产生原因

先来说一下b is not defined这个错误要如何产生

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        window.onerror = function (message, url, line, column, error{
            console.log(message, url, line, column, error);
        }
        console.log(b);
    
</script>
</body>
</html>

结果很明显的,我们捕获到了这个错误的详细信息

下面来复现下script error.,我们将上面的一小段代码上传到服务器上,然后在现有的js中加上这样一段代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        window.onerror = function (message, url, line, column, error{
            console.log(message, url, line, column, error);
        }
    
</script>
    <script src="https://cmc-web.oss-cn-beijing.aliyuncs.com/2.js"></script>
</body>
</html>

你看,这个神秘的script error.出现了,而且最有用的信息恐怕就是script error.了🐶

这两种case有何不同?第二种情况下,我们引用了一个非同源的文件。

在webkit中,有这样一段代码

bool ScriptExecutionContext::sanitizeScriptError(String& errorMessage, int& lineNumber, String& sourceURL)
    {
        KURL targetURL = completeURL(sourceURL);
        if (securityOrigin()->canRequest(targetURL))
            return false;
        errorMessage = "Script error.";
        sourceURL = String();
        lineNumber = 0;
        return true;
    }

它会解析来源url,如果和当前url不是同源的时候,就会强制errorMessagescript error.。所以,可以得出结论:这是浏览器出于安全的考虑,刻意隐藏了其他域下js文件抛出的错误信息,这样可以避免敏感信息无意中被不受控制的第三方脚本捕获。所以,对于其他域下发生的错误,我们也只是能知道这里有一个错误,并无从知道错误信息的详细内容。

script error.解决办法

不知道详细错误信息可不行呀,那怎么办呢?这里有两种解决方案

先来试试crossOrigin?

这个其实就是开启CORS(跨域资源共享),这个方法要分两步来操作:

1、script标签加上crossOrigin属性:告知浏览器以匿名的方式获取目标脚本,这意味着请求脚本时不会向服务端发送潜在的用户身份信息

crossorigin="anonymous"

2、添加跨域HTTP响应头

Access-Control-Allow-Origin: *

或者

Access-Control-Allow-Origin: https://cmc-web.oss-cn-beijing.aliyuncs.com

完成这两步操作后,看下结果:

再来试试try catch?

先来说下try catch的优点:浏览器不会对其异常进行跨域拦截,所以这个时候可以拿到堆栈信息。

但同时它也有缺点:

如果使用try catch的话,只能捕获到同步的错误,像这样:

try {
    console.log(b);
catch (error) {
    console.log(error); 
}

如果换成异步呢

 try {
    setTimeout(() => {
        console.log(b);
    }, 2000)
catch (error) {
    console.log(error);
}

会发现并拿不到错误信息,所以针对此现象,我们对不同的动作做不同的改动

拦截普通函数捕获异常

考虑到这句console.log(b)直接写在代码里,也不会打包成功,所以这块我们稍加修改,重新上传到服务器

function test({
    console.log(b);
}

下面写一个拦截函数,考虑到我们可能对很多个函数都要做拦截,所以采取这样一个思想:函数执行统一交给另外一个函数,统一拦截这个函数

function emit(callback{
    callback()
}

let originError = emit;
emit = function(func{
    const addStack = new Error(`Event error`).stack;
    const wrapppedFunc = function(...args{
        try {
            return func.apply(this, args);
        }
        catch (err) {
            // 异常发生时,扩展堆栈
            err.stack += '\n' + addStack;
            throw err;
        }
    }
    return originError.call(this, wrapppedFunc);
}

拦截事件函数捕获异常

<button id="btn">点我</button>

// script
let oBtn = document.getElementById('btn');
oBtn.addEventListener('click'function() {
    test();
}, false)

因为上面写了对于普通函数的异常捕获拦截,所以可以这样解:

let oBtn = document.getElementById('btn');
oBtn.addEventListener('click'function({
    emit(test);
}, false)

如果不想每次都调用emit函数,就要实现一个对于事件拦截的函数:

let originAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, func, async{
    const addStack = new Error(`Event ${type}`).stack;
    const wrapppedFunc = function(...args{
        try {
            return func.apply(this, args);
        }
        catch (err) {
            // 异常发生时,扩展堆栈
            err.stack += '\n' + addStack;
            throw err;
        }
    }
    return originAddEventListener.call(this, type, wrapppedFunc, async);
}

结果如下:

拦截setTimeout函数捕获异常

setTimeout同上

let originSetTimeout = window.setTimeout;
window.setTimeout = function(func, delay{
    const addStack = new Error(`setTimout error`).stack;
    const wrapppedFunc = function(...args{
        try {
            return func(args);
        }
        catch (err) {
            // 异常发生时,扩展堆栈
            err.stack += '\n' + addStack;
            throw err;
        }
    }
    return originSetTimeout.call(this, wrapppedFunc, delay);
}

综上几个case,发现都是用try catch包裹,那有没有办法使用一个通用的办法通通解决呢?

写在最后

本文理了一下script error从产生到解决的过程,我们可以思考一下最后抛出来的问题~

最后,分享一下我的个人微信公众号「web前端日记」,大家可以关注一波~


评论