自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入理解JavaScript錯(cuò)誤和堆棧追蹤

開發(fā) 前端
有時(shí)候人們并不關(guān)注這些細(xì)節(jié),但這方面的知識(shí)肯定有用,尤其是當(dāng)你正在編寫與測(cè)試或errors相關(guān)的庫。例如這個(gè)星期我們的chai中出現(xiàn)了一個(gè)令人驚嘆的Pull Request,它大大改進(jìn)了我們處理堆棧跟蹤的方式,并在用戶斷言失敗時(shí)提供了更多的信息。

有時(shí)候人們并不關(guān)注這些細(xì)節(jié),但這方面的知識(shí)肯定有用,尤其是當(dāng)你正在編寫與測(cè)試或errors相關(guān)的庫。例如這個(gè)星期我們的chai中出現(xiàn)了一個(gè)令人驚嘆的Pull Request,它大大改進(jìn)了我們處理堆棧跟蹤的方式,并在用戶斷言失敗時(shí)提供了更多的信息。

深入理解JavaScript錯(cuò)誤和堆棧追蹤

操作堆棧記錄可以讓你清理無用數(shù)據(jù),并集中精力處理重要事項(xiàng)。此外,當(dāng)你真正弄清楚Error及其屬性,你將會(huì)更有信心地利用它。

本文開頭部分或許太過于簡(jiǎn)單,但當(dāng)你開始處理堆棧記錄時(shí),它將變得稍微有些復(fù)雜,所以請(qǐng)確保你在開始這個(gè)那部分章節(jié)之前已經(jīng)充分理解前面的內(nèi)容。

堆棧調(diào)用如何工作

在談?wù)揺rrors之前我們必須明白堆棧調(diào)用如何工作。它非常簡(jiǎn)單,但對(duì)于我們將要深入的內(nèi)容而言卻是至關(guān)重要的。如果你已經(jīng)知道這部分內(nèi)容,請(qǐng)隨時(shí)跳過本節(jié)。

每當(dāng)函數(shù)被調(diào)用,它都會(huì)被推到堆棧的頂部。函數(shù)執(zhí)行完畢,便會(huì)從堆棧頂部移除。

這種數(shù)據(jù)結(jié)構(gòu)的有趣之處在于***一個(gè)入棧的將會(huì)***個(gè)從堆棧中移除,這也就是我們所熟悉的LIFO(后進(jìn),先出)特性。

這也就是說我們?cè)诤瘮?shù)x中調(diào)用函數(shù)y,那么對(duì)應(yīng)的堆棧中的順序?yàn)閤 y。

假設(shè)你有下面這樣的代碼:

  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)執(zhí)行a函數(shù)時(shí),a便會(huì)添加到堆棧的頂部,然后當(dāng)b函數(shù)在a函數(shù)中被調(diào)用,b也會(huì)被添加到堆棧的頂部,依次類推,在b中調(diào)用c也會(huì)發(fā)生同樣的事情。

當(dāng)c執(zhí)行時(shí),堆棧中的函數(shù)的順序?yàn)閍 b c

c執(zhí)行完畢后便會(huì)從棧頂移除,這時(shí)控制流重新回到了b中,b執(zhí)行完畢同樣也會(huì)從棧頂移除,***控制流又回到了a中,***a執(zhí)行完畢,a也從堆棧中移除。

我們可以利用console.trace()來更好的演示這種行為,它會(huì)在控制臺(tái)打印出當(dāng)前堆棧中的記錄。此外,通常而言你應(yīng)該從上到下讀取堆棧記錄。想想下面的每一行代碼都是在哪調(diào)用的。

  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(); 

在Node REPL服務(wù)器上運(yùn)行上述代碼會(huì)得到如下結(jié)果:

  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è)赾中打印堆棧,堆棧中的記錄為a,b,c。

如果我們現(xiàn)在在b中并且在c執(zhí)行完之后打印堆棧,我們將會(huì)發(fā)現(xiàn)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. a(); 

正如你看到的那樣,堆棧中已經(jīng)沒有c,因?yàn)樗呀?jīng)完成運(yùn)行,已經(jīng)被彈出去了。

  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) 

