国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

你不知道的 JS 錯(cuò)誤和調(diào)用棧常識(shí)

tainzhi / 1234人閱讀

摘要:錯(cuò)誤堆棧包含了產(chǎn)生該錯(cuò)誤時(shí)完整的調(diào)用棧信息。總結(jié)通過(guò)本文的描述,相信你對(duì)中的調(diào)用棧對(duì)象錯(cuò)誤堆棧有了清晰的認(rèn)識(shí),在遇到錯(cuò)誤的時(shí)候不在慌亂。

本文首發(fā)知乎專(zhuān)欄:《前端周刊》。全文共 6988 字,讀完需 10 分鐘,速讀需 3 分鐘。通過(guò)剖析 JS 中調(diào)用棧的工作機(jī)制,講解錯(cuò)誤拋出、處理的正確姿勢(shì),以及錯(cuò)誤堆棧的獲取、清理處理方法,希望大家對(duì)這個(gè)少有人關(guān)注但極其有用的知識(shí)點(diǎn)能夠有所理解和掌握。適合的學(xué)習(xí)對(duì)象是初中級(jí) JS 工程師。

大多數(shù)工程師可能并沒(méi)留意過(guò) JS 中錯(cuò)誤對(duì)象、錯(cuò)誤堆棧的細(xì)節(jié),即使他們每天的日常工作會(huì)面臨不少的報(bào)錯(cuò),部分同學(xué)甚至在 console 的錯(cuò)誤面前一臉懵逼,不知道從何開(kāi)始排查,如果你對(duì)本文講解的內(nèi)容有系統(tǒng)的了解,就會(huì)從容很多。而錯(cuò)誤堆棧清理能讓你有效去掉噪音信息,聚焦在真正重要的地方,此外,如果理解了 Error 的各種屬性到底是什么,你就能更好的利用他。

接下來(lái),我們就直奔主題。

調(diào)用棧的工作機(jī)制

在探討 JS 中的錯(cuò)誤之前,我們必須理解調(diào)用棧(Call Stack)的工作機(jī)制,其實(shí)這個(gè)機(jī)制非常簡(jiǎn)單,如果你對(duì)這個(gè)已經(jīng)一清二楚了,可以直接跳過(guò)這部分內(nèi)容。

簡(jiǎn)單的說(shuō):函數(shù)被調(diào)用時(shí),就會(huì)被加入到調(diào)用棧頂部,執(zhí)行結(jié)束之后,就會(huì)從調(diào)用棧頂部移除該函數(shù),這種數(shù)據(jù)結(jié)構(gòu)的關(guān)鍵在于后進(jìn)先出,即大家所熟知的 LIFO。比如,當(dāng)我們?cè)诤瘮?shù) y 內(nèi)部調(diào)用函數(shù) x 的時(shí)候,調(diào)用棧從下往上的順序就是 y -> x

我們?cè)倥e個(gè)代碼實(shí)例:

function c() {
    console.log("c");
}

function b() {
    console.log("b");
    c();
}

function a() {
    console.log("a");
    b();
}

a();

這段代碼運(yùn)行時(shí),首先 a 會(huì)被加入到調(diào)用棧的頂部,然后,因?yàn)?a 內(nèi)部調(diào)用了 b,緊接著 b 被加入到調(diào)用棧的頂部,當(dāng) b 內(nèi)部調(diào)用 c 的時(shí)候也是類(lèi)似的。在調(diào)用 c 的時(shí)候,我們的調(diào)用棧從下往上會(huì)是這樣的順序: a -> b -> c。在 c 執(zhí)行完畢之后,c 被從調(diào)用棧中移除,控制流回到 b 上,調(diào)用棧會(huì)變成:a -> b,然后 b 執(zhí)行完之后,調(diào)用棧會(huì)變成:a,當(dāng) a 執(zhí)行完,也會(huì)被從調(diào)用棧移除。

為了更好的說(shuō)明調(diào)用棧的工作機(jī)制,我們對(duì)上面的代碼稍作改動(dòng),使用 console.trace 來(lái)把當(dāng)前的調(diào)用棧輸出到 console 中,你可以認(rèn)為console.trace 打印出來(lái)的調(diào)用棧的每一行出現(xiàn)的原因是它下面的那行調(diào)用而引起的。

function c() {
    console.log("c");
    console.trace();
}

function b() {
    console.log("b");
    c();
}

function a() {
    console.log("a");
    b();
}

a();

當(dāng)我們?cè)?Node.js 的 REPL 中運(yùn)行這段代碼,會(huì)得到如下的結(jié)果:

