JavaScript中最常見(jiàn)的三個(gè)面試題解析
前言
本文不是講述最新的JavaScript庫(kù),日常的開(kāi)發(fā)實(shí)踐或任何新的 ES6 函數(shù)。 相反,在討論JavaScript時(shí),經(jīng)常會(huì)在面試中出現(xiàn)這3個(gè)問(wèn)題。 我自己被問(wèn)到過(guò)這些問(wèn)題,我的朋友告訴我他們也被到問(wèn)過(guò)。
當(dāng)然,你在JavaScript面試前不應(yīng)該只學(xué)習(xí)這3個(gè)問(wèn)題 – 這里有很多 方法 可以讓你更好地準(zhǔn)備即將到來(lái)的面試 – 但面試官可能會(huì)問(wèn)到下面是3個(gè)問(wèn)題,來(lái)判斷你對(duì)JavaScript語(yǔ)言的理解和DOM的掌握程度。
讓我們開(kāi)始吧!請(qǐng)注意,我們將在下面的示例中使用原生 JavaScript,因?yàn)槟愕拿嬖嚬偻ǔO肟纯茨阍跊](méi)有第三方庫(kù)(比如jQuery)的幫助下,是如何理解 JavaScript 和 DOM 的。
問(wèn)題 #1: 事件委托
注:也叫事件委派,時(shí)間代理等;
當(dāng)構(gòu)建應(yīng)用程序時(shí),有時(shí)你需要將事件監(jiān)聽(tīng)器綁定到頁(yè)面上的按鈕,文本或圖像上,以便在用戶(hù)與元素交互時(shí)執(zhí)行某些操作。
如果我們以一個(gè)簡(jiǎn)單的待辦事項(xiàng)列表為例,面試官可能會(huì)告訴你,他們希望在用戶(hù)單擊其中一個(gè)列表項(xiàng)時(shí)需要執(zhí)行某些操作。
他們希望你用 JavaScript 實(shí)現(xiàn)這個(gè)功能,假設(shè)HTML代碼如下:
<ul id="todo-app"> <li class="item">Walk the dog</li> <li class="item">Pay bills</li> <li class="item">Make dinner</li> <li class="item">Code for one hour</li> </ul>
你可能會(huì)想像下面這樣在元素綁定事件監(jiān)聽(tīng)器:
document.addEventListener('DOMContentLoaded', function() {
let app = document.getElementById('todo-app');
let items = app.getElementsByClassName('item');
// 將事件偵聽(tīng)器綁定到每個(gè)列表項(xiàng)
for (let item of items) {
item.addEventListener('click', function() {
alert('you clicked on item: ' + item.innerHTML);
});
}
});
雖然這個(gè)實(shí)現(xiàn)了功能,問(wèn)題是您要單獨(dú)將事件偵聽(tīng)器綁定到每個(gè)列表項(xiàng)。這是4個(gè)元素,沒(méi)什么大問(wèn)題,但如果有人在他們的待辦事項(xiàng)列表中添加了10,000個(gè)事項(xiàng)(他們可能有很多事情要做)怎么辦?然后你的函數(shù)將創(chuàng)建 10,000 個(gè)獨(dú)立的事件監(jiān)聽(tīng)器,并將每個(gè)事件監(jiān)聽(tīng)器綁定到 DOM 。這樣代碼執(zhí)行的效率非常低下。
在面試中,最好首先詢(xún)問(wèn)面試官用戶(hù)可以輸入事項(xiàng)的最大數(shù)量是多少。如果它永遠(yuǎn)不會(huì)超過(guò) 10 個(gè),上面的代碼將工作正常。但是,如果用戶(hù)可以輸入的事項(xiàng)數(shù)量沒(méi)有限制,那么你應(yīng)該使用一個(gè)更高效的解決方案。
如果你的應(yīng)用程序最終可能有幾百個(gè)事件監(jiān)聽(tīng)器,更高效的解決方案是將一個(gè)事件偵聽(tīng)器實(shí)際綁定到整個(gè)容器上,然后在實(shí)際單擊時(shí)可以訪(fǎng)問(wèn)每個(gè)確切元素。這被稱(chēng)為事件委托,并且它每個(gè)元素單獨(dú)綁定事件處理程序更高效。
用事件委托的代碼:
document.addEventListener('DOMContentLoaded', function() {
let app = document.getElementById('todo-app');
// 事件偵聽(tīng)器綁定到整個(gè)容器上
app.addEventListener('click', function(e) {
if (e.target && e.target.nodeName === 'LI') {
let item = e.target;
alert('you clicked on item: ' + item.innerHTML);
}
});
});
問(wèn)題 #2: 在循環(huán)內(nèi)使用閉包(Closures)
閉包常常在面試中出現(xiàn),以便面試官衡量你對(duì)這門(mén)語(yǔ)言的熟悉程度,以及是否知道何時(shí)使用閉包。
閉包的本質(zhì)是一個(gè)內(nèi)部函數(shù)訪(fǎng)問(wèn)其作用域之外的變量。閉包可以用于實(shí)現(xiàn)諸如 私有變量 和 創(chuàng)建工廠(chǎng)函數(shù)之類(lèi)的東西。關(guān)于使用閉包的常見(jiàn)面試問(wèn)題是這樣的:
編寫(xiě)一個(gè)函數(shù),它將循環(huán)遍歷整數(shù)列表,并在3秒延遲后打印每個(gè)元素的索引。
我看到這個(gè)問(wèn)題的最常見(jiàn)(但是不正確)是像下面這樣的實(shí)現(xiàn):
const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log('The index of this number is: ' + i);
}, 3000);
}
如果運(yùn)行上面代碼,3秒延遲后你會(huì)看到,實(shí)際上每次打印輸出是4,而不是期望的0,1,2,3 。
為了正確理解為什么會(huì)發(fā)生這種情況,在JavaScript中很有用,這正是面試官真正的意圖。
其原因是因?yàn)?setTimeout 函數(shù)創(chuàng)建了一個(gè)可以訪(fǎng)問(wèn)其外部作用域的函數(shù)(也就是我們經(jīng)常說(shuō)的閉包),每個(gè)循環(huán)都包含了索引i。
3秒后,該函數(shù)被執(zhí)行并且打印出i的值,其在循環(huán)結(jié)束時(shí)為4,因?yàn)樗难h(huán)周期經(jīng)歷了0,1,2,3,4,并且循環(huán)最終在4時(shí)停止。
實(shí)際有幾種 正確的寫(xiě)法來(lái)解決這個(gè)問(wèn)題,下面列舉兩種:
const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
// 通過(guò)傳遞變量 i
// 在每個(gè)函數(shù)中都可以獲取到正確的索引
setTimeout(function(i_local) {
return function() {
console.log('The index of this number is: ' + i_local);
}
}(i), 3000);
}
const arr = [10, 12, 15, 21];
for (let i = 0; i < arr.length; i++) {
// 使用ES6的let語(yǔ)法,它會(huì)創(chuàng)建一個(gè)新的綁定
// 每個(gè)方法都是被單獨(dú)調(diào)用的
// 更多詳細(xì)信息請(qǐng)閱讀: http://exploringjs.com/es6/ch_variables.html#sec_let-const-loop-heads
setTimeout(function() {
console.log('The index of this number is: ' + i);
}, 3000);
}
問(wèn)題 #3: 函數(shù)防抖動(dòng)(Debouncing)
有一些瀏覽器事件可以在很短的時(shí)間內(nèi)快速啟動(dòng)多次,例如調(diào)整窗口大小或向下滾動(dòng)頁(yè)面。例如,如果將事件偵聽(tīng)器綁定到窗口滾動(dòng)事件上,并且用戶(hù)繼續(xù)非??焖俚叵蛳聺L動(dòng)頁(yè)面,你的事件可能會(huì)在3秒的范圍內(nèi)被觸發(fā)數(shù)千次。這可能會(huì)導(dǎo)致一些嚴(yán)重的性能問(wèn)題。
如果你在面試中討論構(gòu)建應(yīng)用程序和事件,如滾動(dòng),窗口調(diào)整大小,或鍵盤(pán)按下的事件時(shí),請(qǐng)務(wù)必提及 函數(shù)防抖動(dòng)(Debouncing) 和/或 函數(shù)節(jié)流(Throttling)來(lái)提升頁(yè)面速度和性能。一個(gè)真實(shí)的案例,來(lái)自 guest post on css-tricks:
在2011年,一個(gè)問(wèn)題在Twitter上被提出:當(dāng)你滾動(dòng)Twitter feed時(shí),它會(huì)會(huì)變得非常慢甚至未響應(yīng)。John Resig 就這個(gè)問(wèn)題發(fā)布了一篇博文,它解釋了直接綁定函數(shù)到scroll事件上是多么糟糕的事。
函數(shù)防抖動(dòng)(Debouncing) 是解決這個(gè)問(wèn)題的一種方式,通過(guò)限制需要經(jīng)過(guò)的時(shí)間,直到再次調(diào)用函數(shù)。一個(gè)正確實(shí)現(xiàn)函數(shù)防抖的方法是:把多個(gè)函數(shù)放在一個(gè)函數(shù)里調(diào)用,隔一定時(shí)間執(zhí)行一次。這里有一個(gè)使用原生JavaScript實(shí)現(xiàn)的例子,用到了作用域、閉包、this和定時(shí)事件:
// debounce函數(shù)用來(lái)包裹我們的事件
function debounce(fn, delay) {
// 持久化一個(gè)定時(shí)器 timer
let timer = null;
// 閉包函數(shù)可以訪(fǎng)問(wèn) timer
return function() {
// 通過(guò) 'this' 和 'arguments'
// 獲得函數(shù)的作用域和參數(shù)
let context = this;
let args = arguments;
// 如果事件被觸發(fā),清除 timer 并重新開(kāi)始計(jì)時(shí)
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
}
}
當(dāng)這個(gè)函數(shù)綁定在一個(gè)事件上,只有經(jīng)過(guò)一段指定的時(shí)間后才會(huì)被調(diào)用。
你可以像這樣去使用這個(gè)函數(shù):
// 當(dāng)用戶(hù)滾動(dòng)時(shí)函數(shù)會(huì)被調(diào)用
function foo() {
console.log('You are scrolling!');
}
// 在事件觸發(fā)的兩秒后,我們包裹在debounce中的函數(shù)才會(huì)被觸發(fā)
let elem = document.getElementById('container');
elem.addEventListener('scroll', debounce(foo, 2000));
函數(shù)節(jié)流是另一個(gè)類(lèi)似函數(shù)防抖的技巧,除了使用等待一段時(shí)間再調(diào)用函數(shù)的方法,函數(shù)節(jié)流還限制固定時(shí)間內(nèi)只能調(diào)用一次。所以一個(gè)事件如果在100毫秒內(nèi)發(fā)生10次,函數(shù)節(jié)流會(huì)每2秒調(diào)用一次函數(shù),而不是100毫秒內(nèi)全部調(diào)用。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- 13道關(guān)于JavaScript正則表達(dá)式的面試題
- 詳解JS中的this、apply、call、bind(經(jīng)典面試題)
- 關(guān)于javascript作用域的常見(jiàn)面試題分享
- 面試常見(jiàn)的js算法題
- 10道典型的JavaScript面試題
- 80%應(yīng)聘者都不及格的JS面試題
- Javascript前端經(jīng)典的面試題及答案
- 一篇文章搞定JavaScript類(lèi)型轉(zhuǎn)換(面試常見(jiàn))
- 總結(jié)幾道關(guān)于Node.js的面試問(wèn)題
- 「中高級(jí)前端面試」JavaScript手寫(xiě)代碼無(wú)敵秘籍(推薦)
相關(guān)文章
JavaScript數(shù)據(jù)存儲(chǔ) Cookie篇
這篇文章主要為大家介紹了JavaScript數(shù)據(jù)存儲(chǔ) Cookie篇,感興趣的朋友可以參考一下2016-07-07
ECMAScript5中的對(duì)象存取器屬性:getter和setter介紹
這篇文章主要介紹了ECMAScript5中的對(duì)象屬性存取器:getter和setter介紹,事實(shí)上在除ie外最新主流瀏覽器的實(shí)現(xiàn)中,任何一個(gè)對(duì)象的鍵值都可以被getter和setter方法所取代,這被稱(chēng)之為“存取器屬性”,需要的朋友可以參考下2014-12-12
axios params 和 data 的區(qū)別小結(jié)
本文介紹了在前端開(kāi)發(fā)中使用Axios發(fā)送HTTP請(qǐng)求時(shí),params和data的區(qū)別及其應(yīng)用場(chǎng)景,具有一定的參考價(jià)值,感興趣的可以了解一下2025-03-03
Uniapp中嵌入H5并在H5中跳轉(zhuǎn)到APP的指定頁(yè)面方法詳解
Uniapp是一款基于Vue.js框架的跨平臺(tái)開(kāi)發(fā)工具,支持在一套代碼中開(kāi)發(fā)出運(yùn)行于各大平臺(tái)的應(yīng)用程序,這篇文章主要給大家介紹了關(guān)于Uniapp中嵌入H5并在H5中跳轉(zhuǎn)到APP的指定頁(yè)面的相關(guān)資料,需要的朋友可以參考下2023-09-09
JavaScript如何刪除對(duì)象的某個(gè)屬性詳析
這篇文章主要給大家介紹了關(guān)于JavaScript如何刪除對(duì)象的某個(gè)屬性的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05