總結(jié):調(diào)用方法,方法便會(huì)添加到堆棧頂部,執(zhí)行完畢之后,它就會(huì)從堆棧中彈出。

Error對(duì)象 和 Error處理

當(dāng)程序發(fā)生錯(cuò)誤時(shí),通常都會(huì)拋出一個(gè)Error對(duì)象。Error對(duì)象也可以作為一個(gè)原型,用戶可以擴(kuò)展它并創(chuàng)建自定義錯(cuò)誤。

Error.prototype對(duì)象通常有以下屬性:

  • constructor- 實(shí)例原型的構(gòu)造函數(shù)。
  • message - 錯(cuò)誤信息
  • name - 錯(cuò)誤名稱

以上都是標(biāo)準(zhǔn)屬性,(但)有時(shí)候每個(gè)環(huán)境都有其特定的屬性,在例如Node,F(xiàn)irefox,Chorme,Edge,IE 10+,Opera 和 Safari 6+ 中,還有一個(gè)包含錯(cuò)誤堆棧記錄的stack屬性。錯(cuò)誤堆棧記錄包含從(堆棧底部)它自己的構(gòu)造函數(shù)到(堆棧頂部)所有的堆棧幀。

如果想了解更多關(guān)于Error對(duì)象的具體屬性,我強(qiáng)烈推薦MDN上的這篇文章。

拋出錯(cuò)誤必須使用throw關(guān)鍵字,你必須將可能拋出錯(cuò)誤的代碼包裹在try代碼塊內(nèi)并緊跟著一個(gè)catch代碼塊來捕獲拋出的錯(cuò)誤。

正如Java中的錯(cuò)誤處理,try/catch代碼塊后緊跟著一個(gè)finally代碼塊在JavaScript中也是同樣允許的,無論try代碼塊內(nèi)是否拋出異常,finally代碼塊內(nèi)的代碼都會(huì)執(zhí)行。在完成處理之后,***實(shí)踐是在finally代碼塊中做一些清理的事情,(因?yàn)?無論你的操作是否生效,都不會(huì)影響到它的執(zhí)行。

(鑒于)上面所談到的所有事情對(duì)大多數(shù)人來講都是小菜一碟,那么就讓我們來談一些不為人所知的細(xì)節(jié)。

try代碼塊后面不必緊跟著catch,但(此種情況下)其后必須緊跟著finally。這意味著我們可以使用三種不同形式的try語句:

  • try...catch
  • try...finally
  • try...catch...finally

Try語句可以像下面這樣互相嵌套:

  1. try { 
  2.     try { 
  3.         throw new Error('Nested error.'); // The error thrown here will be caught by its own `catch` clause 
  4.     } catch (nestedErr) { 
  5.         console.log('Nested catch'); // This runs 
  6.     } 
  7. } catch (err) { 
  8.     console.log('This will not run.'); 

你甚至還可以在catch和finally代碼塊中嵌套try語句:

  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.     } 
  10.  
  11. try { 
  12.     console.log('The try block is running...'); 
  13. } finally { 
  14.     try { 
  15.         throw new Error('Error inside finally.'); 
  16.     } catch (err) { 
  17.         console.log('Caught an error inside the finally block.'); 
  18.     } 

還有很重要的一點(diǎn)值得注意,那就是我們甚至可以大可不必拋出Error對(duì)象。盡管這看起來非常cool且非常自由,但實(shí)際并非如此,尤其是對(duì)開發(fā)第三方庫的開發(fā)者來說,因?yàn)樗麄儽仨毺幚碛脩?使用庫的開發(fā)者)的代碼。由于缺乏標(biāo)準(zhǔn),他們并不能把控用戶的行為。你不能相信用戶并簡(jiǎn)單的拋出一個(gè)Error對(duì)象,因?yàn)樗麄儾灰欢〞?huì)那么做而是僅僅拋出一個(gè)字符串或者數(shù)字(鬼知道用戶會(huì)拋出什么)。這也使得處理必要的堆棧跟蹤和其他有意義的元數(shù)據(jù)變得更加困難。

假設(shè)有以下代碼:

  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ù)(那就謝天謝地了),然而總有些人偷想懶直接拋出一個(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); 