Trace
    at c (repl:3:9)
    at b (repl:3:1)
    at a (repl:3:1)
    at repl:1:1 // <-- 從這行往下的內(nèi)容可以忽略,因?yàn)檫@些都是 Node 內(nèi)部的東西
    at realRunInThisContextScript (vm.js:22:35)
    at sigintHandlersWrap (vm.js:98:12)
    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
    at REPLServer.defaultEval (repl.js:313:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)

顯而易見(jiàn),當(dāng)我們?cè)?c 內(nèi)部調(diào)用 console.trace 的時(shí)候,調(diào)用棧從下往上的結(jié)構(gòu)是:a -> b -> c。如果把代碼再稍作改動(dòng),在 bc 執(zhí)行完之后調(diào)用,如下:

function c() {
    console.log("c");
}

function b() {
    console.log("b");
    c();
    console.trace();
}

function a() {
    console.log("a");
    b();
}

a();

通過(guò)輸出結(jié)果可以看到,此時(shí)打印的調(diào)用棧從下往上是:a -> b,已經(jīng)沒(méi)有 c 了,因?yàn)?c 執(zhí)行完之后就從調(diào)用棧移除了。

Trace
    at b (repl:4:9)
    at a (repl:3:1)
    at repl:1:1  // <-- 從這行往下的內(nèi)容可以忽略,因?yàn)檫@些都是 Node 內(nèi)部的東西
    at realRunInThisContextScript (vm.js:22:35)
    at sigintHandlersWrap (vm.js:98:12)
    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
    at REPLServer.defaultEval (repl.js:313:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.onLine (repl.js:513:10)

再總結(jié)下調(diào)用棧的工作機(jī)制:調(diào)用函數(shù)的時(shí)候,會(huì)被推到調(diào)用棧的頂部,而執(zhí)行完畢之后,就會(huì)從調(diào)用棧移除。

Error 對(duì)象及錯(cuò)誤處理

當(dāng)代碼中發(fā)生錯(cuò)誤時(shí),我們通常會(huì)拋出一個(gè) Error 對(duì)象。Error 對(duì)象可以作為擴(kuò)展和創(chuàng)建自定義錯(cuò)誤類(lèi)型的原型。Error 對(duì)象的 prototype 具有以下屬性:

constructor - 負(fù)責(zé)該實(shí)例的原型構(gòu)造函數(shù);

message - 錯(cuò)誤信息;

name - 錯(cuò)誤的名字;

上面都是標(biāo)準(zhǔn)屬性,有些 JS 運(yùn)行環(huán)境還提供了標(biāo)準(zhǔn)屬性之外的屬性,如 Node.js、Firefox、Chrome、Edge、IE 10、Opera 和 Safari 6+ 中會(huì)有 stack 屬性,它包含了錯(cuò)誤代碼的調(diào)用棧,接下來(lái)我們簡(jiǎn)稱(chēng)錯(cuò)誤堆棧錯(cuò)誤堆棧包含了產(chǎn)生該錯(cuò)誤時(shí)完整的調(diào)用棧信息。如果您想了解更多關(guān)于 Error 對(duì)象的非標(biāo)準(zhǔn)屬性,我強(qiáng)烈建議你閱讀 MDN 的這篇文章。

拋出錯(cuò)誤時(shí),你必須使用 throw 關(guān)鍵字。為了捕獲拋出的錯(cuò)誤,則必須使用 try catch 語(yǔ)句把可能出錯(cuò)的代碼塊包起來(lái),catch 的時(shí)候可以接收一個(gè)參數(shù),該參數(shù)就是被拋出的錯(cuò)誤。與 Java 中類(lèi)似,JS 中也可以在 try catch 語(yǔ)句之后有 finally,不論前面代碼是否拋出錯(cuò)誤 finally 里面的代碼都會(huì)執(zhí)行,這種語(yǔ)言的常見(jiàn)用途有:在 finally 中做些清理的工作。

此外,你可以使用沒(méi)有 catchtry 語(yǔ)句,但是后面必須跟上 finally,這意味著我們可以使用三種不同形式的 try 語(yǔ)句:

try ... catch

try ... finally

try ... catch ... finally

try 語(yǔ)句還可以嵌套在 try 語(yǔ)句中,比如:

try {
    try {
        throw new Error("Nested error."); // 這里的錯(cuò)誤會(huì)被自己緊接著的 catch 捕獲
    } catch (nestedErr) {
        console.log("Nested catch"); // 這里會(huì)運(yùn)行
    }
} catch (err) {
    console.log("This will not run.");  // 這里不會(huì)運(yùn)行
}

try 語(yǔ)句也可以嵌套在 catchfinally 語(yǔ)句中,比如下面的兩個(gè)例子:

try {
    throw new Error("First error");
} catch (err) {
    console.log("First catch running");
    try {
        throw new Error("Second error");
    } catch (nestedErr) {
        console.log("Second catch running.");
    }
}
try {
    console.log("The try block is running...");
} finally {
    try {
        throw new Error("Error inside finally.");
    } catch (err) {
        console.log("Caught an error inside the finally block.");
    }
}

同樣需要注意的是,你可以拋出不是 Error 對(duì)象的任意值。這可能看起來(lái)很酷,但在工程上確是強(qiáng)烈不建議的做法。如果恰巧你需要處理錯(cuò)誤的調(diào)用棧信息和其他有意義的元數(shù)據(jù),拋出非 Error 對(duì)象的錯(cuò)誤會(huì)讓你的處境很尷尬。

假如我們有如下的代碼:

function runWithoutThrowing(func) {
    try {
        func();
    } catch (e) {
        console.log("There was an error, but I will not throw it.");
        console.log("The error"s message was: " + e.message)
    }
}

function funcThatThrowsError() {
    throw new TypeError("I am a TypeError.");
}

runWithoutThrowing(funcThatThrowsError);

如果 runWithoutThrowing 的調(diào)用者傳入的函數(shù)都能拋出 Error 對(duì)象,這段代碼不會(huì)有任何問(wèn)題,如果他們拋出了字符串那就有問(wèn)題了,比如:

function runWithoutThrowing(func) {
    try {
        func();
    } catch (e) {
        console.log("There was an error, but I will not throw it.");
        console.log("The error"s message was: " + e.message)
    }
}

function funcThatThrowsString() {
    throw "I am a String.";
}

runWithoutThrowing(funcThatThrowsString);

這段代碼運(yùn)行時(shí),runWithoutThrowing 中的第 2 次 console.log 會(huì)拋出錯(cuò)誤,因?yàn)?e.message 是未定義的。這些看起來(lái)似乎沒(méi)什么大不了的,但如果你的代碼需要使用 Error 對(duì)象的某些特定屬性,那么你就需要做很多額外的工作來(lái)確保一切正常。如果你拋出的值不是 Error 對(duì)象,你就不會(huì)拿到錯(cuò)誤相關(guān)的重要信息,比如 stack,雖然這個(gè)屬性在部分 JS 運(yùn)行環(huán)境中才會(huì)有。

Error 對(duì)象也可以向其他對(duì)象那樣使用,你可以不用拋出錯(cuò)誤,而只是把錯(cuò)誤傳遞出去,Node.js 中的錯(cuò)誤優(yōu)先回調(diào)就是這種做法的典型范例,比如 Node.js 中的 fs.readdir 函數(shù):

const fs = require("fs");

fs.readdir("/example/i-do-not-exist", function callback(err, dirs) {
    if (err) {
        // `readdir` will throw an error because that directory does not exist
        // We will now be able to use the error object passed by it in our callback function
        console.log("Error Message: " + err.message);
        console.log("See? We can use Errors without using try statements.");
    } else {
        console.log(dirs);
    }
});

此外,Error 對(duì)象還可以用于 Promise.reject 的時(shí)候,這樣可以更容易的處理 Promise 失敗,比如下面的例子:

new Promise(function(resolve, reject) {
    reject(new Error("The promise was rejected."));
}).then(function() {
    console.log("I am an error.");
}).catch(function(err) {
    if (err instanceof Error) {
        console.log("The promise was rejected with an error.");
        console.log("Error Message: " + err.message);
    }
});
錯(cuò)誤堆棧的裁剪

Node.js 才支持這個(gè)特性,通過(guò) Error.captureStackTrace 來(lái)實(shí)現(xiàn),Error.captureStackTrace 接收一個(gè) object 作為第 1 個(gè)參數(shù),以及可選的 function 作為第 2 個(gè)參數(shù)。其作用是捕獲當(dāng)前的調(diào)用棧并對(duì)其進(jìn)行裁剪,捕獲到的調(diào)用棧會(huì)記錄在第 1 個(gè)參數(shù)的 stack 屬性上,裁剪的參照點(diǎn)是第 2 個(gè)參數(shù),也就是說(shuō),此函數(shù)之前的調(diào)用會(huì)被記錄到調(diào)用棧上面,而之后的不會(huì)。

讓我們用代碼來(lái)說(shuō)明,首先,把當(dāng)前的調(diào)用棧捕獲并放到 myObj 上:

const myObj = {};

function c() {
}

function b() {
    // 把當(dāng)前調(diào)用棧寫(xiě)到 myObj 上
    Error.captureStackTrace(myObj);
    c();
}

function a() {
    b();
}

// 調(diào)用函數(shù) a
a();

// 打印 myObj.stack
console.log(myObj.stack);

// 輸出會(huì)是這樣
//    at b (repl:3:7) <-- Since it was called inside B, the B call is the last entry in the stack
//    at a (repl:2:1)
//    at repl:1:1 <-- Node internals below this line
//    at realRunInThisContextScript (vm.js:22:35)
//    at sigintHandlersWrap (vm.js:98:12)
//    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
//    at REPLServer.defaultEval (repl.js:313:29)
//    at bound (domain.js:280:14)
//    at REPLServer.runBound [as eval] (domain.js:293:12)
//    at REPLServer.onLine (repl.js:513:10)

上面的調(diào)用棧中只有 a -> b,因?yàn)槲覀冊(cè)?b 調(diào)用 c 之前就捕獲了調(diào)用棧。現(xiàn)在對(duì)上面的代碼稍作修改,然后看看會(huì)發(fā)生什么:

const myObj = {};

function d() {
    // 我們把當(dāng)前調(diào)用棧存儲(chǔ)到 myObj 上,但是會(huì)去掉 b 和 b 之后的部分
    Error.captureStackTrace(myObj, b);
}

function c() {
    d();
}

function b() {
    c();
}

function a() {
    b();
}

// 執(zhí)行代碼
a();

// 打印 myObj.stack
console.log(myObj.stack);

// 輸出如下
//    at a (repl:2:1) <-- As you can see here we only get frames before `b` was called
//    at repl:1:1 <-- Node internals below this line
//    at realRunInThisContextScript (vm.js:22:35)
//    at sigintHandlersWrap (vm.js:98:12)
//    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
//    at REPLServer.defaultEval (repl.js:313:29)
//    at bound (domain.js:280:14)
//    at REPLServer.runBound [as eval] (domain.js:293:12)
//    at REPLServer.onLine (repl.js:513:10)
//    at emitOne (events.js:101:20)

在這段代碼里面,因?yàn)槲覀冊(cè)谡{(diào)用 Error.captureStackTrace 的時(shí)候傳入了 b,這樣 b 之后的調(diào)用棧都會(huì)被隱藏。

現(xiàn)在你可能會(huì)問(wèn),知道這些到底有啥用?如果你想對(duì)用戶隱藏跟他業(yè)務(wù)無(wú)關(guān)的錯(cuò)誤堆棧(比如某個(gè)庫(kù)的內(nèi)部實(shí)現(xiàn))就可以試用這個(gè)技巧。

總結(jié)

通過(guò)本文的描述,相信你對(duì) JS 中的調(diào)用棧、Error 對(duì)象、錯(cuò)誤堆棧有了清晰的認(rèn)識(shí),在遇到錯(cuò)誤的時(shí)候不在慌亂。如果對(duì)文中的內(nèi)容有任何疑問(wèn),歡迎在下面評(píng)論。想知道這個(gè)人接下來(lái)會(huì)寫(xiě)些什么?歡迎訂閱我的知乎專(zhuān)欄:《前端周刊》

Happy Hacking

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/86846.html

相關(guān)文章

  • 2017年3月份前端資源分享

    平日學(xué)習(xí)接觸過(guò)的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個(gè)網(wǎng)址:http://www.kancloud.cn/jsfron... 03月份前端資源分享 1. Javascript 175453545 Redux compose and middleware 源碼分析 深入 Promise(二)——進(jìn)擊的 Promise Effective JavaScript leeheys blog -...

    ermaoL 評(píng)論0 收藏0
  • 2017年3月份前端資源分享

    平日學(xué)習(xí)接觸過(guò)的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個(gè)網(wǎng)址:http://www.kancloud.cn/jsfron... 03月份前端資源分享 1. Javascript 175453545 Redux compose and middleware 源碼分析 深入 Promise(二)——進(jìn)擊的 Promise Effective JavaScript leeheys blog -...

    kamushin233 評(píng)論0 收藏0
  • 2017年3月份前端資源分享

    平日學(xué)習(xí)接觸過(guò)的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個(gè)網(wǎng)址:http://www.kancloud.cn/jsfron... 03月份前端資源分享 1. Javascript 175453545 Redux compose and middleware 源碼分析 深入 Promise(二)——進(jìn)擊的 Promise Effective JavaScript leeheys blog -...

    yy736044583 評(píng)論0 收藏0
  • 2017年3月份前端資源分享

    平日學(xué)習(xí)接觸過(guò)的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個(gè)網(wǎng)址:http://www.kancloud.cn/jsfron... 03月份前端資源分享 1. Javascript 175453545 Redux compose and middleware 源碼分析 深入 Promise(二)——進(jìn)擊的 Promise Effective JavaScript leeheys blog -...

    awokezhou 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<