精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

深入理解JavaScript Errors和Stack Traces

開(kāi)發(fā) 開(kāi)發(fā)工具
本文我們聊聊Errors和Stack traces以及如何熟練地使用它們。

[[186908]]

這次我們聊聊 Errors 和 Stack traces 以及如何熟練地使用它們。

很多同學(xué)并不重視這些細(xì)節(jié),但是這些知識(shí)在你寫 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è)例子,看下面代碼:

  1. function c() { 
  2.     console.log('c'); 
  3.  
  4. function b() { 
  5.     console.log('b'); 
  6.     c(); 
  7.  
  8. function a() { 
  9.     console.log('a'); 
  10.     b(); 
  11.  
  12. 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í)候是從上往下讀的。

  1. function c() { 
  2.     console.log('c'); 
  3.     console.trace(); 
  4.  
  5. function b() { 
  6.     console.log('b'); 
  7.     c(); 
  8.  
  9. function a() { 
  10.     console.log('a'); 
  11.     b(); 
  12.  
  13. a(); 

當(dāng)我們?cè)?Node REPL 服務(wù)端執(zhí)行的時(shí)候,會(huì)返回如下:

  1. Trace 
  2.     at c (repl:3:9) 
  3.     at b (repl:3:1) 
  4.     at a (repl:3:1) 
  5.     at repl:1:1 // <-- For now feel free to ignore anything below this point, these are Node's internals 
  6.     at realRunInThisContextScript (vm.js:22:35) 
  7.     at sigintHandlersWrap (vm.js:98:12) 
  8.     at ContextifyScript.Script.runInThisContext (vm.js:24:12) 
  9.     at REPLServer.defaultEval (repl.js:313:29) 
  10.     at bound (domain.js:280:14) 
  11.     at REPLServer.runBound [as eval] (domain.js:293:12) 

從上面我們可以看到,當(dāng)棧信息從 c 中打印出來(lái)的時(shí)候,我看到了 a,b 和 c。現(xiàn)在,如果在 c 執(zhí)行完畢以后,在 b 中把 Stack trace 打印出來(lái),我們可以看到 c 已經(jīng)從棧中移出了,棧中只有 a 和 b。

  1. function c() { 
  2.     console.log('c'); 
  3.  
  4. function b() { 
  5.     console.log('b'); 
  6.     c(); 
  7.     console.trace(); 
  8.  
  9. function a() { 
  10.     console.log('a'); 
  11.     b(); 
  12.  
  13. a(); 

下面可以看到,c 已經(jīng)不在棧中了,在其執(zhí)行完以后,從棧中 pop 出去了。

  1. Trace 
  2.     at b (repl:4:9) 
  3.     at a (repl:3:1) 
  4.     at repl:1:1  // <-- For now feel free to ignore anything below this point, these are Node's internals 
  5.     at realRunInThisContextScript (vm.js:22:35) 
  6.     at sigintHandlersWrap (vm.js:98:12) 
  7.     at ContextifyScript.Script.runInThisContext (vm.js:24:12) 
  8.     at REPLServer.defaultEval (repl.js:313:29) 
  9.     at bound (domain.js:280:14) 
  10.     at REPLServer.runBound [as eval] (domain.js:293:12) 
  11.     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ǔ)句中,如:

  1. try { 
  2.     try { 
  3.         // 這里拋出的Error,將被下面的catch獲取到 
  4.         throw new Error('Nested error.');  
  5.     } catch (nestedErr) { 
  6.         // 這里會(huì)打印出來(lái) 
  7.         console.log('Nested catch'); 
  8.     } 
  9. } catch (err) { 
  10.     console.log('This will not run.'); 

你也可以把 try 語(yǔ)句內(nèi)嵌在 catch 和 finally 塊中:

  1. try { 
  2.     throw new Error('First error'); 
  3. } catch (err) { 
  4.     console.log('First catch running'); 
  5.     try { 
  6.         throw new Error('Second error'); 
  7.     } catch (nestedErr) { 
  8.         console.log('Second catch running.'); 
  9.     } 
  1. try { 
  2.     console.log('The try block is running...'); 
  3. } finally { 
  4.     try { 
  5.         throw new Error('Error inside finally.'); 
  6.     } catch (err) { 
  7.         console.log('Caught an error inside the finally block.'); 
  8.     } 

這里給出另外一個(gè)重要的提示:你可以拋出非 Error 對(duì)象的值。盡管這看起來(lái)很炫酷,很靈活,但實(shí)際上這個(gè)用法并不好,尤其在一個(gè)開(kāi)發(fā)者改另一個(gè)開(kāi)發(fā)者寫的庫(kù)的時(shí)候。因?yàn)檫@樣代碼沒(méi)有一個(gè)標(biāo)準(zhǔn),你不知道其他人會(huì)拋出什么信息。這樣的話,你就不能簡(jiǎn)單的相信拋出的 Error 信息了,因?yàn)橛锌赡芩⒉皇?Error 信息,而是一個(gè)字符串或者一個(gè)數(shù)字。另外這也導(dǎo)致了如果你需要處理 Stack trace 或者其他有意義的元數(shù)據(jù),也將變的很困難。

例如給你下面這段代碼:

  1. function runWithoutThrowing(func) { 
  2.     try { 
  3.         func(); 
  4.     } catch (e) { 
  5.         console.log('There was an error, but I will not throw it.'); 
  6.         console.log('The error\'s message was: ' + e.message) 
  7.     } 
  8.  
  9. function funcThatThrowsError() { 
  10.     throw new TypeError('I am a TypeError.'); 
  11.  
  12. runWithoutThrowing(funcThatThrowsError); 

這段代碼,如果其他人傳遞一個(gè)帶有拋出 Error 對(duì)象的函數(shù)給 runWithoutThrowing 函數(shù)的話,將***運(yùn)行。然而,如果他拋出一個(gè) String 類型的話,則情況就麻煩了。

  1. function runWithoutThrowing(func) { 
  2.     try { 
  3.         func(); 
  4.     } catch (e) { 
  5.         console.log('There was an error, but I will not throw it.'); 
  6.         console.log('The error\'s message was: ' + e.message) 
  7.     } 
  8.  
  9. function funcThatThrowsString() { 
  10.     throw 'I am a String.'; 
  11.  
  12. 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ù)這樣:

  1. const fs = require('fs'); 
  2.  
  3. fs.readdir('/example/i-do-not-exist', function callback(err, dirs) { 
  4.     if (err instanceof Error) { 
  5.         // 'readdir'將會(huì)拋出一個(gè)異常,因?yàn)槟夸洸淮嬖?nbsp;
  6.         // 我們可以在我們的回調(diào)函數(shù)中使用 Error 對(duì)象 
  7.         console.log('Error Message: ' + err.message); 
  8.         console.log('See? We can use  Errors  without using try statements.'); 
  9.     } else { 
  10.         console.log(dirs); 
  11.     } 
  12. }); 

***,你也可以在 promise 被 reject 的時(shí)候使用 Error 對(duì)象,這使得處理 promise reject 變得很簡(jiǎn)單。

  1. new Promise(function(resolve, reject) { 
  2.     reject(new Error('The promise was rejected.')); 
  3. }).then(function() { 
  4.     console.log('I am an error.'); 
  5. }).catch(function(err) { 
  6.     if (err instanceof Error) { 
  7.         console.log('The promise was rejected with an error.'); 
  8.         console.log('Error Message: ' + err.message); 
  9.     } 
  10. }); 

三、使用 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ì)象中。

  1. const myObj = {}; 
  2.  
  3. function c() { 
  4.  
  5. function b() { 
  6.     // 這里存儲(chǔ)當(dāng)前的堆棧路徑,保存到myObj中 
  7.     Error.captureStackTrace(myObj); 
  8.     c(); 
  9.  
  10. function a() { 
  11.     b(); 
  12.  
  13. // 首先調(diào)用這些函數(shù) 
  14. a(); 
  15.  
  16. // 這里,我們看一下堆棧路徑往 myObj.stack 中存儲(chǔ)了什么 
  17. console.log(myObj.stack); 
  18.  
  19. // 這里將會(huì)打印如下堆棧信息到控制臺(tái) 
  20. //    at b (repl:3:7) <-- Since it was called inside B, the B call is the last entry in the stack 
  21. //    at a (repl:2:1) 
  22. //    at repl:1:1 <-- Node internals below this line 
  23. //    at realRunInThisContextScript (vm.js:22:35) 
  24. //    at sigintHandlersWrap (vm.js:98:12) 
  25. //    at ContextifyScript.Script.runInThisContext (vm.js:24:12) 
  26. //    at REPLServer.defaultEval (repl.js:313:29) 
  27. //    at bound (domain.js:280:14) 
  28. //    at REPLServer.runBound [as eval] (domain.js:293:12) 
  29. //    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ā)生什么?

  1. const myObj = {}; 
  2.  
  3. function d() { 
  4.     // 這里存儲(chǔ)當(dāng)前的堆棧路徑,保存到myObj中 
  5.     // 這次我們隱藏包含b在內(nèi)的b以后的所有堆棧幀 
  6.     Error.captureStackTrace(myObj, b); 
  7.  
  8. function c() { 
  9.     d(); 
  10.  
  11. function b() { 
  12.     c(); 
  13.  
  14. function a() { 
  15.     b(); 
  16.  
  17. // 首先調(diào)用這些函數(shù) 
  18. a(); 
  19.  
  20. // 這里,我們看一下堆棧路徑往 myObj.stack 中存儲(chǔ)了什么 
  21. console.log(myObj.stack); 
  22.  
  23. // 這里將會(huì)打印如下堆棧信息到控制臺(tái) 
  24. //    at a (repl:2:1) <-- As you can see here we only get frames before `b` was called 
  25. //    at repl:1:1 <-- Node internals below this line 
  26. //    at realRunInThisContextScript (vm.js:22:35) 
  27. //    at sigintHandlersWrap (vm.js:98:12) 
  28. //    at ContextifyScript.Script.runInThisContext (vm.js:24:12) 
  29. //    at REPLServer.defaultEval (repl.js:313:29) 
  30. //    at bound (domain.js:280:14) 
  31. //    at REPLServer.runBound [as eval] (domain.js:293:12) 
  32. //    at REPLServer.onLine (repl.js:513:10) 
  33. //    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ù)做了什么。

  1. // 'ssfi'代表"起始堆棧函數(shù)",它是移除其他不相關(guān)堆棧幀的起始標(biāo)記 
  2. function AssertionError (message, _props, ssf) { 
  3.   var extend = exclude('name', 'message', 'stack', 'constructor', 'toJSON') 
  4.     , props = extend(_props || {}); 
  5.  
  6.   // 默認(rèn)值 
  7.   this.message = message || 'Unspecified AssertionError'; 
  8.   this.showDiff = false
  9.  
  10.   // 從屬性中copy 
  11.   for (var key in props) { 
  12.     this[key] = props[key]; 
  13.   } 
  14.  
  15.   // 這里是和我們相關(guān)的 
  16.   // 如果提供了起始堆棧函數(shù),那么我們從當(dāng)前堆棧路徑中獲取到, 
  17.   // 并且將其傳遞給'captureStackTrace',以保證移除其后的所有幀 
  18.   ssfssf = ssf || arguments.callee; 
  19.   if (ssf && Error.captureStackTrace) { 
  20.     Error.captureStackTrace(this, ssf); 
  21.   } else { 
  22.     // 如果沒(méi)有提供起始堆棧函數(shù),那么使用原始堆棧 
  23.     try { 
  24.       throw new Error(); 
  25.     } catch(e) { 
  26.       this.stack = e.stack; 
  27.     } 
  28.   } 

正如你在上面可以看到的,我們使用了 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ù)看下面的代碼之前,我要先告訴你 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)。

  1. function assertLength (n, msg) { 
  2.     if (msg) flag(this, 'message', msg); 
  3.     var obj = flag(this, 'object') 
  4.         , ssfi = flag(this, 'ssfi'); 
  5.  
  6.     // 密切關(guān)注這一行 
  7.     new Assertion(obj, msg, ssfi, true).to.have.property('length'); 
  8.     var len = obj.length; 
  9.  
  10.     // 這一行也是相關(guān)的 
  11.     this.assert( 
  12.             len == n 
  13.         , 'expected #{this} to have a length of #{exp} but got #{act}' 
  14.         , 'expected #{this} to not have a length of #{act}' 
  15.         , n 
  16.         , len 
  17.     ); 
  18.  
  19. Assertion.addChainableMethod('lengthOf', assertLength, assertLengthChain); 

下面是 this.assert 方法的代碼:

  1. function assertLength (n, msg) { 
  2.     if (msg) flag(this, 'message', msg); 
  3.     var obj = flag(this, 'object') 
  4.         , ssfi = flag(this, 'ssfi'); 
  5.  
  6.     // 密切關(guān)注這一行 
  7.     new Assertion(obj, msg, ssfi, true).to.have.property('length'); 
  8.     var len = obj.length; 
  9.  
  10.     // 這一行也是相關(guān)的 
  11.     this.assert( 
  12.             len == n 
  13.         , 'expected #{this} to have a length of #{exp} but got #{act}' 
  14.         , 'expected #{this} to not have a length of #{act}' 
  15.         , n 
  16.         , len 
  17.     ); 
  18.  
  19. Assertion.addChainableMethod('lengthOf', assertLength, assertLengthChain); 

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)的代碼:

  1. 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ù)。

  1. function Assertion (obj, msg, ssfi, lockSsfi) { 
  2.     // 這是和我們相關(guān)的行 
  3.     flag(this, 'ssfi', ssfi || Assertion); 
  4.     flag(this, 'lockSsfi', lockSsfi); 
  5.     flag(this, 'object', obj); 
  6.     flag(this, 'message', msg); 
  7.  
  8.     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)有用的堆棧幀都做了什么工作:

  1. 當(dāng)我們運(yùn)行一個(gè) Assertion 時(shí),我們?cè)O(shè)置它本身來(lái)作為我們移除其后面堆棧幀的標(biāo)記。
  2. 這個(gè) Assertion 開(kāi)始執(zhí)行,如果判斷失敗,那么從剛才我們所存儲(chǔ)的那個(gè)標(biāo)記開(kāi)始,移除其后面所有的內(nèi)部幀。
  3. 如果有內(nèi)嵌 Assertion,那么我們必須要使用包含當(dāng)前 Assertion 的方法作為移除后面堆棧幀的標(biāo)記,即放到 ssfi 中。因此我們要傳遞當(dāng)前 ssfi(起始堆棧函數(shù)指示器)到我們即將要新創(chuàng)建的內(nèi)嵌 Assertion 中來(lái)存儲(chǔ)起來(lái)。

點(diǎn)擊《深入理解 JavaScript Errors 和 Stack Traces》閱讀原文。

【本文是51CTO專欄作者“胡子大哈”的原創(chuàng)文章,轉(zhuǎn)載請(qǐng)聯(lián)系作者本人獲取授權(quán)】

戳這里,看該作者更多好文

責(zé)任編輯:趙寧寧 來(lái)源: 51CTO專欄
相關(guān)推薦

2021-02-17 11:25:33

前端JavaScriptthis

2017-04-25 15:30:23

堆棧函數(shù)JavaScript

2015-11-04 09:57:18

JavaScript原型

2019-11-05 10:03:08

callback回調(diào)函數(shù)javascript

2024-07-18 10:12:04

2013-11-05 13:29:04

JavaScriptreplace

2011-09-06 09:56:24

JavaScript

2019-03-13 08:00:00

JavaScript作用域前端

2020-12-16 09:47:01

JavaScript箭頭函數(shù)開(kāi)發(fā)

2020-07-24 10:00:00

JavaScript執(zhí)行上下文前端

2011-03-02 12:33:00

JavaScript

2024-05-08 13:52:04

JavaScriptWeb應(yīng)用程序

2012-01-05 15:07:11

JavaScript

2016-12-08 15:36:59

HashMap數(shù)據(jù)結(jié)構(gòu)hash函數(shù)

2020-07-21 08:26:08

SpringSecurity過(guò)濾器

2010-06-01 15:25:27

JavaCLASSPATH

2013-07-31 10:04:42

hadoopHadoop集群集群和網(wǎng)絡(luò)

2012-11-08 14:47:52

Hadoop集群

2012-08-31 10:00:12

Hadoop云計(jì)算群集網(wǎng)絡(luò)

2024-09-02 14:12:56

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

精品久久久久久久| 国产偷倩在线播放| 日本女人一区二区三区| 日韩在线资源网| 天堂va欧美va亚洲va老司机| h片在线观看下载| 久久久欧美精品sm网站| 国产一区二区在线免费视频| 深夜福利影院在线观看| 欧美偷窥清纯综合图区| 欧美日韩在线免费视频| www.亚洲成人网| 第一福利在线| 成人免费高清视频在线观看| 国产精品免费一区豆花| 色老板免费视频| 一区三区在线欧| 91精品国产综合久久久久久久久久| 免费cad大片在线观看| 国内精品一区视频| 成人h精品动漫一区二区三区| 国产aⅴ夜夜欢一区二区三区| 亚洲 欧美 变态 另类 综合| 精品精品精品| 欧美日韩成人在线| 欧美日韩第二页| 亚洲妇熟xxxx妇色黄| 久久精品视频网| 国产激情999| 免费无遮挡无码永久在线观看视频 | 午夜肉伦伦影院| 欧洲精品二区| 亚洲人精品午夜| 亚洲高清在线播放| 日本一区高清| 99久久99久久精品国产片果冻| 91精品视频观看| 最新黄色网址在线观看| 男女精品网站| 欧美一区二区影院| 日本少妇性生活| 欧美一区免费| 久久亚洲精品视频| 精品国产国产综合精品| 成人在线视频免费观看| 亚洲国产一区自拍| 男人的天堂影院| 91欧美极品| 欧美tickling网站挠脚心| 亚洲一区二区三区四区精品| 中文字幕综合| 欧美日韩午夜影院| 亚洲国产成人va在线观看麻豆| 日韩大片欧美大片| 色88888久久久久久影院野外| 欧美色图另类小说| 亚洲精品mv| 日韩欧美视频一区二区三区| 成人在线免费在线观看| 亚洲伊人av| 日韩欧美精品中文字幕| 好男人www社区| 久久av日韩| 5566中文字幕一区二区电影| 爱豆国产剧免费观看大全剧苏畅| 日韩欧乱色一区二区三区在线| 欧美视频第二页| www.五月天色| 成人高潮视频| 日韩精品在线视频美女| a级大片在线观看| 成人在线免费观看网站| 久久久国产影院| 久久久久免费看| 一区二区三区精品视频在线观看| 51ⅴ精品国产91久久久久久| 国产成人无码av| 老司机免费视频一区二区| 成人国产精品日本在线| а√中文在线资源库| 不卡一区二区中文字幕| 欧美日韩精品免费观看视一区二区 | 麻豆一区二区在线| 亚洲最大福利网站| 天天综合网天天综合| 国产日韩v精品一区二区| 午夜在线视频免费观看| a'aaa级片在线观看| 91久久精品国产91性色tv | 久久国产视频播放| 久热re这里精品视频在线6| 国产日韩av在线播放| 亚洲国产视频一区二区三区| 久久久久久免费毛片精品| 综合一区中文字幕| av中文在线资源库| 欧美日韩亚洲综合在线 | 亚洲天堂av老司机| 国产成人无码a区在线观看视频| 一二区成人影院电影网| 日韩欧美一二区| 亚洲理论片在线观看| 欧美成人高清| 国产精品高精视频免费| 亚洲av无码乱码国产精品| 久久久亚洲午夜电影| 粉嫩av一区二区三区天美传媒| 成人勉费视频| 精品美女一区二区| 精品视频第一页| 国产九九精品| 99c视频在线| 91xxx在线观看| 婷婷综合五月天| 天堂网成人在线| 精品久久久久久久久久久下田| 欧美精品福利视频| 国产精品玖玖玖| 久久精品人人做人人综合| 国产真人做爰毛片视频直播| 欧美高清免费| 国产亚洲精品久久久久久| 国产成人啪精品午夜在线观看| 精品一区二区三区免费| 日本视频一区二区不卡| 九色porny自拍视频在线观看 | 91免费精品| 日韩美女在线观看| 五月天福利视频| 亚洲电影激情视频网站| 亚洲av无一区二区三区久久| 日韩一区自拍| 国产精品欧美日韩一区二区| 暖暖视频在线免费观看| 性久久久久久久久| 黑森林av导航| 亚洲视频碰碰| 成人午夜电影免费在线观看| av网站大全在线| 91精品国产综合久久婷婷香蕉| 国产免费嫩草影院| 日韩av高清在线观看| 女同一区二区| 免费观看一级欧美片| 日韩精品免费观看| 国产a∨精品一区二区三区仙踪林| 国产福利一区二区| 成人手机在线播放| 亚洲精品一二三**| 欧美日韩xxxxx| 亚洲精品免费在线观看视频| 亚洲精品国产第一综合99久久| 免费成年人高清视频| 久久密一区二区三区| 国产精品综合久久久| www 日韩| 在线不卡欧美精品一区二区三区| 一级性生活免费视频| 国内精品免费在线观看| 熟女视频一区二区三区| 免费观看性欧美大片无片| 色综合久综合久久综合久鬼88| 精品国产亚洲AV| 亚洲一区二区三区四区的| yy1111111| 老牛影视一区二区三区| 亚洲高清视频在线观看| 精品国产三区在线| 国产做受69高潮| 久久精品色图| 欧美久久婷婷综合色| 欧美国产在线看| 91亚洲永久精品| 蜜臀久久99精品久久久酒店新书| 成人情趣视频网站| 97碰碰视频| 国产社区精品视频| 中文字幕久精品免费视频| 国产精品久久久久久久久久久久久久久久久久 | 在线观看精品国产视频| 国产老妇伦国产熟女老妇视频| 亚洲一区二区三区自拍| 亚洲永久精品ww.7491进入| 蜜臀a∨国产成人精品| 男人天堂网站在线| 亚州国产精品| 91日本在线视频| 黄色软件视频在线观看| 日韩在线免费视频观看| 亚洲欧美另类综合| 欧美中文字幕一区| 麻豆chinese极品少妇| 久久精品视频在线看| 极品人妻一区二区| 老司机精品视频网站| 成人毛片100部免费看| 台湾佬综合网| 91精品久久久久久蜜桃| 91av亚洲| 久久久久久久激情视频| jzzjzzjzz亚洲成熟少妇| 精品国免费一区二区三区| 亚洲精品一区二三区| 夜夜嗨av一区二区三区网页| 日韩人妻无码精品综合区| 国产一区在线观看视频| 免费午夜视频在线观看| 欧美日韩国产亚洲一区| 亚洲高清在线播放| 亚洲人亚洲人色久| 成人xxxxx色| 亚洲狼人在线| 国产精品久久在线观看| www.超碰在线| 精品中文字幕在线2019| aaa在线观看| 亚洲免费电影一区| 天堂av手机版| 精品久久国产字幕高潮| 国产精品玖玖玖| 欧美日韩一级黄| 免费无码国产精品| 精品欧美一区二区三区| 久久网一区二区| 亚洲欧美日韩国产综合在线| 精品成人无码一区二区三区| 97精品视频在线观看自产线路二| 搡的我好爽在线观看免费视频| 日韩成人免费电影| 欧美韩国日本在线| 国产日韩视频| 国产精品视频一区二区三区四区五区 | 日韩激情在线视频| 亚洲精品久久久久久动漫器材一区| 欧美丰满少妇xxxbbb| 中文字幕在线观看第二页| 在线观看一区日韩| 性色av免费观看| 色香色香欲天天天影视综合网| 激情五月色婷婷| 亚洲成人在线网站| 国产一级在线观看视频| 亚洲尤物在线视频观看| 久久久久香蕉视频| 一二三区精品视频| 黄网站免费在线| 亚洲高清免费在线| 国产视频91在线| 欧美日韩国产激情| 国产剧情在线视频| 一本久道久久综合中文字幕| 无码人妻丰满熟妇奶水区码| 色婷婷综合久久久中文一区二区| 久久99国产综合精品免费| 欧美性猛交xxxxx水多| 中文字幕69页| 欧美在线小视频| 亚洲天堂中文在线| 91精品国产91综合久久蜜臀| 亚洲怡红院av| 日韩欧美激情四射| 国产成人无码www免费视频播放| 精品国产一二三| 天堂在线一二区| 亚洲日本aⅴ片在线观看香蕉| 岛国在线视频| 久久精品国产久精国产一老狼| 在线观看免费视频你懂的| 欧美激情精品久久久| 午夜激情电影在线播放| 国产精品久久久久久亚洲调教| 99精品国产九九国产精品| 91免费在线观看网站| 日本国产精品| 一区二区视频国产| 韩日欧美一区| 日韩视频第二页| 韩国成人在线视频| 玖玖爱在线精品视频| 久久精品在线免费观看| 麻豆网址在线观看| 亚洲成人av福利| 亚洲第一网站在线观看| 91精品国产综合久久蜜臀| 香蕉av一区二区三区| 日韩最新在线视频| www视频在线观看| 日韩av电影国产| 免费一区二区三区在线视频| 久久国产精品99久久久久久丝袜| 日韩理论电影| 男女激情无遮挡| 久久99久久99精品免视看婷婷| 最新日本中文字幕| 国产精品国产三级国产专播品爱网| 精品无码m3u8在线观看| 91久久线看在观草草青青| 亚洲av少妇一区二区在线观看 | 在线不卡中文字幕| 日本电影一区二区在线观看| 久久久国产精品视频| a日韩av网址| 国产高清在线一区| 色综合狠狠操| 一本大道熟女人妻中文字幕在线| 韩国理伦片一区二区三区在线播放| 国产熟妇搡bbbb搡bbbb| 一区二区三区成人| 在线亚洲欧美日韩| 亚洲精品中文字幕av| 丝袜在线观看| 国产日韩欧美黄色| 欧美欧美黄在线二区| 欧美中文字幕在线观看视频| 美女视频一区二区三区| 一本色道久久综合亚洲精品图片| 亚洲最新在线观看| 亚洲视频中文字幕在线观看| 日韩精品一二三四区| 免费在线看污片| 成人激情视频在线| 欧美日韩国产高清电影| 免费高清在线观看免费| 成人性生交大合| 免费一级黄色大片| 91精品国产综合久久久久久| 69xxxx欧美| 国产精品视频区| 欧美伦理影院| 在线观看的毛片| 国产婷婷精品av在线| 手机看片久久久| 日韩黄在线观看| 密臀av在线播放| 国产一区喷水| 激情综合电影网| 四虎成人免费视频| 午夜精品在线看| 日韩一级中文字幕| 97精品视频在线播放| 岛国精品一区| 久久精品国产sm调教网站演员| 国产成人精品免费一区二区| 九九久久免费视频| 精品国产亚洲在线| 国内激情视频在线观看| 精品日韩电影| av不卡在线看| 日本高清www| 日本精品视频一区二区三区| 猫咪在线永久网站| 国产精品久久久av| 欧美高清视频手机在在线| 五月婷婷六月丁香激情| 亚洲精品中文字幕乱码三区| 精品国产999久久久免费| 色综合久久88色综合天天看泰| 亚洲天堂中文字幕在线观看| 亚洲理论电影在线观看| 91丨九色丨尤物| 免费看污视频的网站| 深夜福利国产精品| 国产一区二区| www.av毛片| 久久久高清一区二区三区| 成人午夜精品视频| 久久大大胆人体| 成人三级av在线| 蜜臀av午夜一区二区三区 | 在线国产99| 国产不卡在线视频| 伊人手机在线视频| 中文字幕视频一区二区在线有码| 在线观看欧美| 又大又硬又爽免费视频| 久久久久亚洲蜜桃| 一区二区三区亚洲视频| 欧美黑人狂野猛交老妇| 美女少妇全过程你懂的久久| 天堂中文视频在线| 一区二区三区视频在线看| 少妇人妻一区二区| 国产精品美女网站| 欧美日韩hd| 97人妻精品一区二区免费| 7777精品伊人久久久大香线蕉的| 国产91足控脚交在线观看| 欧美不卡三区| 国产一区二区女| 久久夜色精品国产噜噜亚洲av| yellow中文字幕久久| 欧美色图婷婷| 亚洲图片 自拍偷拍| 欧美日韩免费在线观看| 成人免费看片| 裸模一区二区三区免费| 国产在线麻豆精品观看| 日韩在线播放中文字幕| 欧美成人三级视频网站| 精品免费av|