3個常常被問到的 JavaScript 面試題

原文連接javascript

問題 #1: 事件委託

事件委託,也叫事件委派,事件代理。css

當構建應用程序時,有時須要將事件監聽器綁定到頁面上的某些元素上,以便在用戶與元素交互時執行某些操做。html

假設咱們如今有一個無序列表:java

<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>

咱們須要在<li>上綁定點擊事件,咱們可能會這樣操做:node

app = document.getElementById('todo-app');
let items = app.getElementsByClassName('item');

// 將事件偵聽器綁定到每一個列表項
for (let item of items) {
  item.addEventListener('click', function() {
    alert('you clicked on item: ' + item.innerHTML);
  });
}

雖然這樣能夠實現功能,但問題是要單獨將事件偵聽器綁定到每一個列表項。這是4個元素,沒什麼大問題,但若是列表中有10,000個事項,怎麼辦?這個函數將會建立10,000個獨立的事件監聽器,並將每一個事件監聽器綁定到 DOM 。這樣代碼執行的效率很是低下面試

更高效的解決方案是將一個事件偵聽器實際綁定到父容器<ul>上,而後在實際單擊時能夠訪問每一個確切元素。這被稱爲事件委託,而且它比每一個元素單獨綁定事件的處理程序更高效。瀏覽器

那麼上面的代碼能夠改變爲:閉包

let app = document.getElementById('todo-app');
  
// 事件偵聽器綁定到整個容器上
app.addEventListener('click', function(e) {
  if (e.target && e.target.nodeName === 'LI') {
    let item = e.target;
    alert('you clicked on item: ' + item.innerHTML);
  }
});

問題 #2: 在循環內使用閉包(Closures)

閉包的本質是一個內部函數訪問其做用域以外的變量。閉包能夠用於實現諸如 私有變量 和 建立工廠函數之類的東西。app

在面試中咱們可能會見到一段這樣的代碼:函數

for (var i = 0; i < 4; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

運行上面的代碼控制檯會在1秒後打印4個4,而不是0,1,2,3。

其緣由是由於setTimeout函數建立了一個能夠訪問其外部做用域的函數(也就是咱們常常說的閉包),每一個循環都包含了索引i

1秒後,該函數被執行而且打印出i的值,其在循環結束時爲4,由於它的循環週期經歷了0,1,2,3,4,而且循環最終在4時中止。

下面列舉兩種方案解決這個問題:

for (var i = 0; i < 4; i++) {
  // 經過傳遞變量 i
  // 在每一個函數中均可以獲取到正確的索引
  setTimeout(function(j) {
    return function() {
      console.log(j);
    }
  }(i), 1000);
}
for (let i = 0; i < 4; i++) {
  // 使用ES6的let語法,它會建立一個新的綁定
  // 每一個方法都是被單獨調用的
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

問題 #3: 函數防抖(Debouncing)

有一些瀏覽器事件能夠在很短的時間內快速啓動屢次,例如頁面滾動事件。若是將事件偵聽器綁定到窗口滾動事件上,而且用戶快速滾動頁面,事件極可能會在短期屢次觸發。這可能會致使一些嚴重的性能問題。

所以,在偵聽滾動,窗口調整大小,或鍵盤按下的事件時,請務必使用函數防抖動(Debouncing)函數節流(Throttling)來提高頁面速度和性能。

函數防抖(Debouncing)是解決這個問題的一種方式,經過限制須要通過的時間,直到再次調用函數。一個實現函數防抖的方法是:把多個函數放在一個函數裏調用,隔必定時間執行一次。

這裏有一個使用原生JavaScript實現的例子,用到了做用域、閉包、this和定時事件:

function debounce(fn, delay) {
  // 持久化一個定時器 timer
  let timer = null;
  // 閉包函數能夠訪問 timer
  return function() {
    // 經過 'this' 和 'arguments' 得到函數的做用域和參數
    let self = this;
    let args = arguments;
    // 若是事件被觸發,清除 timer 並從新開始計時
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(self, args);
    }, delay);
  }
}

// 當用戶滾動時調用函數foo()
function foo() {
  console.log('You are scrolling!');
} 

// 在事件觸發的兩秒後,包裹在debounce()中的函數纔會被觸發
window.addEventListener('scroll', debounce(foo, 2000));

函數節流是另外一個相似函數防抖的技巧,除了使用等待一段時間再調用函數的方法,函數節流還限制固定時間內只能調用一次。因此,若是一個事件在100毫秒內發生10次,函數節流會每2秒調用一次函數,而不是100毫秒內所有調用。

(完)

相關文章
相關標籤/搜索