精品欧美一区二区三区在线观看 _久久久久国色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)

中日韩免视频上线全都免费| 免费黄色在线| 麻豆91精品| 中文字幕日韩av电影| 深夜福利网站在线观看| 国产99re66在线视频| 久久久久久免费网| 亚洲一区二区三区久久| 国产精品一区二区三区四| 色偷偷综合网| 国产午夜精品久久久| 天天看片天天操| 美女18一级毛片一品久道久久综合| 中文子幕无线码一区tr| 成人区精品一区二区| 久久久久久久亚洲| 在线播放一区| 久久精品国产成人| 亚洲AV无码国产成人久久| 日韩一区二区三区精品| 欧美性淫爽ww久久久久无| 日韩国产小视频| 91欧美在线视频| 99视频一区二区| 亚洲一区二区三区sesese| 蜜臀99久久精品久久久久小说| 欧美视频在线观看| 精品国偷自产在线| 一级肉体全黄裸片| 亚欧日韩另类中文欧美| 精品国产成人在线影院| 在线一区二区不卡| 中文字幕系列一区| 日韩欧美亚洲成人| 蜜臀av无码一区二区三区| 最新超碰在线| 亚洲日本电影在线| 亚洲一区二区在线免费观看| 欧美69xxxxx| 99re热视频精品| www.久久久| 国产激情无套内精对白视频| 精品在线一区二区三区| 国产精品日日摸夜夜添夜夜av| 日韩不卡视频在线| 国产精品主播| 555www成人网| 黄色一级片免费在线观看| 精品二区久久| 国外成人在线直播| 日本网站在线播放| 亚洲高清电影| 91国语精品自产拍在线观看性色 | 美女福利视频在线| 免费h在线看| 欧美日韩国产页| 黄色国产一级视频| 爱啪视频在线观看视频免费| 亚洲电影第三页| 欧美日韩精品在线一区二区 | 亚洲午夜久久久久久久久电影网| 99热一区二区三区| 18加网站在线| 无码av免费一区二区三区试看 | 亚洲最大的黄色网址| 97欧美在线视频| 久久精品一本久久99精品| 亚洲熟女毛茸茸| 亚洲精品二区三区| 久久久久久久久久久免费精品| 久久国产在线视频| 国产伦理一区| 国产精品国产自产拍高清av水多 | 久久精品国产色蜜蜜麻豆| 国产欧美精品日韩| 精品人妻一区二区三区麻豆91| 成人一级视频在线观看| 国偷自产av一区二区三区小尤奈| 少妇av一区二区| 久久婷婷色综合| 亚洲一二三区精品| 日本中文字幕中出在线| 欧美日韩在线免费观看| 男女视频在线看| 综合视频一区| 亚洲欧美日韩一区二区在线| 国精产品久拍自产在线网站| 亚洲手机在线| 国产精品久久久久久久久久小说| 国产免费的av| 91免费视频网址| 中文字幕一区二区中文字幕| 青青草视频在线免费直播| 黑人精品xxx一区一二区| wwww.国产| 爱爱精品视频| 在线观看日韩av| 国产在线视频在线观看| 日本欧美韩国一区三区| 国产精品视频免费观看| 成全电影播放在线观看国语| 亚洲综合自拍偷拍| 搡女人真爽免费午夜网站| 中文字幕视频精品一区二区三区| 亚洲网站视频福利| 国产精品成人久久| 国产真实精品久久二三区| 久久久水蜜桃| 欧美精品videosex| 欧美日韩精品免费观看视频| 一起草在线视频| 中文字幕一区二区三区久久网站| 青青在线视频一区二区三区| 成人黄色在线观看视频| 国产精品久久久久久久久久免费看| 成品人视频ww入口| 国产精品777777在线播放| 亚洲免费成人av电影| 久久久久人妻一区精品色欧美| 欧美aaaaa成人免费观看视频| 精品产品国产在线不卡| 黄色在线播放网站| 欧美日韩在线综合| 国产艳俗歌舞表演hd| 黑丝一区二区| 91一区二区三区| 午夜激情视频在线| 欧美在线你懂得| 91精品国产自产| 亚洲毛片视频| 国产日韩欧美综合精品| 亚洲综合伊人久久大杳蕉| 欧美精品久久99久久在免费线| 91中文字幕永久在线| 亚洲一区国产一区| 国产精品日本一区二区| 天堂av在线电影| 欧美一级欧美一级在线播放| av最新在线观看| 久久99在线观看| 亚洲欧美日韩另类精品一区二区三区| 中文字幕资源网在线观看免费| 精品999久久久| 久久久久久久久艹| 成人亚洲一区二区一| 日韩精品久久一区二区| 97久久亚洲| 午夜精品久久久久久久99热| 日韩一区二区三区不卡| 亚洲成a人片在线观看中文| 国产人成视频在线观看| 亚洲国产精品第一区二区| 国产欧美在线一区二区| 青青在线视频| 亚洲精品720p| 亚洲不卡视频在线观看| 国产免费久久精品| www欧美激情| 91精品国产调教在线观看| 成人免费看黄网站| 色呦呦呦在线观看| 亚洲国产福利在线| 在线能看的av| 欧美国产1区2区| 激情图片中文字幕| 欧美午夜电影在线观看 | 国产aⅴ精品一区二区三区久久| 欧洲成人性视频| 免费在线黄色影片| 欧美日韩亚洲综合在线 | 99在线精品视频在线观看| 久久婷婷开心| 99久久伊人| 久热国产精品视频| 日日躁夜夜躁白天躁晚上躁91| 午夜视黄欧洲亚洲| www色com| 国产99久久久精品| 国产第一页视频| 国产精品成久久久久| caoporen国产精品| 亚洲天堂一区二区| 久久在精品线影院精品国产| 天天躁日日躁狠狠躁喷水| 在线区一区二视频| 国产suv一区二区三区| 91蝌蚪porny| 91 视频免费观看| 国产日韩专区| 日本老太婆做爰视频| 亚洲成a人片77777在线播放| 91精品久久久久久| 涩涩涩视频在线观看| 久久黄色av网站| 青青草视频在线免费观看| 7777精品伊人久久久大香线蕉经典版下载| 久久久久久福利| 国产亚洲欧洲997久久综合 | 日本一区二区综合亚洲| 日本wwwxx| 日本一不卡视频| 岛国大片在线播放| 97精品视频在线看| 青青草久久网络| 18国产精品| 国产精选久久久久久| 高端美女服务在线视频播放| 精品国产一区二区在线| 日本天堂影院在线视频| 日韩一区二区精品| 糖心vlog精品一区二区| 五月婷婷另类国产| 欧产日产国产v| 欧美国产精品一区| 美国黄色一级毛片| 成人高清免费观看| 在线观看视频在线观看| 秋霞成人午夜伦在线观看| 日本在线xxx| 欧美日韩一区二区高清| 亚洲一区二区三区在线观看视频| 色88888久久久久久影院| 99中文视频在线| 国产精品毛片aⅴ一区二区三区| 国产精品久久久久一区二区| 妞干网免费在线视频| 久久久久久12| 在线免费观看的av| 久久中文字幕在线视频| 色综合久久影院| 一本一本久久a久久精品牛牛影视 一本色道久久综合亚洲精品小说 一本色道久久综合狠狠躁篇怎么玩 | 丝袜美腿美女被狂躁在线观看| 亚洲欧美www| 视频三区在线观看| 亚洲第一区第二区| 亚洲高清视频在线播放| 欧美一区二区日韩| 国产绳艺sm调教室论坛| 制服.丝袜.亚洲.另类.中文| 中文字幕一区二区三区四区视频 | 综合亚洲色图| 九色视频成人porny| 激情小说亚洲图片| 精品国产综合久久| 欧美电影在线观看完整版| 国产日韩在线一区二区三区| 成人动态视频| 国产精品毛片va一区二区三区| 成人在线视频中文字幕| 国产精品综合久久久久久| 国产精品chinese在线观看| 国产三级精品在线不卡| 老司机精品视频在线播放| 精品蜜桃一区二区三区| 杨幂一区二区三区免费看视频| 麻豆av一区| 国产欧美一区| 欧美日韩在线免费观看视频| 伊人久久大香线蕉综合四虎小说 | 日韩一区二区免费视频| 精品美女www爽爽爽视频| 精品久久99ma| 视频二区在线| 综合网日日天干夜夜久久| 在线播放毛片| 久精品免费视频| 麻豆mv在线看| 国产精品久久久精品| 婷婷成人av| 成人在线视频网址| 亚洲第一二三区| 亚洲福利av| 欧美福利专区| 免费无码国产v片在线观看| 久久精品五月| 亚洲国产高清av| 丰满少妇久久久久久久| 男女黄床上色视频| 国产精品灌醉下药二区| 日本三级片在线观看| 在线视频国产一区| 午夜精品在线播放| 精品在线欧美视频| 国产一二区在线| 97精品久久久| 日本电影久久久| 国产精品自拍首页| 日韩情爱电影在线观看| 久久久久久久9| 久久婷婷激情| 午夜啪啪小视频| xnxx国产精品| 麻豆国产尤物av尤物在线观看| 色综合天天性综合| 国产av无码专区亚洲av麻豆| 日韩精品免费电影| av在线免费播放| 日本在线精品视频| 99这里只有精品视频| 视频一区二区精品| 亚洲国产免费| 精品国产乱码久久久久久1区二区| 91美女在线视频| 欧美成人黄色网| 欧美视频一区二区三区| 婷婷五月综合激情| 久久伊人精品一区二区三区| 播放一区二区| 国产在线一区二| 亚洲精品二区三区| 在线黄色免费观看| www激情久久| 国产一级片免费观看| 欧美日韩国产首页在线观看| 日本高清中文字幕二区在线| 欧美日本中文字幕| 亚洲欧洲日韩精品在线| 日本成人三级电影网站| 亚洲激情自拍| 亚洲色图偷拍视频| 国产精品污污网站在线观看| 天天爽夜夜爽夜夜爽精品| 日韩欧美一级在线播放| 免费看a在线观看| 国产精品日韩精品| 成人综合一区| 男人舔女人下面高潮视频| 91丨九色丨黑人外教| 国产一国产二国产三| 日韩片之四级片| 国产成人l区| 亚洲va码欧洲m码| 午夜精品毛片| 8x8x成人免费视频| 亚洲视频免费观看| 国产理论片在线观看| 日韩一区av在线| 欧美另类激情| 中文字幕一区二区三区有限公司 | 超碰在线无需免费| 国产欧美中文字幕| 成人免费av| 亚洲欧美在线精品| 1000精品久久久久久久久| 亚洲免费视频二区| 在线播放日韩专区| 成人精品国产亚洲| 亚洲精品人成| 久热成人在线视频| 久久国产高清视频| 9191成人精品久久| 麻豆tv免费在线观看| 亚洲xxxxx性| 欧美三区视频| 麻豆精品国产传媒av| 精品久久久久久久久久ntr影视| 日韩在线观看视频一区| 51视频国产精品一区二区| 国产一区2区| 久久久久久蜜桃一区二区| 亚洲日本在线天堂| 男人天堂一区二区| 日韩av不卡电影| 首页国产精品| 久久久久99人妻一区二区三区| 午夜精品福利一区二区蜜股av| 三级毛片在线免费看| 国产精品爽黄69天堂a| 午夜精品毛片| 国产精品久久久久久亚洲色| 欧美性猛交xxxx免费看| av电影在线播放高清免费观看| 91色视频在线观看| 亚洲麻豆一区| 女人裸体性做爰全过| 日韩欧美一级特黄在线播放| 最新中文字幕在线播放| 亚洲一区二区三区免费观看| 国产成人免费视频精品含羞草妖精| 欧美一级视频免费观看| 一个色综合导航| 日本在线视频一区二区三区| 国产白丝袜美女久久久久| 国产精品女主播av| 亚洲精品一区二区口爆| 国产999精品| 欧美日韩天堂| 久久久久久久毛片| 日韩欧美亚洲国产另类| 日韩在线免费| av在线观看地址| 国产欧美一区二区精品忘忧草 | 99精品国产一区二区三区不卡| 国产一卡二卡三卡| 欧美激情视频在线观看| 国产精品一区二区三区av麻| 最好看的中文字幕| 欧美性受xxxx| 交100部在线观看| 肉大捧一出免费观看网站在线播放|