現(xiàn)在第二個(gè)console.log會(huì)打印出 the error’s message is undefined.這么看來也沒多大的事(后果)呀,但是如果您需要確保某些屬性存在于Error對(duì)象上,或以另一種方式(例如Chai的throws斷言 does))處理Error對(duì)象的特定屬性,那么你做需要更多的工作,以確保它會(huì)正常工資。

此外,當(dāng)拋出的值不是Error對(duì)象時(shí),你無法訪問其他重要數(shù)據(jù),例如stack,在某些環(huán)境中它是Error對(duì)象的一個(gè)屬性。

Errors也可以像其他任何對(duì)象一樣使用,并不一定非得要拋出他們,這也是它們?yōu)槭裁炊啻伪挥米骰卣{(diào)函數(shù)的***個(gè)參數(shù)(俗稱 err first)。 在下面的fs.readdir()例子中就是這么用的。

  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` will throw an error because that directory does not exist 
  6.         // We will now be able to use the error object passed by it in our callback function 
  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. }); 

***,在rejecting promises時(shí)也可以使用Error對(duì)象。這使得它更容易處理promise rejections:

  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. }); 

操縱堆棧跟蹤

上面啰嗦了那么多,***的重頭戲來了,那就是如何操縱堆棧跟蹤。

本章專門針對(duì)那些像NodeJS支Error.captureStackTrace的環(huán)境。

Error.captureStackTrace函數(shù)接受一個(gè)object作為***個(gè)參數(shù),第二個(gè)參數(shù)是可選的,接受一個(gè)函數(shù)。capture stack trace 捕獲當(dāng)前堆棧跟蹤,并在目標(biāo)對(duì)象中創(chuàng)建一個(gè)stack屬性來存儲(chǔ)它。如果提供了第二個(gè)參數(shù),則傳遞的函數(shù)將被視為調(diào)用堆棧的終點(diǎn),因此堆棧跟蹤將僅顯示調(diào)用該函數(shù)之前發(fā)生的調(diào)用。

讓我們用例子來說明這一點(diǎn)。首先,我們將捕獲當(dāng)前堆棧跟蹤并將其存儲(chǔ)在公共對(duì)象中。

  1. const myObj = {}; 
  2.  
  3. function c() { 
  4.  
  5. function b() { 
  6.     // Here we will store the current stack trace into myObj 
  7.     Error.captureStackTrace(myObj); 
  8.     c(); 
  9.  
  10. function a() { 
  11.     b(); 
  12.  
  13. // First we will call these functions 
  14. a(); 
  15.  
  16. // Now let's see what is the stack trace stored into myObj.stack 
  17. console.log(myObj.stack); 
  18.  
  19. // This will print the following stack to the console: 
  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) 

不知道你注意到?jīng)],我們首先調(diào)用了a(a入棧),然后我們a中又調(diào)用了b(b入棧且在a之上)。然后在b中我們捕獲了當(dāng)前堆棧記錄并將其存儲(chǔ)在myObj中。因此在控制臺(tái)中才會(huì)按照b a的順序打印堆棧。

現(xiàn)在讓我們給Error.captureStackTrace傳遞一個(gè)函數(shù)作為第二個(gè)參數(shù),看看會(huì)發(fā)生什么:

  1. const myObj = {}; 
  2.  
  3. function d() { 
  4.     // Here we will store the current stack trace into myObj 
  5.     // This time we will hide all the frames after `b` and `b` itself 
  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. // First we will call these functions 
  18. a(); 
  19.  
  20. // Now let's see what is the stack trace stored into myObj.stack 
  21. console.log(myObj.stack); 
  22.  
  23. // This will print the following stack to the console: 
  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本身以及它之后所有的調(diào)用幀。因此控制臺(tái)僅僅打印出一個(gè)a。

至此你應(yīng)該會(huì)問自己:“這到底有什么用?”。這非常有用,因?yàn)槟憧梢杂盟鼇黼[藏與用戶無關(guān)的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。在Chai中,我們使用它來避免向用戶顯示我們是如何實(shí)施檢查和斷言本身的不相關(guān)的細(xì)節(jié)。

操作堆棧追蹤實(shí)戰(zhàn)

正如我在上一節(jié)中提到的,Chai使用堆棧操作技術(shù)使堆棧跟蹤更加與我們的用戶相關(guān)。下面將揭曉我們是如何做到的。

首先,讓我們來看看當(dāng)斷言失敗時(shí)拋出的AssertionError的構(gòu)造函數(shù):

  1. // `ssfi` stands for "start stack function". It is the reference to the 
  2. // starting point for removing irrelevant frames from the stack trace 
  3. function AssertionError (message, _props, ssf) { 
  4.   var extend = exclude('name''message''stack''constructor''toJSON'
  5.     , props = extend(_props || {}); 
  6.  
  7.   // Default values 
  8.   this.message = message || 'Unspecified AssertionError'
  9.   this.showDiff = false
  10.  
  11.   // Copy from properties 
  12.   for (var key in props) { 
  13.     this[key] = props[key]; 
  14.   } 
  15.  
  16.   // Here is what is relevant for us: 
  17.   // If a start stack function was provided we capture the current stack trace and pass 
  18.   // it to the `captureStackTrace` function so we can remove frames that come after it 
  19.   ssf = ssf || arguments.callee; 
  20.   if (ssf && Error.captureStackTrace) { 
  21.     Error.captureStackTrace(this, ssf); 
  22.   } else { 
  23.     // If no start stack function was provided we just use the original stack property 
  24.     try { 
  25.       throw new Error(); 
  26.     } catch(e) { 
  27.       this.stack = e.stack; 
  28.     } 
  29.   } 

如你所見,我們使用Error.captureStackTrace捕獲堆棧追蹤并將它存儲(chǔ)在我們正在創(chuàng)建的AssertError實(shí)例中(如果存在的話),然后我們將一個(gè)起始堆棧函數(shù)傳遞給它,以便從堆棧跟蹤中刪除不相關(guān)的調(diào)用幀,它只顯示Chai的內(nèi)部實(shí)現(xiàn)細(xì)節(jié),最終使堆棧變得清晰明了。

現(xiàn)在讓我們來看看@meeber在這個(gè)令人驚嘆的PR中提交的代碼。

在你開始看下面的代碼之前,我必須告訴你addChainableMethod方法是干啥的。它將傳遞給它的鏈?zhǔn)椒椒ㄌ砑拥綌嘌陨希灿冒瑪嘌缘姆椒?biāo)記斷言本身,并將其保存在變量ssfi(啟動(dòng)堆棧函數(shù)指示符)中。這也就意味著當(dāng)前斷言將會(huì)是堆棧中的***一個(gè)調(diào)用幀,因此我們不會(huì)在堆棧中顯示Chai中的任何進(jìn)一步的內(nèi)部方法。我沒有添加整個(gè)代碼,因?yàn)樗隽撕芏嗍虑?,有點(diǎn)棘手,但如果你想讀它,點(diǎn)我閱讀。

下面的這個(gè)代碼片段中,我們有一個(gè)lengOf斷言的邏輯,它檢查一個(gè)對(duì)象是否有一定的length。我們希望用戶可以像這樣來使用它: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.     // Pay close attention to this line 
  7.     new Assertion(obj, msg, ssfi, true).to.have.property('length'); 
  8.     var len = obj.length; 
  9.  
  10.     // This line is also relevant 
  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); 

Assertion.addChainableMethod('lengthOf', assertLength, assertLengthChain);

在上面的代碼片段中,我突出強(qiáng)調(diào)了與我們現(xiàn)在相關(guān)的代碼。讓我們從調(diào)用this.assert開始說起。

以下是this.assert方法的源代碼:

  1. Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) { 
  2.     var ok = util.test(this, arguments); 
  3.     if (false !== showDiff) showDiff = true
  4.     if (undefined === expected && undefined === _actual) showDiff = false
  5.     if (true !== config.showDiff) showDiff = false
  6.  
  7.     if (!ok) { 
  8.         msg = util.getMessage(this, arguments); 
  9.         var actual = util.getActual(this, arguments); 
  10.  
  11.         // This is the relevant line for us 
  12.         throw new AssertionError(msg, { 
  13.                 actual: actual 
  14.             , expected: expected 
  15.             , showDiff: showDiff 
  16.         }, (config.includeStack) ? this.assert : flag(this, 'ssfi')); 
  17.     } 
  18. }; 

assert方法負(fù)責(zé)檢查斷言布爾表達(dá)式是否通過。如果不通過,我們則實(shí)例化一個(gè)AssertionError。不知道你注意到?jīng)],在實(shí)例化AssertionError時(shí),我們也給它傳遞了一個(gè)堆棧追蹤函數(shù)指示器(ssfi),如果配置的includeStack處于開啟狀態(tài),我們通過將this.assert本身傳遞給它來為用戶顯示整個(gè)堆棧跟蹤。反之,我們則只顯示ssfi標(biāo)記中存儲(chǔ)的內(nèi)容,隱藏掉堆棧跟蹤中更多的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。

現(xiàn)在讓我們來討論下一行和我們相關(guān)的代碼吧:

  1. `new Assertion(obj, msg, ssfi, true).to.have.property('length');` 

As you can see here we are passing the content we’ve got from the ssfi flag when creating our nested assertion. This means that when the new assertion gets created it will use this function as the starting point for removing unuseful frames from the stack trace. By the way, this is the Assertion constructor: 如你所見,我們?cè)趧?chuàng)建嵌套斷言時(shí)將從ssfi標(biāo)記中的內(nèi)容傳遞給了它。這意味著新創(chuàng)建的斷言會(huì)使用那個(gè)方法作為起始調(diào)用幀,從而可以從堆棧追蹤中清除沒有的調(diào)用棧。順便也看下Assertion的構(gòu)造器吧:

  1. function Assertion (obj, msg, ssfi, lockSsfi) { 
  2.     // This is the line that matters to us 
  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方法,它使用自己的父級(jí)方法設(shè)置ssfi標(biāo)志,這意味著它始終處于堆棧的底部,我們可以刪除它之上的所有調(diào)用幀。

通過將ssfi傳遞給嵌套斷言,它只檢查我們的對(duì)象是否具有長度屬性,我們就可以避免重置我們將要用作起始指標(biāo)器的調(diào)用幀,然后在堆棧中可以看到以前的addChainableMethod。

這可能看起來有點(diǎn)復(fù)雜,所以讓我們回顧一下我們想從棧中刪除無用的調(diào)用幀時(shí)Chai中所發(fā)生的事情:

  • 當(dāng)我們運(yùn)行斷言時(shí),我們將它自己的方法作為移除堆棧中的下一個(gè)調(diào)用幀的參考
  • 斷言失敗時(shí),我們會(huì)移除所有我們?cè)趨⒖紟蟊4娴膬?nèi)部調(diào)用幀。
  • 如果存在嵌套的斷言。我們必須依舊使用當(dāng)前斷言的父方法作為刪除下一個(gè)調(diào)用幀的參考點(diǎn),因此我們把當(dāng)前的ssfi(起始函數(shù)指示器)傳遞給我們所創(chuàng)建的斷言,以便它可以保存。
責(zé)任編輯:未麗燕 來源: 碼農(nóng)網(wǎng)
相關(guān)推薦

2017-03-08 08:57:04

JavaScript錯(cuò)誤堆棧

2017-04-06 14:40:29

JavaScript錯(cuò)誤處理堆棧追蹤

2021-02-17 11:25:33

前端JavaScriptthis

2017-03-28 21:39:41

ErrorsStack trace代碼

2015-11-04 09:57:18

JavaScript原型

2024-07-18 10:12:04

2019-11-05 10:03:08

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

2013-11-05 13:29:04

JavaScriptreplace

2011-09-06 09:56:24

JavaScript

2019-03-13 08:00:00

JavaScript作用域前端

2011-03-02 12:33:00

JavaScript

2020-12-16 09:47:01

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

2020-07-24 10:00:00

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

2024-05-08 13:52:04

JavaScriptWeb應(yīng)用程序

2012-01-05 15:07:11

JavaScript

2010-06-01 15:25:27

JavaCLASSPATH

2016-12-08 15:36:59

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

2020-07-21 08:26:08

SpringSecurity過濾器

2012-11-08 14:47:52

Hadoop集群

2012-08-31 10:00:12

Hadoop云計(jì)算群集網(wǎng)絡(luò)
點(diǎn)贊
收藏

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