JavaScript hiện đang là xu hướng trong giới lập trình web hiện giờ. Vì lý do đó, các câu hỏi về JavaScript đã dần xuất hiện trong các cuộc phỏng vấn công việc cho các lập trình viên.

Bài viết này sẽ không bàn đến các thư viện mới nhất của JavaScript, các tut luyện tập JavaScript hay là các hàm hay ho mới nhất của ES6. Nội dung của bài viết này sẽ tập trung vào 3 điều thường gặp trong các cuộc phỏng vấn về JavaScript. Chính tôi đã từng gặp những câu hỏi này, cả những nguời bạn của tôi nữa.

Đừng hiểu nhầm rằng chỉ cần học 3 điều này là bạn có thể vượt qua 1 cuộc phỏng vấn về JavaScript - có hằng hà sa số thứ bạn cần chuẩn bị trước buổi phỏng vấn sắp tới.  Tuy nhiên, 3 điều tôi sắp nói đến trong bài viết này chính là cơ sở chủ yếu để người phỏng vấn đánh giá kiến thức JavaScript và DOM của bạn tốt đến đâu.

Hãy cùng bắt đầu thôi. Chú ý rằng ta sẽ sử dụng vanilla JavaScript trong các ví dụ. Lý do bởi vì bạn cần chứng tỏ kiến thức JavaScript thuần túy của bạn với các phỏng vấn viên thay vì khoe khả năng sử dụng thư viện hay framework.

Câu hỏi #1: Event Delegation

Khi xây dựng ứng dụng, thỉnh thoảng bạn cần gán các event listener vào các button, đoạn text, hay hình ảnh trong 1 trang web để biểu diễn 1 hành động nào đó khi người dùng tương tác với các thành phần trên trang.

Lấy ví dụ với đoạn code của ứng dụng todo-list dưới đây. Người phỏng vấn sẽ muốn 1 hành động xảy ra khi người dùng click vào 1 trong những công việc trong danh sách. họ cũng muốn bạn thực thi tính năng này trong JavaScript:

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

Bạn có thể viết code như đoạn code dưới đây để gán các event listener vào các element:

document.addEventListener('DOMContentLoaded', function() {
  
  let app = document.getElementById('todo-app');
  let items = app.getElementsByClassName('item');
  
  // attach event listener to each item
  for (let item of items) {
    item.addEventListener('click', function() {
      alert('you clicked on item: ' + item.innerHTML);
    });
  }
  
});

Mặc dù đoạn code hoàn toàn đúng, tuy nhiên vấn đề chính là bạn đã phải gán từng event listener vào từng element riêng lẻ. Với số lượng element nhỏ, điều này là chấp nhận được. Tuy nhiên với số lượng element lớn, hàm bạn viết sẽ cần số lượng event listener tương đương và gán từng cái vào DOM. Rõ ràng công việc này không hiệu quả 1 chút nào.

Vì lẽ đó, trong buổi phỏng vấn, điều mà bạn nên làm trước tiên chính là hỏi lại người phỏng vấn số lượng element tối đa. Nếu nó không quá 10, đoạn code trên sẽ chạy rất ổn. Tuy nhiên nếu không có giới hạn về số lượng element thì bạn cần cân nhắc về những lựa chọn tối ưu hơn.

Trong trường hợp đó, lý tưởng nhất chính là gán một event listener cho cả container, cho phép nó truy cập vào từng item trong container khi được click. Giải pháp này được gọi là Event Delegation, rõ ràng nó hiệu quả hơn hẳn việc gán từng event listener một.

Dưới đây là code mẫu:

document.addEventListener('DOMContentLoaded', function() {
  
  let app = document.getElementById('todo-app');
  
  // attach event listener to whole container
  app.addEventListener('click', function(e) {
    if (e.target && e.target.nodeName === 'LI') {
      let item = e.target;
      alert('you clicked on item: ' + item.innerHTML);
    }
  });
  
});
Tham khảo các khóa học lập trình online, onlab, và thực tập lập trình tại TechMaster

Câu hỏi #2: Sử dụng closure bên trong 1 vòng lặp

Closure là 1 chủ đề thường thấy trong các buổi phỏng vấn về JavaScript, nó giúp người phỏng vấn đánh giá bạn thành thục ngôn ngữ đến đâu, bạn biết implement closure hay không.

Về cơ bản, closure là 1 hàm nội truy cập đến các biến bên ngoài phạm vi của nó. Closure có thể được sử dụng để implement privacy và tạo ra các function factory. Một câu hỏi phỏng vấn thường thấy về việc sử dụng closure sẽ có kiểu thế này:

Viết 1 function lặp qua 1 danh sách các số dạng integer và in ra index của mỗi giá trị sau thời gian chờ 3s.

