整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          DevTools小技巧-讓你不止會console.log()

          開發(fā)過程中,你可能會經(jīng)常用到控制臺命令console.log(),但是,其實(shí)除了這個(gè)命令外,還有一些其他的命令和技巧可供我們使用,讓我們看看它們究竟是什么,會不會為你的調(diào)試能力帶來一點(diǎn)新的啟發(fā)。

          突破舒適區(qū)

          同樣作為開發(fā)者,有一點(diǎn)不可否認(rèn)的是程序員這個(gè)群體總是會不斷優(yōu)化工作流程,使其變得更高效。不過這很容易使我們陷入誤區(qū),讓我們很難突破已經(jīng)慣有的工作流程,繼而閉耳塞聽得認(rèn)為沒有比現(xiàn)在更好的工作方法和流程。

          一般Web開發(fā)者的工作流程是在IDE中寫好代碼并保存,然后到瀏覽器中刷新測試。同時(shí)使用瀏覽器的DevTools調(diào)整CSS,還可以測試產(chǎn)品在不同分辨率和移動(dòng)設(shè)備上的表現(xiàn)。在需要深入研究的地方可以通過添加 console.log()語句來調(diào)試我們的腳本。

          如果console.log()在最終產(chǎn)品中被濫用,那么你在網(wǎng)上沖浪時(shí)如果一直打開DevTools,你就會在控制臺中看到很多本不該出現(xiàn)在最終產(chǎn)品中的調(diào)試信息。

          下面讓我為大家介紹一下除了console.log()之外的其他命令,看看它們會不會為各位的工作帶來什么新的啟發(fā)吧!

          控制臺的多種用法

          我們可能已經(jīng)習(xí)慣了通過 console.log("參數(shù)") 來了解程序中正在發(fā)生的事情,一般來說對于字符或數(shù)字這種類型的輸出,這種用法就足夠了,不過在輸出些類似像對象、數(shù)組類型的數(shù)據(jù)時(shí)卻沒有那么順手。

          第一個(gè)技巧是在變量上加上大括號,這樣不僅可以打印出它們的值,還可以打印出變量的名稱,這使我們在日志中更方便的定位到什么值來自哪里。

          let x = 2;
          console.log(x) // 2
          console.log({x}) // {x: 2}

          格式化日志

          你可以在console.log中使用以百分號操作符指代不同格式的記錄值來格式化字符串,以下是操作符類型的定義:

          • %s: 字符串
          • %i或%d:整數(shù)。
          • %f:浮點(diǎn)數(shù)。
          • %c:CSS樣式。
          • %o:可擴(kuò)展DOM元素。
          • %O:可擴(kuò)展JavaScript對象。

          你可以把它們分別放在控制臺試試效果,首先是字符串和整數(shù)的示例:

          console.log('%ix %s developer', 10, 'console');
          // 10x console developer

          如果計(jì)劃將數(shù)字格式化為整型,可以使用如下示例:

          console.log('%i', 12.34455241234324234);
          // 12

          %c操作符可以令你使用CSS樣式定制輸出日志的樣式

          console.log('%cPay attention to me','color:firebrick;font-size:40px')

          分組日志

          你可以使用console.group()來對日志進(jìn)行分組,以將其顯示為可擴(kuò)展和可折疊的組。

          const label = 'The Millenium Falcon Crew';
          console.group(label);
          console.log('Leia');
          console.log('Han');
          console.log('Chewie');
          console.log('Ben');
          console.groupEnd(label);

          你可以嵌套分組,并可以使用 console.groupCollapsed() 在默認(rèn)情況下不展開它們

          const extendedlabel = 'The Millenium Falcon Crew extended';
          const meat = 'Humanoids';
          const metal = 'Droids';
          console.group(extendedlabel);
          console.groupCollapsed(meat);
          console.log('Leia');
          console.log('Han');
          console.log('Chewie');
          console.log('Ben');
          console.groupEnd(meat);
          console.group(metal);
          console.log('R2D2');
          console.log('C3PO');
          console.groupEnd(metal);
          console.groupEnd(extendedlabel);

          日志控制臺過濾

          除了console.log外,你也可以使用 console.info()、console.error()和 console.warning()來代替它。通過這些語句,你可以在控制臺側(cè)邊欄或下拉列表中來過濾你在控制臺中看到的消息。這樣一來,你可以更容易地在來自第三方腳本和項(xiàng)目中的其他腳本中找到自己的日志消息。

          其他控制臺命令

          你可能在debug時(shí)曾創(chuàng)建過統(tǒng)計(jì)某個(gè)方法被調(diào)用或被執(zhí)行次數(shù)的變量。這里推薦另一種方法, console.count()和 console.countReset(),通過它們你可以創(chuàng)建任意數(shù)量的變量,并通過標(biāo)簽來區(qū)分。

          console.count('Chocula'); // Chocula: 1
          console.count(); // default: 1
          console.count('Chocula'); // Chocula: 2
          console.countReset('Chocula');
          console.count(); // default: 2
          console.count(); // default: 3
          console.count('Chocula'); // Chocula: 1

          您還可以使用console.time()方法去統(tǒng)計(jì)代碼執(zhí)行的總耗時(shí):

          console.time('go');
          for(let i = 0; i < 200000; i+=1) {
          let x = Math.random()*2000;
          }
          console.timeEnd('go'); // go: 11.7861328125 ms

          使用 console.dir()不僅可以顯示內(nèi)容,還可以顯示你發(fā)送的數(shù)據(jù)類型。例如,如果你想要一個(gè)節(jié)點(diǎn)的XML表示,你可以使用console.dirxml()。而console.table()對于顯示JSON數(shù)據(jù)作為一個(gè)可排序的表格顯示效果也很好。

          使用實(shí)時(shí)表達(dá)式Live Expression替代console.log

          使用 console.log() 來監(jiān)測那些變化范圍很大的數(shù)值時(shí),不僅低效且困難。你可在在開發(fā)人員工具中通過點(diǎn)擊“眼睛“圖標(biāo)來激活Live Expression功能。它可以將你想要關(guān)注的數(shù)值pin在工具頂端。

          例如,你可以先輸入document.activeElement 來試試。該表達(dá)式表示當(dāng)前獲得焦點(diǎn)的元素。

          在這有一點(diǎn)需要說明,因?yàn)長ive Expression并不和某一個(gè)站點(diǎn)及域名所關(guān)聯(lián),所以它會一直保留在你的DevTools中。因此建議在完成一項(xiàng)調(diào)試后及時(shí)刪除它們,以免為調(diào)試其他站點(diǎn)時(shí)帶來不必要的麻煩。

          使用控制臺處理當(dāng)前文檔

          開發(fā)人員工具中的控制臺不僅僅是用于顯示日志的一種方式。它是一個(gè)REPL,可讓您編寫和執(zhí)行JavaScript并使用自動(dòng)完成功能了解當(dāng)前文檔的可用方法和屬性。

          你可以試試,在開發(fā)人員工具的控制臺,輸入doc并按下tab,它會自動(dòng)將其轉(zhuǎn)為document。如果輸入’.’ 你會看到所有document可用的方法和屬性。這是一種學(xué)習(xí)可用方法和屬性的有趣且簡單的方法之一,這樣可以使你在短時(shí)間內(nèi)寫出大量代碼。

          除此之外,控制臺和當(dāng)前文檔進(jìn)行交互還有很多快捷方式可供你使用 “控制臺實(shí)用程序”。其中一些是:

          • $_存儲最后一條控制臺命令的結(jié)果。所以如果你之前輸入了2+2并按回車鍵,你在輸入$_將為你直接提供4。
          • >$0 到 $4 是你通過Elements 選項(xiàng)卡選中元素的堆棧,$0 為當(dāng)前你選擇的元素。< 到 是你通過Elements 選項(xiàng)卡選中元素的堆棧,>$0 到 $4 是你通過Elements 選項(xiàng)卡選中元素的堆棧,$0 為當(dāng)前你選擇的元素。< 為當(dāng)前你選擇的元素。
          • $() 通過選擇器選擇頁面中的元素
          • $$()返回與給定選擇器匹配的元素?cái)?shù)組。此命令等同于document.querySelectorAll()。。
          • $x() 允許您通過XPATH選擇DOM元素。
          • copy() 將您提供的所有內(nèi)容復(fù)制到剪貼板。
          • clear() 清除控制臺。
          • getEventListeners(node) 返回在指定對象上注冊的事件監(jiān)聽器。
          • monitorEvents(node, events) 監(jiān)視并記錄對象上發(fā)生的事件。
          • monitor(method) 每當(dāng)調(diào)用方法時(shí),都會創(chuàng)建一個(gè)日志。

          其中有些方法的功能很強(qiáng)大,但可能我們在并不清楚的前提下自己實(shí)現(xiàn)了一系列 console.log() 語句。

          例如如下使用場景:

          monitorEvents(window, ['resize', 'scroll']);
          monitorEvents($0, 'key');

          每次窗口滾動(dòng)或調(diào)整大小時(shí)都會記錄一條日志。第二個(gè)示例比較有意思,因?yàn)樗涗浟水?dāng)前選定元素上的任何按鍵行為。

          以下代碼列出頁面中的所有a標(biāo)簽(因?yàn)?$('a')是document.querySelectorAll('a')的簡稱),并以可排序的表格形式顯示。作為table方法的第二個(gè)參數(shù)的數(shù)組定義了表格的列。否則,鏈接的每個(gè)屬性都會變成一列,那就很難瀏覽了。這個(gè)表不僅是可排序的,而且你還可以復(fù)制和粘貼它--例如,復(fù)制到Excel中。

          console.table($$('a'),['href','text'])

          與其使用復(fù)雜的JavaScript來過濾這些結(jié)果,你不如試試CSS選擇器。再比如,你想獲得一個(gè)文檔中所有非內(nèi)嵌圖片的src和alt信息的表格,你可以使用以下方法:

          console.table($$('img:not([src^=data])'), ['src','alt'])

          另外,當(dāng)您使用Markdown生成HTML時(shí),大多數(shù)頁面生成器都會在標(biāo)題上創(chuàng)建自動(dòng)ID,例如 # New Stuff標(biāo)題會變成<h1 id="new-stuff">New stuff</h1>。如果我需要批量創(chuàng)建許多指向這些錨點(diǎn)的URL,但不想手動(dòng)去做這些事時(shí),可能需要通過控制臺編寫腳本來為我做這件事:

          let out = '';
          $$('#main [id]').filter(
          elm => {return elm.nodeName.startsWith('H')}
          ).forEach(elm => {
          out += `${elm.innerText}
          ${document.location.href}#${elm.id}
          `
          });
          copy(out);

          結(jié)果是一個(gè)文本塊,每個(gè)標(biāo)題的文本內(nèi)容后跟指向該標(biāo)題的完整URL。

          這里展示了$$快捷方式的一個(gè)有趣的額外功能。document.querySelectorAll('#main [id]').filter() 會導(dǎo)致一個(gè)錯(cuò)誤,因?yàn)榉祷氐闹挡皇且粋€(gè)數(shù)組而是一個(gè)NodeList。你需要用[...document.querySelectoAll('#main [id]').filter()]或Array.from(document.querySelectoAll('#main [id]').filter())方法把它強(qiáng)制轉(zhuǎn)換成一個(gè)Array,這在相當(dāng)長的一段時(shí)間中困擾著從jQuery轉(zhuǎn)到JavaScript的開發(fā)者,而使用$$,可以直接使用所有的Array方法。

          一般來說,你可以通過控制臺來改變?yōu)g覽器頁面中的所有元素。而且你還有一個(gè)額外的好處,就是可以使用DevTools的元素選項(xiàng)卡來獲得元素所有的頁面路徑。點(diǎn)擊每個(gè)元素旁的...菜單,并通過彈出的上下文菜單中選擇你要復(fù)制的路徑。

          雖然控制臺本身很好用,但很快你就會發(fā)現(xiàn)Console在編寫代碼存在著諸多困難,例如,Console是單行環(huán)境,不小心點(diǎn)擊Enter后就會立即執(zhí)行。不過在這最后為大家介紹一個(gè)小技巧,你可以使用Shift + Enter來代替編寫多行腳本。

          Sources

          總的來說,Console是一個(gè)很好的測試環(huán)境,但對于編輯體驗(yàn)來說卻很差。不過還好在Sources面板中也有一個(gè)完整的編輯器。在那里,你可以檢查當(dāng)前頁面的代碼,并編寫更復(fù)雜的腳本與之交互。

          除了點(diǎn)按上面tab菜單之外,DevTools還有一套快捷鍵Command Menu供你使用,你可以通過按control + shift + P(Windows, Linux)或Command+Shift+P(macOS)來訪問它。或選擇...?)菜單,選擇“Run command”。

          Snippets 代碼片段

          Snippets是保存你曾寫過的能明顯提高開發(fā)效率的代碼小片段。在DevTools中點(diǎn)擊Command Menu鍵盤快捷鍵,輸入snip并按下Enter鍵,選擇創(chuàng)建一個(gè)新的snippet,這樣就會進(jìn)入Snippets編輯器,具體如下圖所示:

          右邊的窗體包括一個(gè)完整的源碼編輯器,具有關(guān)鍵詞著色、自動(dòng)補(bǔ)全、多光標(biāo)等功能。下面我們開始試試上面的示例:

          console.clear();
          let out = '';
          let problems = [];
          $$('a').forEach(a => {
          let text = a.innerText.trim();
          let prefix = '';
          if (!text) {
          if (a.querySelector('img')){
          text = a.querySelector('img').alt;
          prefix = 'Image: ';
          }
          if (a.getAttribute('aria-label')) {
          text = a.getAttribute('aria-label');
          prefix = 'Aria Label: ';
          }
          if (a.getAttribute('aria-labelledby')) {
          text = $('#' + a.getAttribute('aria-labelledby')).innerText;
          prefix = 'Aria Labelled By: ';
          }
          }
          if (text) {
          text = prefix + text
          } else {
          a.style.border = '1px solid firebrick';
          problems.push(a);
          }
          out += `
          ${text||'No Link text'}
          ${a.href}`;
          });
          if (out === '') {
          console.warn('Sorry, no links found');
          } else {
          copy(out);
          console.info('done harvesting links, ready to paste');
          if (problems.length > 0) {
          console.warn('There were %d issues:', problems.length);
          console.groupCollapsed('Links without text');
          problems.forEach(a => {console.dirxml(a)});
          console.groupEnd('Links without text');
          }
          }

          以下是執(zhí)行演示:

          Overrides

          Override是通過修改遠(yuǎn)程文件的本地副本,實(shí)現(xiàn)本地測試替換服務(wù)器文件。例如,你可以在本地編輯完整的復(fù)雜樣式表,但無需等待冗長的重新build和部署過程即可看到效果,這也是能在開發(fā)階段即可發(fā)現(xiàn)問題的一種快捷的方式。

          將開發(fā)人員工具和VS Code集成

          你可以通過安裝Microsoft Edge Tools for VS Code擴(kuò)展 ,即可在編輯中獲得開發(fā)人員工具,通過下圖可以看到基礎(chǔ)用法。

          最后總結(jié)

          希望通過這篇文章,可以使你能更全面的對DevTools有一些了解,而不僅僅只會使用console.log。另外作為一個(gè)開發(fā)人員除了上述的這些技巧外,還應(yīng)多多習(xí)慣使用斷點(diǎn)及條件斷點(diǎn)進(jìn)行調(diào)試,希望能夠通過更多的使用和練習(xí)來達(dá)到熟練的狀態(tài)。

          文同步本人掘金平臺原創(chuàng)翻譯的文章:https://juejin.cn/post/6844903834246971400

          你是否遇到過"callbacks"一詞,但是不知道這意味著什么?別著急。你不是一個(gè)人。許多JavaScript的新手發(fā)現(xiàn)回調(diào)也很難理解。

          盡管callbacks可能令人疑惑,但是你仍然需要徹底了解它們,因?yàn)樗鼈兪荍avaScript中的一個(gè)重要的概念。如果你不知道callbacks,你不可能走得很遠(yuǎn)。

          這就是今天的文章(要講的)!你將了解callbacks是什么,為什么它們很重要,以及如何使用它們。

          備注:你會在這篇文章中看到ES6箭頭函數(shù)。如果你不是很熟悉它們,我建議你在往下讀之前復(fù)習(xí)一下ES6這篇文章(只了解箭頭函數(shù)部分就可以了)。

          callbacks是什么?

          callback是作為稍后要執(zhí)行的參數(shù)傳遞給另一個(gè)函數(shù)的函數(shù)。(開發(fā)人員說你在執(zhí)行函數(shù)時(shí)“調(diào)用”一個(gè)函數(shù),這就是被命名為回調(diào)函數(shù)的原因)。

          它們在JavaScript中很常見,你可能自己潛意識的使用了它們而不知道它們被稱為回調(diào)函數(shù)。

          接受函數(shù)回調(diào)的一個(gè)示例是addEventLisnter:

          const button = document.querySelector('button')
          button.addEventListener('click', function(e) {
            // Adds clicked class to button
            this.classList.add('clicked')
          })
          復(fù)制代碼

          看不出是回調(diào)函數(shù)嗎?那么,這種寫法怎樣?

          const button = document.querySelector('button')
          
          // Function that adds 'clicked' class to the element
          function clicked (e) {
            this.classList.add('clicked')
          }
          
          // Adds click function as a callback to the event listener
          button.addEventListener('click', clicked)
          復(fù)制代碼

          在這里,我們告訴JavaScript監(jiān)聽按鈕上的click事件。如果檢測到點(diǎn)擊,則JavaScript應(yīng)觸發(fā)clicked函數(shù)。因此,在這種情況下,clicked是回調(diào)函數(shù),而addEventListener是一個(gè)接受回調(diào)的函數(shù)。

          現(xiàn)在,你明白什么是回調(diào)函數(shù)了嘛?:)

          我們來看另外一個(gè)例子。這一次,假設(shè)你希望通過過濾一組數(shù)據(jù)來獲取小于5的列表。在這里,你將回調(diào)函數(shù)傳遞給filter函數(shù):

          const numbers = [3, 4, 10, 20]
          const lesserThanFive = numbers.filter(num => num < 5)
          復(fù)制代碼

          現(xiàn)在,如果你想通過命名函數(shù)執(zhí)行上面的代碼,則過濾函數(shù)將如下所示:

          const numbers = [3, 4, 10, 20]
          const getLessThanFive = num => num < 5
          
          // Passing getLessThanFive function into filter
          const lesserThanFive = numbers.filter(getLessThanFive)
          復(fù)制代碼

          在這種情況下,getLessThanFive是回調(diào)函數(shù)。Array.filter是一個(gè)接受回調(diào)的函數(shù)。

          現(xiàn)在明白為什么了吧?一旦你知道回調(diào)函數(shù)是什么,它們就無處不在!

          下面的示例向你展示如何編寫回調(diào)函數(shù)和接受回調(diào)的函數(shù):

          // Create a function that accepts another function as an argument
          const callbackAcceptingFunction = (fn) => {
            // Calls the function with any required arguments
            return fn(1, 2, 3)
          }
          
          // Callback gets arguments from the above call
          const callback = (arg1, arg2, arg3) => {
            return arg1 + arg2 + arg3
          }
          
          // Passing a callback into a callback accepting function
          const result = callbackAcceptingFunction(callback)
          console.log(result) // 6
          復(fù)制代碼

          請注意,當(dāng)你將回調(diào)函數(shù)傳遞給另一個(gè)函數(shù)時(shí),你只傳遞該函數(shù)的引用(并沒有執(zhí)行它,因此沒有括號())

          const result = callbackAcceptingFunction(callback)
          復(fù)制代碼

          你只能在callbackAcceptingFunction中喚醒(調(diào)用)回調(diào)函數(shù)。執(zhí)行此操作時(shí),你可以傳遞回調(diào)函數(shù)可能需要的任意數(shù)量的參數(shù):

          const callbackAcceptingFunction = (fn) => {
            // Calls the callback with three args
            fn(1, 2, 3)
          }
          復(fù)制代碼

          這些由callbackAcceptingFunction傳遞給回調(diào)函數(shù)的參數(shù),然后再通過回調(diào)函數(shù)(執(zhí)行):

          // Callback gets arguments from callbackAcceptingFunction
          const callback = (arg1, arg2, arg3) => {
            return arg1 + arg2 + arg3
          }
          復(fù)制代碼

          這是回調(diào)的解剖。現(xiàn)在,你應(yīng)該知道addEventListener包含一個(gè)event參數(shù):)

          // Now you know where this event object comes from! :)
          button.addEventListener('click', (event) => {
            event.preventDefault()
          })
          復(fù)制代碼

          唷!這是callbacks的基本思路!只需要記住其關(guān)鍵:將一個(gè)函數(shù)傳遞給另一個(gè)函數(shù),然后,你會想起我上面提到的機(jī)制。

          旁注:這種傳遞函數(shù)的能力是一件很重要的事情。它是如此重要,以至于說JavaScript中的函數(shù)是高階函數(shù)。高階函數(shù)在編程范例中稱為函數(shù)編程,是一件很重大的事情。

          但這是另一天的話題。現(xiàn)在,我確信你已經(jīng)開始明白callbacks是什么,以及它們是如何被使用的。但是為什么?你為什么需要callbacks呢?

          為什么使用callbacks

          回調(diào)函數(shù)以兩種不同的方式使用 -- 在同步函數(shù)和異步函數(shù)中。

          同步函數(shù)中的回調(diào)

          如果你的代碼從上到下,從左到右的方式順序執(zhí)行,等待上一個(gè)代碼執(zhí)行之后,再執(zhí)行下一行代碼,則你的代碼是同步的

          讓我們看一個(gè)示例,以便更容易理解:

          const addOne = (n) => n + 1
          addOne(1) // 2
          addOne(2) // 3
          addOne(3) // 4
          addOne(4) // 5
          復(fù)制代碼

          在上面的例子中,addOne(1)首先執(zhí)行。一旦它執(zhí)行完,addOne(2)開始執(zhí)行。一旦addOne(2)執(zhí)行完,addOne(3)執(zhí)行。這個(gè)過程一直持續(xù)到最后一行代碼執(zhí)行完畢。

          當(dāng)你希望將部分代碼與其它代碼輕松交換時(shí),回調(diào)將用于同步函數(shù)。

          所以,回到上面的Array.filter示例中,盡管我們將數(shù)組過濾為包含小于5的數(shù)組,但你可以輕松地重用Array.filter來獲取大于10的數(shù)字?jǐn)?shù)組:

          const numbers = [3, 4, 10, 20]
          const getLessThanFive = num => num < 5
          const getMoreThanTen = num => num > 10
          
          // Passing getLessThanFive function into filter
          const lesserThanFive = numbers.filter(getLessThanFive)
          
          // Passing getMoreThanTen function into filter
          const moreThanTen = numbers.filter(getMoreThanTen)
          復(fù)制代碼

          這就是為什么你在同步函數(shù)中使用回調(diào)函數(shù)的原因。現(xiàn)在,讓我們繼續(xù)看看為什么我們在異步函數(shù)中使用回調(diào)。

          異步函數(shù)中的回調(diào)

          這里的異步意味著,如果JavaScript需要等待某些事情完成,它將在等待時(shí)執(zhí)行給予它的其余任務(wù)。

          異步函數(shù)的一個(gè)示例是setTimeout。它接受一個(gè)回調(diào)函數(shù)以便稍后執(zhí)行:

          // Calls the callback after 1 second
          setTimeout(callback, 1000)
          復(fù)制代碼

          如果你給JavaScript另外一個(gè)任務(wù)需要完成,讓我們看看setTimeout是如何工作的:

          const tenSecondsLater = _ = > console.log('10 seconds passed!')
          
          setTimeout(tenSecondsLater, 10000)
          console.log('Start!')
          復(fù)制代碼

          在上面的代碼中,JavaScript會執(zhí)行setTimeout。然后,它會等待10秒,之后打印出"10 seconds passed!"的消息。

          同時(shí),在等待setTimeout10秒內(nèi)完成時(shí),JavaScript執(zhí)行console.log("Start!")。

          所以,如果你(在控制臺上)打印上面的代碼,這就是你會看到的:

          // What happens:
          // > Start! (almost immediately)
          // > 10 seconds passed! (after ten seconds)
          復(fù)制代碼

          啊~異步操作聽起來很復(fù)雜,不是嗎?但為什么我們在JavaScript中頻繁使用它呢?

          要了解為什么異步操作很重要呢?想象一下JavaScript是你家中的機(jī)器人助手。這個(gè)助手非常愚蠢。它一次只能做一件事。(此行為被稱為單線程)。

          假設(shè)你告訴你的機(jī)器人助手為你訂購一些披薩。但機(jī)器人是如此的愚蠢,在打電話給披薩店之后,機(jī)器人坐在你家門前,等待披薩送達(dá)。在此期間它無法做任何其它事情。

          你不能叫它去熨衣服,拖地或在等待(披薩到來)的時(shí)候做任何事情。(可能)你需要等20分鐘,直到披薩到來,它才愿意做其他事情...

          此行為稱為阻塞。當(dāng)你等待某些內(nèi)容完成時(shí),其他操作將被阻止。

          const orderPizza = flavour => {
            callPizzaShop(`I want a ${flavour} pizza`)
            waits20minsForPizzaToCome() // Nothing else can happen here
            bringPizzaToYou()
          }
          
          orderPizza('Hawaiian')
          
          // These two only starts after orderPizza is completed
          mopFloor()
          ironClothes()
          復(fù)制代碼

          而阻止操作是一個(gè)無賴。

          為什么?

          讓我們把愚蠢的機(jī)器人助手放到瀏覽器的上下文中。想象一下,當(dāng)單擊按鈕時(shí),你告訴它更改按鈕的顏色。

          這個(gè)愚蠢的機(jī)器人會做什么?

          它專注于按鈕,忽略所有命令,直到按鈕被點(diǎn)擊。同時(shí),用戶無法選擇任何其他內(nèi)容。看看它都在干嘛了?這就是異步編程在JavaScript中如此重要的原因。

          但是,要真正了解異步操作期間發(fā)生的事情,我們需要引入另外一個(gè)東西 -- 事件循環(huán)。

          事件循環(huán)

          為了設(shè)想事件循環(huán),想象一下JavaScript是一個(gè)攜帶todo-list的管家。此列表包含你告訴它要做的所有事情。然后,JavaScript將按照你提供的順序逐個(gè)遍歷列表。

          假設(shè)你給JavaScript下面五個(gè)命令:

          const addOne = (n) => n + 1
          
          addOne(1) // 2
          addOne(2) // 3
          addOne(3) // 4
          addOne(4) // 5
          addOne(5) // 6
          復(fù)制代碼

          這是JavaScript的待辦事項(xiàng)列表中出現(xiàn)的內(nèi)容。

          相關(guān)命令在JavaScript待辦事項(xiàng)列表中同步出現(xiàn)。

          除了todo-list之外,JavaScript還保留一個(gè)waiting-list來跟蹤它需要等待的事情。如果你告訴JavaScript訂購披薩,它會打電話給披薩店并在等候列表名單中添加“等待披薩到達(dá)”(的指令)。與此同時(shí),它還會做了其他已經(jīng)在todo-list上的事情。

          所以,想象下你有下面代碼:

          const orderPizza (flavor, callback) {
            callPizzaShop(`I want a ${flavor} pizza`)
          
            // Note: these three lines is pseudo code, not actual JavaScript
            whenPizzaComesBack {
              callback()
            }
          }
          
          const layTheTable = _ => console.log('laying the table')
          
          orderPizza('Hawaiian', layTheTable)
          mopFloor()
          ironClothes()
          復(fù)制代碼

          JavaScript的初始化todo-list如下:

          訂披薩,拖地和熨衣服!

          然后,在執(zhí)行orderPizza時(shí),JavaScript知道它需要等待披薩送達(dá)。因此,它會在執(zhí)行其余任務(wù)時(shí),將“等待披薩送達(dá)”(的指令)添加到waiting list上。

          JavaScript等待披薩到達(dá)

          當(dāng)披薩到達(dá)時(shí),門鈴會通知JavaScript,當(dāng)它完成其余雜務(wù)時(shí)。它會做個(gè)**心理記錄(mental note)**去執(zhí)行l(wèi)ayTheTable。

          JavaScript知道它需要通過在其 mental note 中添加命令來執(zhí)行l(wèi)ayTheTable

          然后,一旦完成其他雜務(wù),JavaScript就會執(zhí)行回調(diào)函數(shù)layTheTable。

          其他所有內(nèi)容完成后,JavaScript就會去布置桌面(layTheTable)

          我的朋友,這個(gè)就被稱為事件循環(huán)。你可以使用事件循環(huán)中的實(shí)際關(guān)鍵字替換我們的管家,類比來理解所有的內(nèi)容:

          • Todo-list -> Call stack
          • Waiting-list -> Web apis
          • Mental note -> Event queue

          JavaScript的事件循環(huán)

          如果你有20分鐘的空余時(shí)間,我強(qiáng)烈建議你觀看Philip Roberts 在JSconf中談?wù)摰氖录h(huán)。它將幫助你理解事件循環(huán)的細(xì)節(jié)。

          厄...那么,為什么callbacks那么重要呢?

          哦~我們在事件循環(huán)繞了一大圈。我們回正題吧。

          之前,我們提到如果JavaScript專注于按鈕并忽略所有其他命令,那將是不好的。是吧?

          通過異步回調(diào),我們可以提前提供JavaScript指令而無需停止整個(gè)操作

          現(xiàn)在,當(dāng)你要求JavaScript查看點(diǎn)擊按鈕時(shí),它會將“監(jiān)聽按鈕”(指令)放入waiting list中并繼續(xù)進(jìn)行雜務(wù)。當(dāng)按鈕最終獲得點(diǎn)擊時(shí),JavaScript會激活回調(diào),然后繼續(xù)執(zhí)行。

          以下是回調(diào)中的一些常見用法,用于告訴JavaScript要做什么...

          1. 當(dāng)事件觸發(fā)時(shí)(比如addEventListener)
          2. 在AJAX調(diào)用后(比如jQuery.ajax)
          3. 在讀/寫文件之后(比如fs.readFile)
          // Callbacks in event listeners
          document.addEventListener(button, highlightTheButton)
          document.removeEventListener(button, highlightTheButton)
          
          // Callbacks in jQuery's ajax method
          $.ajax('some-url', {
            success (data) { /* success callback */ },
            error (err) { /* error callback */}
          });
          
          // Callbacks in Node
          fs.readFile('pathToDirectory', (err, data) => {
            if (err) throw err
            console.log(data)
          })
          
          // Callbacks in ExpressJS
          app.get('/', (req, res) => res.sendFile(index.html))
          復(fù)制代碼

          這就是它(異步)的回調(diào)!

          希望你清楚callbacks是什么以及現(xiàn)在如何使用它們。在開始的時(shí)候,你不會創(chuàng)建很多回調(diào),所以要專注于學(xué)習(xí)如何使用可用的回調(diào)函數(shù)。

          現(xiàn)在,在我們結(jié)束(本文)之前,讓我們看一下開發(fā)人員(使用)回調(diào)的第一個(gè)問題 -- 回調(diào)地獄。

          回調(diào)地獄

          回調(diào)地獄是一種多次回調(diào)相互嵌套的現(xiàn)象。當(dāng)你執(zhí)行依賴于先前異步活動(dòng)的異步活動(dòng)時(shí),可能會發(fā)生這種情況。這些嵌套的回調(diào)使代碼更難閱讀。

          根據(jù)我的經(jīng)驗(yàn),你只會在Node中看到回調(diào)地獄。在使用前端JavaScript時(shí),你幾乎從不會遇到回調(diào)地獄。

          下面是一個(gè)回調(diào)地獄的例子:

          // Look at three layers of callback in this code!
          app.get('/', function (req, res) {
            Users.findOne({ _id:req.body.id }, function (err, user) {
              if (user) {
                user.update({/* params to update */}, function (err, document) {
                  res.json({user: document})
                })
              } else {
                user.create(req.body, function(err, document) {
                  res.json({user: document})
                })
              }
            })
          })
          復(fù)制代碼

          而現(xiàn)在,你有個(gè)挑戰(zhàn) -- 嘗試一目了然地破譯上面的代碼。很難,不是嗎?難怪開發(fā)者在看到嵌套回調(diào)時(shí)會不寒而栗。

          克服回調(diào)地獄的一個(gè)解決方案是將回調(diào)函數(shù)分解為更小的部分以減少嵌套代碼的數(shù)量:

          const updateUser = (req, res) => {
            user.update({/* params to update */}, function () {
              if (err) throw err;
              return res.json(user)
            })
          }
          
          const createUser = (req, res, err, user) => {
            user.create(req.body, function(err, user) {
              res.json(user)
            })
          }
          
          app.get('/', function (req, res) {
            Users.findOne({ _id:req.body.id }, (err, user) => {
              if (err) throw err
              if (user) {
                updateUser(req, res)
              } else {
                createUser(req, res)
              }
            })
          })
          復(fù)制代碼

          更容易閱讀了,是吧?

          還有其他解決方案來對抗新版JavaScript中的回調(diào)地獄 -- 比如promises和async / await。但是,解釋它們是我們另一天的話題。

          結(jié)語

          今天,你了解到了回調(diào)是什么,為什么它們在JavaScript中如此重要以及如何使用它們。你還學(xué)會了回調(diào)地獄和對抗它的方法。現(xiàn)在,希望callbakcs不再嚇到你了。

          你對回調(diào)還有任何疑問嗎?如果你有,請隨時(shí)在下面發(fā)表評論,我會盡快回復(fù)你的。【PS:本文譯文,若需作者解答疑問,請移步原作者文章下評論】

          感謝閱讀。這篇文章是否幫助到你?如果有,我希望你考慮分享它。你可能會幫助到其他人。非常感謝!

          .08號模擬前端面試8問

          事件委托

          事件委托是前端開發(fā)中常用的一種優(yōu)化性能和代碼可維護(hù)性的方法,它基于DOM的事件冒泡機(jī)制。當(dāng)一個(gè)元素觸發(fā)事件時(shí),這個(gè)事件會按照從頂層到底層的順序傳播,直到最底層的元素(通常是文檔的根節(jié)點(diǎn))。事件委托利用了這個(gè)特性,通過在父元素上設(shè)置事件處理程序來監(jiān)聽子元素的事件,從而減少不必要的事件處理程序的數(shù)量。


          1. 事件委托的定義與作用:

          事件委托允許我們將事件監(jiān)聽器添加到其父元素上,這樣只有當(dāng)這些子元素觸發(fā)事件時(shí),才會執(zhí)行相應(yīng)的處理程序。這樣做的好處是減少了不必要的事件監(jiān)聽器的創(chuàng)建,因?yàn)椴恍枰獮槊總€(gè)可能觸發(fā)事件的子元素都添加一個(gè)監(jiān)聽器。此外,它還可以減少內(nèi)存占用和提高頁面渲染性能,因?yàn)闇p少了綁定到子元素上的事件處理函數(shù)的數(shù)量。

          2. 使用事件委托的編程場景以及它如何提升性能:

          事件委托通常用于以下場景:

          • 當(dāng)需要對一組相似的元素進(jìn)行操作時(shí),比如所有的按鈕點(diǎn)擊事件。
          • 當(dāng)需要避免在每個(gè)獨(dú)立的子元素上添加事件監(jiān)聽器時(shí)。
          • 當(dāng)希望將某些行為應(yīng)用到一組具有相同或相似行為的多個(gè)元素上時(shí)。

          通過事件委托,我們可以避免為每個(gè)獨(dú)立的子元素重復(fù)編寫相同的事件處理邏輯,從而提高了代碼的復(fù)用性和可維護(hù)性。同時(shí),由于減少了綁定到每個(gè)子元素的事件處理函數(shù)的數(shù)量,因此可以減輕瀏覽器的負(fù)擔(dān),提高頁面的性能。

          私信【學(xué)習(xí)】即可獲取前端資料 都整理好啦!!!


          主站蜘蛛池模板: 学生妹亚洲一区二区| 久久精品免费一区二区喷潮| 无码国产精品一区二区免费虚拟VR| 色噜噜狠狠一区二区三区果冻| 欲色影视天天一区二区三区色香欲| 亚洲天堂一区二区| 精品人妻一区二区三区毛片 | 波多野结衣精品一区二区三区| 78成人精品电影在线播放日韩精品电影一区亚洲 | 在线观看国产一区二区三区| 亚洲AV无码一区二区三区DV | 无码少妇一区二区| 狠狠综合久久av一区二区| 中文字幕VA一区二区三区| 国产一区二区三区美女| 国产成人精品一区二区A片带套| 日本免费一区二区三区| 久久青草精品一区二区三区| 激情内射亚州一区二区三区爱妻| 伊人精品视频一区二区三区| 日韩人妻无码一区二区三区久久99| 在线观看精品视频一区二区三区 | 亚洲一区二区三区在线观看网站| 日韩精品无码一区二区三区| 亚洲美女一区二区三区| 国产精品一区二区不卡| 亚洲日韩中文字幕一区| 国产成人精品一区二三区熟女| 怡红院一区二区在线观看| 免费观看日本污污ww网站一区| 日韩精品久久一区二区三区| 国产天堂在线一区二区三区| 国产精品一区二区三区99| 红桃AV一区二区三区在线无码AV | 国产午夜毛片一区二区三区 | 亚洲av成人一区二区三区在线播放| 亚洲AV无码一区二区乱子仑 | 无码aⅴ精品一区二区三区浪潮| 色狠狠一区二区三区香蕉| 麻豆AV天堂一区二区香蕉| 美女福利视频一区二区|