1.關於防抖/節流
題目要求很明確,要求最終輸出“searchText1”,並且在500ms后輸出“searchText7”,很明顯就是關於同一個函數在短時間內重復調用,如何限制其調用頻率的功能實現。
1 function throttle(action, threshold) { 2 // TODO: 3 } 4 5 let timer = true; 6 const triggerScroll = throttle((val) => { 7 console.log(val); 8 }, 300); 9 10 triggerScroll("searchText1"); 11 triggerScroll("searchText2"); 12 triggerScroll("searchText3"); 13 triggerScroll("searchText4"); 14 triggerScroll("searchText5"); 15 triggerScroll("searchText6"); 16 17 setTimeout(() => {}, 500); 18 19 // 期望輸出結果: 20 // searchText1 21 // 500ms 后輸出 searchText7
首先,拿到題目后我們先運行一下,運行后報錯,顯示“triggerScroll is not a function”;由於題目中throttle函數沒有返回值,所以triggerScroll接收到的throttle返回值為undefined,首先我們要在throttle函數中return一個函數,也就是throttle在調用時傳進去的第一個參數。
1 function throttle(action, threshold) { 2 return action 3 }
這時再運行,控制台打印“searchText1-6”;這時我們只需要把防抖的通用模板套進去就可以實現了,我這里用的防抖,其實使用節流函數也是可以實現的,原理相同,我這里就不展示了,完整代碼如下:
1 function throttle(action, threshold) { 2 return action 3 } 4 5 let timer = true; 6 const triggerScroll = throttle((val) => { 7 if (timer) { 8 timer = false; 9 setTimeout(() => { 10 timer = true; 11 console.log(val); 12 }, 300) 13 } else { 14 return false; 15 } 16 }, 300); 17 18 triggerScroll("searchText1"); 19 triggerScroll("searchText2"); 20 triggerScroll("searchText3"); 21 triggerScroll("searchText4"); 22 triggerScroll("searchText5"); 23 triggerScroll("searchText6"); 24 25 setTimeout(() => { 26 triggerScroll("searchText7"); 27 }, 500);
另附上防抖和節流的通用模板:
//防抖 let timeid = null; function debounce() { if (timeid) { clearTimeout(timeid); } timeid = setTimeout(() => { //這里寫操作 console.log(Math.random()); }, 500); } window.onscroll = debounce;//窗口滾動事件防抖
//節流 let timeid = null; function debounce(func, wait = 0) { if (!timeid) { timeid = setTimeout(() => { console.log(Math.random()); timeid = null; }, 500); } } window.onscroll = debounce;
2.js預編譯以及運行流程
最終輸出結果為“1 1 2 0”。這一題也會涉及到閉包,作用域等知識點。
1 var i = 0; 2 function fn() { 3 i = 1; 4 console.log(i); 5 var i; 6 return function () { 7 console.log(i); 8 i += 1; 9 } 10 } 11 12 var fun = fn();//1 13 fun();//1 14 fun();//2 15 console.log(i);//0
首先我們需要了解js運行流程:首先進行語法解析;先通篇的檢查js語法,若語法有誤,則報錯。其實進行預編譯;即發生在代碼執行之前,為代碼的執行做的准備工作,一般表現於變量提升,即把變量的聲明提前,但是賦值還是在響應位置賦值,再者就是把整個函數聲明提前。最后js運行流程進入最后一步即解析執行。
搞懂js運行流程再區分作用域,即全局作用域和函數作用域,全局作用域顧名思義就是全局聲明的變量或函數等。函數作用域也就是函數內部的作用域,在函數內部中聲明的變量或函數是不會傳到全局作用域的。
搞懂這兩個概念這一題就能夠迎刃而解了,首先全局聲明了一個變量i並賦值為0,在聲明一個fun接受fn執行后的返回值,所以fn會執行一次,局部變量i在fn函數中重新聲明,變量提升,這時函數fn中使用的i都為函數作用域中聲明的i,並且重新賦值為1,輸出結果1。這里不會影響到全局變量i的值。再觀望整體代碼,全局變量i未被重新賦值,所以最后一行i輸出為0.
再往下走,fu函數的返回值也是一個函數,所以fun接收到的是一個函數,fun函數被執行兩次,注意到在fn函數中,i被重新聲明過,所以這里的返回值函數即fun函數的作用域為局部作用域,使用的變量i為局部變量,即再fn函數中聲明的i,所以輸出1。輸出之后i自增1,即 i = i+1,局部作用域i的值在fun函數弟一次執行后變為2,再次執行后,輸出i為2。
3.值類型和引用類型
這一題主要涉及到值類型和引用類型的存儲方式,即堆內存和棧內存。
1 function fn(obj) { 2 obj.name = "name"; 3 obj = new Object(); 4 obj.name = "newName"; 5 } 6 var xt = new Object(); 7 fn(xt); 8 console.log(xt.name);//name
首先題目中聲明了一個函數fn和一個對象xt,調用fn函數,並把xt傳入函數。這是第二行的obj和xt都為引用類型,新增了obj的屬性name值也為name。由於此時obj和xt指向的同一片內存空間,所以xt也會新增name屬性,並設置值為name。故最后一個輸出name。
在第三行中可以看到obj被重新賦值,即重新開辟了一片內存空間,這時obj依然為新增name屬性而不是修改name屬性,並將name屬性設置為newName,所以這是obj新增的屬性不會影響到對象xt的屬性。
4.同步和異步
這一題主要是考察異步代碼和同步代碼執行的先后順序以及js任務隊列。
1 for (let index = 0; index < 5; index++) { 2 setTimeout(() => { 3 console.log(index); 4 }, 200); 5 } 6 7 for (let j = 0; j < 5; j++) { 8 setTimeout(() => { 9 console.log(j); 10 }, 200); 11 }
首先可以看到題目中有兩個循環體,兩個循環體中分別有兩個延時200ms的延時器。做題之前我們應該先了解到JavaScript對於同步代碼和異步代碼的處理方式,js會先執行同步代碼,遇到異步代碼(例:延時器、計時器、Promise等)會先放置任務隊列,等待觸發條件依次執行。這個概念了解清楚后這題就能夠迎刃而解了。
首先兩個循環體為同步代碼,會先執行。此時第一個循環體會將循環出的五個延時器放置任務隊列,等待200ms后執行。第二個循環體也是如此,所以輸出為“0 1 2 3 4 0 1 2 3 4”。
5.宏任務和微任務
此題主要是考察對於同步異步,以及宏任務和微任務的理解。
//寫出執行結果 setTimeout(() => { console.log(1); }, 0); new Promise((res) => { console.log(2); for (var index = 0; index < 1000; index++) { index = 9999 && res() } console.log(3); }).then(() => { console.log(4); }) console.log(5);
此題最終輸出結果為“2 3 5 4 1”;
解答此題我們需要先了解到Promise的特性。Promise是異步編程的一種解決方案,是解決恐怖回調/回調噩夢的一種方案,其就相當於一個容器,里面保存着某個未來才會結束的事件(通常是一個異步操作)的結果。
創建promise對象,function就相當於容器,用於存放異步代碼(不僅限於異步,同步也可以),此函數在頁面加載時會正常執行,同步代碼會進入主線程執行,異步代碼會進入任務隊列,未來才會得到結果。並且Promise有三種狀態,進行時(pending默認值),已成功(fulfilled/resolved),已失敗(rejected)一旦狀態改變,就不會再變,任何時候都可以得到這個結果,成功后會調用.then()方法,then方法接受兩個參數,皆為回調函數,第一個函數promise狀態變為已成功時觸發的,第二個函數promise狀態變為已失敗時觸發。
了解了Promise后,我們還需要搞清楚宏任務和微任務的概念。在上面我們提到過,js會見異步代碼放置任務隊列,宏任務是由宿主(Node、瀏覽器)發起的,常見的就是計時器和延時器;而微任務是由js引擎發起的,常見的就時Promise;執行順序為微任務先執行,宏任務后執行。
這時我們就可以來解答這個題目了,首先遇到延時器,為宏任務,放入任務隊列,其實遇到promise,先執行promise的同步代碼,輸出2,再進入循環,第一次進入循環index被重新賦值為9999,並且將promise的狀態改為已成功,再往下走輸出3;
此時promise狀態為已成功,觸發then方法的第一個函數,但是promise響應為異步響應。並且為微任務,放置任務隊列,排在宏任務之前。再往下走就輸出5。
此時同步代碼執行完畢,開始執行異步代碼,先執行微任務,輸出4,在執行延時器代碼。輸出5。
好啦。以上就是小譚今天分享的內容啦,也歡迎各位一起來討論技術呀!文章中有不足的地方小譚也歡迎各位指正探討,不勝感激!