摘要:當(dāng)執(zhí)行完畢后也會(huì)從棧頂移出,控制流交還到。一個(gè)的堆棧追蹤包含了從其構(gòu)造函數(shù)開(kāi)始的所有堆棧幀。我們將捕獲當(dāng)前堆棧路徑并且將其存儲(chǔ)到一個(gè)普通對(duì)象中。用表示起始堆棧函數(shù)指示器這個(gè)名字記錄。
譯者注:本文作者是著名 JavaScript BDD 測(cè)試框架 Chai.js 源碼貢獻(xiàn)者之一,Chai.js 中會(huì)遇到很多異常處理的情況。跟隨作者思路,從 JavaScript 基本的 Errors 原理,到如何實(shí)際使用 Stack Traces,深入學(xué)習(xí)和理解 JavaScript Errors 和 Stack Traces。文章貼出的源碼鏈接也非常值得學(xué)習(xí)。
作者:lucasfcosta
編譯:胡子大哈
翻譯原文:[http://huziketang.com/blog/po...
](http://huziketang.com/blog/po...
英文原文:JavaScript Errors and Stack Traces in Depth
轉(zhuǎn)載請(qǐng)注明出處,保留原文鏈接以及作者信息
很久沒(méi)給大家更新關(guān)于 JavaScript 的內(nèi)容了,這篇文章我們來(lái)聊聊 JavaScript 。
這次我們聊聊 Errors 和 Stack traces 以及如何熟練地使用它們。
很多同學(xué)并不重視這些細(xì)節(jié),但是這些知識(shí)在你寫(xiě) Testing 和 Error 相關(guān)的 lib 的時(shí)候是非常有用的。使用 Stack traces 可以清理無(wú)用的數(shù)據(jù),讓你關(guān)注真正重要的問(wèn)題。同時(shí),你真正理解 Errors 和它們的屬性到底是什么的時(shí)候,你將會(huì)更有信心的使用它們。
這篇文章在開(kāi)始的時(shí)候看起來(lái)比較簡(jiǎn)單,但當(dāng)你熟練運(yùn)用 Stack trace 以后則會(huì)感到非常復(fù)雜。所以在看難的章節(jié)之前,請(qǐng)確保你理解了前面的內(nèi)容。
Stack是如何工作的在我們談到 Errors 之前,我們必須理解 Stack 是如何工作的。它其實(shí)非常簡(jiǎn)單,但是在開(kāi)始之前了解它也是非常必要的。如果你已經(jīng)知道了這些,可以略過(guò)這一章節(jié)。
每當(dāng)有一個(gè)函數(shù)調(diào)用,就會(huì)將其壓入棧頂。在調(diào)用結(jié)束的時(shí)候再將其從棧頂移出。
這種有趣的數(shù)據(jù)結(jié)構(gòu)叫做“最后一個(gè)進(jìn)入的,將會(huì)第一個(gè)出去”。這就是廣為所知的 LIFO(后進(jìn)先出)。
舉個(gè)例子,在函數(shù) x 的內(nèi)部調(diào)用了函數(shù) y,這時(shí)棧中就有個(gè)順序先 x 后 y。我再舉另外一個(gè)例子,看下面代碼:
function c() { console.log("c"); } function b() { console.log("b"); c(); } function a() { console.log("a"); b(); } a();
上面的這段代碼,當(dāng)運(yùn)行 a 的時(shí)候,它會(huì)被壓到棧頂。然后,當(dāng) b 在 a 中被調(diào)用的時(shí)候,它會(huì)被繼續(xù)壓入棧頂,當(dāng) c 在 b 中被調(diào)用的時(shí)候,也一樣。
在運(yùn)行 c 的時(shí)候,棧中包含了 a,b,c,并且其順序也是 a,b,c。
當(dāng) c 調(diào)用完畢時(shí),它會(huì)被從棧頂移出,隨后控制流回到 b。當(dāng) b 執(zhí)行完畢后也會(huì)從棧頂移出,控制流交還到 a。最后,當(dāng) a 執(zhí)行完畢后也會(huì)從棧中移出。
為了更好的展示這樣一種行為,我們用console.trace()來(lái)將 Stack trace 打印到控制臺(tái)上來(lái)。通常我們讀 Stack traces 信息的時(shí)候是從上往下讀的。
function c() { console.log("c"); console.trace(); } function b() { console.log("b"); c(); } function a() { console.log("a"); b(); } a();
當(dāng)我們?cè)贜ode REPL服務(wù)端執(zhí)行的時(shí)候,會(huì)返回如下:
Trace at c (repl:3:9) at b (repl:3:1) at a (repl:3:1) at repl:1:1 // <-- For now feel free to ignore anything below this point, these are Node"s internals 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)
從上面我們可以看到,當(dāng)棧信息從 c 中打印出來(lái)的時(shí)候,我看到了 a,b 和 c?,F(xiàn)在,如果在 c 執(zhí)行完畢以后,在 b 中把 Stack trace 打印出來(lái),我們可以看到 c 已經(jīng)從棧中移出了,棧中只有 a 和 b。
function c() { console.log("c"); } function b() { console.log("b"); c(); console.trace(); } function a() { console.log("a"); b(); } a();
下面可以看到,c 已經(jīng)不在棧中了,在其執(zhí)行完以后,從棧中 pop 出去了。
Trace at b (repl:4:9) at a (repl:3:1) at repl:1:1 // <-- For now feel free to ignore anything below this point, these are Node"s internals 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)
概括一下:當(dāng)調(diào)用時(shí),壓入棧頂。當(dāng)它執(zhí)行完畢時(shí),被彈出棧,就是這么簡(jiǎn)單。
Error 對(duì)象和 Error 處理當(dāng)Error發(fā)生的時(shí)候,通常會(huì)拋出一個(gè)Error對(duì)象。Error對(duì)象也可以被看做一個(gè)Error原型,用戶可以擴(kuò)展其含義,以創(chuàng)建自己的 Error 對(duì)象。
Error.prototype對(duì)象通常包含下面屬性:
constructor - 一個(gè)錯(cuò)誤實(shí)例原型的構(gòu)造函數(shù)
message - 錯(cuò)誤信息
name - 錯(cuò)誤名稱
這幾個(gè)都是標(biāo)準(zhǔn)屬性,有時(shí)不同編譯的環(huán)境會(huì)有其獨(dú)特的屬性。在一些環(huán)境中,例如 Node 和 Firefox,甚至還有stack屬性,這里面包含了錯(cuò)誤的 Stack trace。一個(gè)Error的堆棧追蹤包含了從其構(gòu)造函數(shù)開(kāi)始的所有堆棧幀。
如果你想要學(xué)習(xí)一個(gè)Error對(duì)象的特殊屬性,我強(qiáng)烈建議你看一下在MDN上的這篇文章。
要拋出一個(gè)Error,你必須使用throw關(guān)鍵字。為了catch一個(gè)拋出的Error,你必須把可能拋出Error的代碼用try塊包起來(lái)。然后緊跟著一個(gè)catch塊,catch塊中通常會(huì)接受一個(gè)包含了錯(cuò)誤信息的參數(shù)。
和在 Java 中類似,不論在try中是否拋出Error, JavaScript 中都允許你在try/catch塊后面緊跟著一個(gè)finally塊。不論你在try中的操作是否生效,在你操作完以后,都用finally來(lái)清理對(duì)象,這是個(gè)編程的好習(xí)慣。
介紹到現(xiàn)在的知識(shí),可能對(duì)于大部分人來(lái)說(shuō),都是已經(jīng)掌握了的,那么現(xiàn)在我們就進(jìn)行更深入一些的吧。
使用try塊時(shí),后面可以不跟著catch塊,但是必須跟著finally塊。所以我們就有三種不同形式的try語(yǔ)句:
try...catch
try...finally
try...catch...finally
Try語(yǔ)句也可以內(nèi)嵌在一個(gè)try語(yǔ)句中,如:
try { try { // 這里拋出的Error,將被下面的catch獲取到 throw new Error("Nested error."); } catch (nestedErr) { // 這里會(huì)打印出來(lái) console.log("Nested catch"); } } catch (err) { console.log("This will not run."); }
你也可以把try語(yǔ)句內(nèi)嵌在catch和finally塊中:
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."); }
}
這里給出另外一個(gè)重要的提示:你可以拋出非Error對(duì)象的值。盡管這看起來(lái)很炫酷,很靈活,但實(shí)際上這個(gè)用法并不好,尤其在一個(gè)開(kāi)發(fā)者改另一個(gè)開(kāi)發(fā)者寫(xiě)的庫(kù)的時(shí)候。因?yàn)檫@樣代碼沒(méi)有一個(gè)標(biāo)準(zhǔn),你不知道其他人會(huì)拋出什么信息。這樣的話,你就不能簡(jiǎn)單的相信拋出的Error信息了,因?yàn)橛锌赡芩⒉皇?b>Error信息,而是一個(gè)字符串或者一個(gè)數(shù)字。另外這也導(dǎo)致了如果你需要處理 Stack trace 或者其他有意義的元數(shù)據(jù),也將變的很困難。
例如給你下面這段代碼:
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);
這段代碼,如果其他人傳遞一個(gè)帶有拋出Error對(duì)象的函數(shù)給runWithoutThrowing函數(shù)的話,將完美運(yùn)行。然而,如果他拋出一個(gè)String類型的話,則情況就麻煩了。
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);
可以看到這段代碼中,第二個(gè)console.log會(huì)告訴你這個(gè) Error 信息是undefined。這現(xiàn)在看起來(lái)不是很重要,但是如果你需要確定是否這個(gè)Error中確實(shí)包含某個(gè)屬性,或者用另一種方式處理Error的特殊屬性,那你就需要多花很多的功夫了。
另外,當(dāng)拋出一個(gè)非Error對(duì)象的值時(shí),你沒(méi)有訪問(wèn)Error對(duì)象的一些重要的數(shù)據(jù),比如它的堆棧,而這在一些編譯環(huán)境中是一個(gè)非常重要的Error對(duì)象屬性。
Error 還可以當(dāng)做其他普通對(duì)象一樣使用,你并不需要拋出它。這就是為什么它通常作為回調(diào)函數(shù)的第一個(gè)參數(shù),就像fs.readdir函數(shù)這樣:
const fs = require("fs"); fs.readdir("/example/i-do-not-exist", function callback(err, dirs) { if (err instanceof Error) { // "readdir"將會(huì)拋出一個(gè)異常,因?yàn)槟夸洸淮嬖? // 我們可以在我們的回調(diào)函數(shù)中使用 Error 對(duì)象 console.log("Error Message: " + err.message); console.log("See? We can use Errors without using try statements."); } else { console.log(dirs); } });
最后,你也可以在 promise 被 reject 的時(shí)候使用Error對(duì)象,這使得處理 promise reject 變得很簡(jiǎn)單。
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); } });使用 Stack Trace
ok,那么現(xiàn)在,你們所期待的部分來(lái)了:如何使用堆棧追蹤。
這一章專門討論支持 Error.captureStackTrace 的環(huán)境,如:NodeJS。
Error.captureStackTrace函數(shù)的第一個(gè)參數(shù)是一個(gè)object對(duì)象,第二個(gè)參數(shù)是一個(gè)可選的function。捕獲堆棧跟蹤所做的是要捕獲當(dāng)前堆棧的路徑(這是顯而易見(jiàn)的),并且在 object 對(duì)象上創(chuàng)建一個(gè)stack屬性來(lái)存儲(chǔ)它。如果提供了第二個(gè) function 參數(shù),那么這個(gè)被傳遞的函數(shù)將會(huì)被看成是本次堆棧調(diào)用的終點(diǎn),本次堆棧跟蹤只會(huì)展示到這個(gè)函數(shù)被調(diào)用之前。
我們來(lái)用幾個(gè)例子來(lái)更清晰的解釋下。我們將捕獲當(dāng)前堆棧路徑并且將其存儲(chǔ)到一個(gè)普通 object 對(duì)象中。
const myObj = {}; function c() { } function b() { // 這里存儲(chǔ)當(dāng)前的堆棧路徑,保存到myObj中 Error.captureStackTrace(myObj); c(); } function a() { b(); } // 首先調(diào)用這些函數(shù) a(); // 這里,我們看一下堆棧路徑往 myObj.stack 中存儲(chǔ)了什么 console.log(myObj.stack); // 這里將會(huì)打印如下堆棧信息到控制臺(tái) // 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(a被壓入棧),然后從a的內(nèi)部調(diào)用了b(b被壓入棧,并且在a的上面)。在b中,我們捕獲到了當(dāng)前堆棧路徑并且將其存儲(chǔ)在了myObj中。這就是為什么打印在控制臺(tái)上的只有a和b,而且是下面a上面b。
好的,那么現(xiàn)在,我們傳遞第二個(gè)參數(shù)到Error.captureStackTrace看看會(huì)發(fā)生什么?
const myObj = {}; function d() { // 這里存儲(chǔ)當(dāng)前的堆棧路徑,保存到myObj中 // 這次我們隱藏包含b在內(nèi)的b以后的所有堆棧幀 Error.captureStackTrace(myObj, b); } function c() { d(); } function b() { c(); } function a() { b(); } // 首先調(diào)用這些函數(shù) a(); // 這里,我們看一下堆棧路徑往 myObj.stack 中存儲(chǔ)了什么 console.log(myObj.stack); // 這里將會(huì)打印如下堆棧信息到控制臺(tái) // 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)
當(dāng)我們傳遞b到Error.captureStackTraceFunction里時(shí),它隱藏了b和在它以上的所有堆棧幀。這就是為什么堆棧路徑里只有a的原因。
看到這,你可能會(huì)問(wèn)這樣一個(gè)問(wèn)題:“為什么這是有用的呢?”。它之所以有用,是因?yàn)槟憧梢噪[藏所有的內(nèi)部實(shí)現(xiàn)細(xì)節(jié),而這些細(xì)節(jié)其他開(kāi)發(fā)者調(diào)用的時(shí)候并不需要知道。例如,在 Chai 中,我們用這種方法對(duì)我們代碼的調(diào)用者屏蔽了不相關(guān)的實(shí)現(xiàn)細(xì)節(jié)。
真實(shí)場(chǎng)景中的 Stack Trace 處理正如我在上一節(jié)中提到的,Chai 用棧處理技術(shù)使得堆棧路徑和調(diào)用者更加相關(guān),這里是我們?nèi)绾螌?shí)現(xiàn)它的。
首先,讓我們來(lái)看一下當(dāng)一個(gè) Assertion 失敗的時(shí)候,AssertionError的構(gòu)造函數(shù)做了什么。
// "ssfi"代表"起始堆棧函數(shù)",它是移除其他不相關(guān)堆棧幀的起始標(biāo)記 function AssertionError (message, _props, ssf) { var extend = exclude("name", "message", "stack", "constructor", "toJSON") , props = extend(_props || {}); // 默認(rèn)值 this.message = message || "Unspecified AssertionError"; this.showDiff = false; // 從屬性中copy for (var key in props) { this[key] = props[key]; } // 這里是和我們相關(guān)的 // 如果提供了起始堆棧函數(shù),那么我們從當(dāng)前堆棧路徑中獲取到, // 并且將其傳遞給"captureStackTrace",以保證移除其后的所有幀 ssf = ssf || arguments.callee; if (ssf && Error.captureStackTrace) { Error.captureStackTrace(this, ssf); } else { // 如果沒(méi)有提供起始堆棧函數(shù),那么使用原始堆棧 try { throw new Error(); } catch(e) { this.stack = e.stack; } } }
正如你在上面可以看到的,我們使用了Error.captureStackTrace來(lái)捕獲堆棧路徑,并且把它存儲(chǔ)在我們所創(chuàng)建的一個(gè)AssertionError實(shí)例中。然后傳遞了一個(gè)起始堆棧函數(shù)進(jìn)去(用if判斷如果存在則傳遞),這樣就從堆棧路徑中移除掉了不相關(guān)的堆棧幀,不顯示一些內(nèi)部實(shí)現(xiàn)細(xì)節(jié),保證了堆棧信息的“清潔”。
感興趣的讀者可以繼續(xù)看一下最近 @meeber 在 這里 的代碼。
在我們繼續(xù)看下面的代碼之前,我要先告訴你addChainableMethod都做了什么。它添加所傳遞的可以被鏈?zhǔn)秸{(diào)用的方法到 Assertion,并且用包含了 Assertion 的方法標(biāo)記 Assertion 本身。用ssfi(表示起始堆棧函數(shù)指示器)這個(gè)名字記錄。這意味著當(dāng)前 Assertion 就是堆棧的最后一幀,就是說(shuō)不會(huì)再多顯示任何 Chai 項(xiàng)目中的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)了。我在這里就不多列出來(lái)其整個(gè)代碼了,里面用了很多 trick 的方法,但是如果你想了解更多,可以從 這個(gè)鏈接 里獲取到。
在下面的代碼中,展示了lengthOf的 Assertion 的邏輯,它是用來(lái)檢查一個(gè)對(duì)象的確定長(zhǎng)度的。我們希望調(diào)用我們函數(shù)的開(kāi)發(fā)者這樣來(lái)使用:expect(["foo", "bar"]).to.have.lengthOf(2)。
function assertLength (n, msg) { if (msg) flag(this, "message", msg); var obj = flag(this, "object") , ssfi = flag(this, "ssfi"); // 密切關(guān)注這一行 new Assertion(obj, msg, ssfi, true).to.have.property("length"); var len = obj.length; // 這一行也是相關(guān)的 this.assert( len == n , "expected #{this} to have a length of #{exp} but got #{act}" , "expected #{this} to not have a length of #{act}" , n , len ); } Assertion.addChainableMethod("lengthOf", assertLength, assertLengthChain);
在代碼中,我著重對(duì)跟我們相關(guān)的代碼進(jìn)行了注釋,我們從this.assert的調(diào)用開(kāi)始。
下面是this.assert方法的代碼:
Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) { var ok = util.test(this, arguments); if (false !== showDiff) showDiff = true; if (undefined === expected && undefined === _actual) showDiff = false; if (true !== config.showDiff) showDiff = false; if (!ok) { msg = util.getMessage(this, arguments); var actual = util.getActual(this, arguments); // 這是和我們相關(guān)的行 throw new AssertionError(msg, { actual: actual , expected: expected , showDiff: showDiff }, (config.includeStack) ? this.assert : flag(this, "ssfi")); } };
assert方法主要用來(lái)檢查 Assertion 的布爾表達(dá)式是真還是假。如果是假,則我們必須實(shí)例化一個(gè)AssertionError。這里注意,當(dāng)我們實(shí)例化一個(gè)AssertionError對(duì)象的時(shí)候,我們也傳遞了一個(gè)起始堆棧函數(shù)指示器(ssfi)。如果配置標(biāo)記includeStack是打開(kāi)的,我們通過(guò)傳遞一個(gè)this.assert給調(diào)用者,以向他展示整個(gè)堆棧路徑。可是,如果includeStack配置是關(guān)閉的,我們則必須從堆棧路徑中隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié),這就需要用到存儲(chǔ)在ssfi中的標(biāo)記了。
ok,那么我們?cè)賮?lái)討論一下其他和我們相關(guān)的代碼:
new Assertion(obj, msg, ssfi, true).to.have.property("length");
可以看到,當(dāng)創(chuàng)建這個(gè)內(nèi)嵌 Assertion 的時(shí)候,我們傳遞了ssfi中已獲取到的內(nèi)容。這意味著,當(dāng)創(chuàng)建一個(gè)新的 Assertion 時(shí),將使用這個(gè)函數(shù)來(lái)作為從堆棧路徑中移除無(wú)用堆棧幀的起始點(diǎn)。順便說(shuō)一下,下面這段代碼是Assertion的構(gòu)造函數(shù)。
function Assertion (obj, msg, ssfi, lockSsfi) { // 這是和我們相關(guān)的行 flag(this, "ssfi", ssfi || Assertion); flag(this, "lockSsfi", lockSsfi); flag(this, "object", obj); flag(this, "message", msg); return util.proxify(this); }
還記得我在講述addChainableMethod時(shí)說(shuō)的,它用包含他自己的方法設(shè)置的ssfi標(biāo)記,這就意味著這是堆棧路徑中最底層的內(nèi)部幀,我們可以移除在它之上的所有幀。
回想上面的代碼,內(nèi)嵌 Assertion 用來(lái)判斷對(duì)象是不是有合適的長(zhǎng)度(Length)。傳遞ssfi到這個(gè) Assertion 中,要避免重置我們要將其作為起始指示器的堆棧幀,并且使先前的addChainableMethod在堆棧中保持可見(jiàn)狀態(tài)。
這看起來(lái)可能有點(diǎn)復(fù)雜,現(xiàn)在我們重新回顧一下,我們想要移除沒(méi)有用的堆棧幀都做了什么工作:
當(dāng)我們運(yùn)行一個(gè) Assertion 時(shí),我們?cè)O(shè)置它本身來(lái)作為我們移除其后面堆棧幀的標(biāo)記。
這個(gè) Assertion 開(kāi)始執(zhí)行,如果判斷失敗,那么從剛才我們所存儲(chǔ)的那個(gè)標(biāo)記開(kāi)始,移除其后面所有的內(nèi)部幀。
如果有內(nèi)嵌 Assertion,那么我們必須要使用包含當(dāng)前 Assertion 的方法作為移除后面堆棧幀的標(biāo)記,即放到ssfi中。因此我們要傳遞當(dāng)前ssfi(起始堆棧函數(shù)指示器)到我們即將要新創(chuàng)建的內(nèi)嵌 Assertion 中來(lái)存儲(chǔ)起來(lái)。
最后我還是強(qiáng)烈建議來(lái)閱讀一下 @meeber的評(píng)論 來(lái)加深對(duì)它的理解。
我最近正在寫(xiě)一本《React.js 小書(shū)》,對(duì) React.js 感興趣的童鞋,歡迎指點(diǎn)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/81597.html
摘要:封裝手寫(xiě)的方筆記使用檢測(cè)文件前端掘金副標(biāo)題可以做什么以及使用中會(huì)遇到的坑。目的是幫助人們用純中文指南實(shí)現(xiàn)復(fù)選框中多選功能前端掘金作者緝熙簡(jiǎn)介是推出的一個(gè)天挑戰(zhàn)。 深入理解 JavaScript Errors 和 Stack Traces - 前端 - 掘金譯者注:本文作者是著名 JavaScript BDD 測(cè)試框架 Chai.js 源碼貢獻(xiàn)者之一,Chai.js 中會(huì)遇到很多異常處理...
摘要:封裝手寫(xiě)的方筆記使用檢測(cè)文件前端掘金副標(biāo)題可以做什么以及使用中會(huì)遇到的坑。目的是幫助人們用純中文指南實(shí)現(xiàn)復(fù)選框中多選功能前端掘金作者緝熙簡(jiǎn)介是推出的一個(gè)天挑戰(zhàn)。 深入理解 JavaScript Errors 和 Stack Traces - 前端 - 掘金譯者注:本文作者是著名 JavaScript BDD 測(cè)試框架 Chai.js 源碼貢獻(xiàn)者之一,Chai.js 中會(huì)遇到很多異常處理...
摘要:調(diào)用堆棧實(shí)際上就是一個(gè)方法列表,按調(diào)用順序保存所有在運(yùn)行期被調(diào)用的方法。調(diào)用堆棧會(huì)將當(dāng)前正在執(zhí)行的函數(shù)調(diào)用壓入堆棧,一旦函數(shù)調(diào)用結(jié)束,又會(huì)將它移出堆棧。 原文 JavaScript Errors and Stack Traces in Depth 調(diào)用棧Call Stack是如何工作的 棧是一個(gè)后進(jìn)先出LIFO (Last in,F(xiàn)irst out)的數(shù)據(jù)結(jié)構(gòu)。調(diào)用堆棧實(shí)際上就是一個(gè)方...
摘要:譯者注翻譯一個(gè)對(duì)新手比較友好的工作原理解析系列文章注意以下全部是概念經(jīng)驗(yàn)豐富的老鳥(niǎo)可以離場(chǎng)啦正文從這里開(kāi)始隨著的流行團(tuán)隊(duì)們正在利用來(lái)支持多個(gè)級(jí)別的技術(shù)棧包括前端后端混合開(kāi)發(fā)嵌入式設(shè)備以及更多這篇文章旨在成為深入挖掘和實(shí)際上他是怎么工作的系列 譯者注 翻譯一個(gè)對(duì)新手比較友好的 JavaScript 工作原理解析系列文章 注意: 以下全部是概念,經(jīng)驗(yàn)豐富的老鳥(niǎo)可以離場(chǎng)啦 正文從這里開(kāi)始 隨...
摘要:監(jiān)聽(tīng)上報(bào)應(yīng)用無(wú)響應(yīng)是數(shù)據(jù)采集系統(tǒng)功能之一,本文講述一種可行實(shí)現(xiàn)方案。是一個(gè)用于監(jiān)聽(tīng)文件訪問(wèn)創(chuàng)建修改刪除移動(dòng)等操作的監(jiān)聽(tīng)器。為此本文同時(shí)提供一種線程輪詢措施,用于輔助監(jiān)聽(tīng)。 監(jiān)聽(tīng)上報(bào)ANR(Application Not Responding,應(yīng)用無(wú)響應(yīng))是數(shù)據(jù)采集系統(tǒng)功能之一,本文講述一種可行實(shí)現(xiàn)方案。 方案概述 ANR一般有三種類型[1]: KeyDispatchTimeout(5...
閱讀 1677·2021-11-22 09:34
閱讀 1737·2019-08-29 16:36
閱讀 2723·2019-08-29 15:43
閱讀 3165·2019-08-29 13:57
閱讀 1353·2019-08-28 18:05
閱讀 1940·2019-08-26 18:26
閱讀 3307·2019-08-26 10:39
閱讀 3511·2019-08-23 18:40