Dưới đây là cách tôi hay thấy khi mọi người giải quyết bài toán này (thực ra là sai):

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);
}

Nếu bạn chạy đoạn code trên, output bạn nhận được sẽ luôn là 4, mặc dù ta mong rằng output sẽ phải là 0, 1, 2, 3 sau mỗi 3s.

Tại sao lại như thế? Để hiểu rõ lý do, đương nhiên bạn cần nắm vững kiến thức về closure của JavaScript, bởi vì người phỏng vấn đang kiểm tra bạn về nó cơ mà!

Lý do là bởi vì hàm setTimeout sẽ tạo ra 1 function (closure) có thể truy cập phạm vi bên ngoài nó, vòng loop sẽ chứa index i. Sau 3s, hàm được thực thi và nó sẽ log ra giá trị của i, là giá trị cuối cùng của vòng lặp (4).

1 số cách để viết hàm đúng. Dưới đây tôi sẽ chỉ nêu ra 2 cách:

const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
  // pass in the variable i so that each function 
  // has access to the correct index
  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++) {
  // using the ES6 let syntax, it creates a new binding
  // every single time the function is called
  // read more here: http://exploringjs.com/es6/ch_variables.html#sec_let-const-loop-heads
  setTimeout(function() {
    console.log('The index of this number is: ' + i);
  }, 3000);
}

Câu hỏi #3: Debouncing

Một số sự kiện xảy ra trên trình duyệt có thể xảy ra nhiều lần trong 1 khoảng thời gian ngắn, như là thay đổi kích thước cửa sổ hay cuộn chuột xuống. Nếu bạn gán 1 event listener với sự kiện cuộn chuột đề cập phía trên, và người sử dụng tiếp tục cuộn chuột xuống rất nhanh, event có thể sẽ xảy ra cả ngàn lần chỉ trong vòng khoảng 3s. Điều này sẽ gây ra 1 số vấn đề về hiệu năng khá nghiêm trọng.

Nếu bạn có dự định sẽ thảo luận trong buổi phỏng vấn về việc xây dựng 1 ứng dụng, các sự kiện như cuộn, thay đổi kích thước cửa sổ, hay là nhấn phím,... hãy chắc rằng bạn có đề cập đến debouncing và/hoặc throttling như là phương án để cải thiện tốc độ cũng như hiệu năng trang web. Đây là 1 ví dụ thực tế từ bài viết css trick:

Vào năm 2011, 1 vấn đề đã xuất hiện trên trang web Twitter: khi bạn cuộn chuột xuống để đọc các feed Twitter, nó ngày càng trở nên chậm và unresponsive. John Resig đã viết 1 bài post blog về vấn đề này để giải thích rằng ý tưởng gán trực tiếp hàm vào sự kiện scroll là tồi tệ đến mức nào.

Debouncing là cách để giải quyết vấn đề này với ý tưởng giới hạn thời gian cần để thực thi cho đến khi 1 hàm được gọi lại. Sử dụng debouncing đúng cách sẽ gói các hàm vào 1 hàm, và thực thi nó đúng 1 lần duy nhất. Dưới đây là 1 cách thực thi trong JavaScript, bao hàm cả các vấn đề như scope, closure, this, timing event:

// debounce function that will wrap our event
function debounce(fn, delay) {
  // maintain a timer
  let timer = null;
  // closure function that has access to timer
  return function() {
    // get the scope and parameters of the function 
    // via 'this' and 'arguments'
    let context = this;
    let args = arguments;
    // if event is called, clear the timer and start over
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    }, delay);
  }
}

Hàm này - khi wrap 1 event - sẽ thực thi chỉ khi thời gian set đã trôi qua.

// function to be called when user scrolls
function foo() {
  console.log('You are scrolling!');
}

// wrap our function in a debounce to fire once 2 seconds have gone by
let elem = document.getElementById('container');
elem.addEventListener('scroll', debounce(foo, 2000));

Throttling là 1 kĩ thuật khác tương tự debouncing, ngoại trừ việc không chờ 1 khoảng thời gian rồi mới gọi hàm. Thay vào đó, nó chỉ kéo dài lời gọi hàm qua khoảng thời gian dài hơn. Do đó nếu có 1 event xảy ra 10 lần trong vòng 100 mili giây, throttling sẽ kéo dài từng lời gọi hàm để thực thi mỗi 2s thay vì tất cả trong 100 mili giây.

Để biết thêm về debouncing và throttling, hãy tìm hiểu thêm các bài viết và tutorial dưới đây:

Bài viết được dịch từ: https://medium.freecodecamp.com/3-questions-to-watch-out-for-in-a-javascript-interview-725012834ccb