3 个经常被问到的 JavaScript 面试题

Lud4185 7年前
   <p style="text-align:center"><img src="https://simg.open-open.com/show/4f0cf74b23efe8cc1bae707c2a16f22b.jpg"></p>    <p>JavaScript是所有现代浏览器的官方语言。 因此,JavaScript 问题出现在各种开发人员的面试中。</p>    <p>本文不是讲述最新的JavaScript库,日常的开发实践或任何新的 ES6 函数 。 相反,在讨论JavaScript时,经常会在面试中出现这3个问题。 我自己被问到过这些问题,我的朋友告诉我他们也被到问过。</p>    <p>当然,你在JavaScript面试前不应该只学习这3个问题 – 这里有 很多 方法 可以 让你更好地准备即将到来的面试 – 但面试官可能会问到下面是3个问题,来判断你对JavaScript语言的理解和DOM的掌握程度。</p>    <p>让我们开始吧!请注意,我们将在下面的示例中使用原生 JavaScript,因为你的面试官通常想看看你在没有第三方库(比如jQuery)的帮助下,是如何理解 JavaScript 和 DOM 的。</p>    <h3>问题 #1: 事件委托</h3>    <p>愚人码头注:也叫事件委派,时间代理等;</p>    <p>当构建应用程序时,有时你需要将事件监听器绑定到页面上的按钮,文本或图像上,以便在用户与元素交互时执行某些操作。</p>    <p>如果我们以一个简单的待办事项列表为例,面试官可能会告诉你,他们希望在用户单击其中一个列表项时需要执行某些操作。 他们希望你用 JavaScript 实现这个功能,假设HTML代码如下:</p>    <pre>  <code class="language-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></code></pre>    <p>你可能会想像下面这样在元素绑定事件监听器:</p>    <pre>  <code class="language-javascript">document.addEventListener('DOMContentLoaded', function() {        let 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);      });    }      });</code></pre>    <p>虽然这个实现了功能,问题是您要单独将事件侦听器绑定到每个列表项。这是4个元素,没什么大问题,但如果有人在他们的待办事项列表中添加了10,000个事项(他们可能有很多事情要做)怎么办?然后你的函数将创建 10,000 个独立的事件监听器,并将每个事件监听器绑定到 DOM 。这样代码执行的 <a href="/misc/goto?guid=4959739785471986883" rel="nofollow,noindex">效率非常低下</a> 。</p>    <p>在面试中,最好首先询问面试官用户可以输入事项的最大数量是多少。如果它永远不会超过 10 个,上面的代码将工作正常。但是,如果用户可以输入的事项数量没有限制,那么你应该使用一个更高效的解决方案。</p>    <p>如果你的应用程序最终可能有几百个事件监听器,更高效的解决方案是将一个事件侦听器实际绑定到整个容器上,然后在实际单击时可以访问每个确切元素。这被称为 <a href="/misc/goto?guid=4959738564202987535" rel="nofollow,noindex">事件委托</a> ,并且它每个元素单独绑定事件处理程序更高效。</p>    <p>用事件委托的代码:</p>    <pre>  <code class="language-javascript">document.addEventListener('DOMContentLoaded', function() {        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);      }    });      });</code></pre>    <h3>问题 #2: 在循环内使用闭包(Closures)</h3>    <p>闭包常常在面试中出现,以便面试官衡量你对这门语言的熟悉程度,以及是否知道何时使用闭包。</p>    <p>闭包的本质是 一个内部函数访问其作用域之外的变量 。闭包可以用于实现诸如 私有变量 和 创建 工厂函数 之类的东西。关于使用闭包的常见面试问题是这样的:</p>    <p>编写一个函数,它将循环遍历整数列表,并在3秒延迟后打印每个元素的索引。</p>    <p>我看到这个问题的最常见(但是不正确)是像下面这样的实现:</p>    <pre>  <code class="language-javascript">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);  }</code></pre>    <p>如果运行上面代码,3秒延迟后你会看到,实际上每次打印输出是 4 ,而不是期望的 0,1,2,3 。</p>    <p>为了正确理解为什么会发生这种情况,在JavaScript中很有用,这正是面试官真正的意图。</p>    <p>其原因是因为 setTimeout 函数创建了一个可以访问其外部作用域的函数(也就是我们经常说的闭包),每个循环都包含了索引 i 。</p>    <p>3秒后,该函数被执行并且打印出 i 的值,其在循环结束时为 4 ,因为它的循环周期经历了 0,1,2,3,4 ,并且循环最终在 4 时停止。</p>    <p>实际有 几种 正确的写法 来解决这个问题,下面列举两种:</p>    <pre>  <code class="language-javascript">const arr = [10, 12, 15, 21];  for (var i = 0; i < arr.length; i++) {    // 通过传递变量 i    // 在每个函数中都可以获取到正确的索引    setTimeout(function(i_local) {      return function() {        console.log('The index of this number is: ' + i_local);      }    }(i), 3000);  }</code></pre>    <pre>  <code class="language-javascript">const arr = [10, 12, 15, 21];  for (let i = 0; i < arr.length; i++) {    // 使用ES6的let语法,它会创建一个新的绑定    // 每个方法都是被单独调用的    // 更多详细信息请阅读: http://exploringjs.com/es6/ch_variables.html#sec_let-const-loop-heads    setTimeout(function() {      console.log('The index of this number is: ' + i);    }, 3000);  }</code></pre>    <h3>问题 #3: 函数防抖动(Debouncing)</h3>    <p>有一些浏览器事件可以在很短的时间内快速启动多次,例如调整窗口大小或向下滚动页面。例如,如果将事件侦听器绑定到窗口滚动事件上,并且用户继续非常快速地向下滚动页面,你的事件可能会在3秒的范围内被触发数千次。这可能会导致一些严重的性能问题。</p>    <p>如果你在面试中讨论构建应用程序和事件,如滚动,窗口调整大小,或键盘按下的事件时,请务必提及 函数防抖动(Debouncing) 和/或 函数节流(Throttling)来提升页面速度和性能。一个真实的案例,来自 guest post on css-tricks :</p>    <p>在2011年,一个问题在推ter上被提出:当你滚动推ter feed时,它会会变得非常慢甚至未响应。John Resig 就这个问题发布了一篇博文 ,它解释了直接绑定函数到 scroll 事件上是多么糟糕的事。</p>    <p>函数防抖动(Debouncing) 是解决这个问题的一种方式,通过限制需要经过的时间,直到再次调用函数。一个正确实现函数防抖的方法是:把多个函数放在一个函数里调用,隔一定时间执行一次。这里有一个使用原生JavaScript实现的例子,用到了 作用域 、闭包、 this 和 定时事件 :</p>    <pre>  <code class="language-javascript">// debounce函数用来包裹我们的事件  function debounce(fn, delay) {    // 持久化一个定时器 timer    let timer = null;    // 闭包函数可以访问 timer    return function() {      // 通过 'this' 和 'arguments'      // 获得函数的作用域和参数      let context = this;      let args = arguments;      // 如果事件被触发,清除 timer 并重新开始计时      clearTimeout(timer);      timer = setTimeout(function() {        fn.apply(context, args);      }, delay);    }  }</code></pre>    <p>当这个函数绑定在一个事件上,只有经过一段指定的时间后才会被调用。</p>    <p>你可以像这样去使用这个函数:</p>    <pre>  <code class="language-javascript">// 当用户滚动时函数会被调用  function foo() {    console.log('You are scrolling!');  }    // 在事件触发的两秒后,我们包裹在debounce中的函数才会被触发  let elem = document.getElementById('container');  elem.addEventListener('scroll', debounce(foo, 2000));</code></pre>    <p>函数节流是另一个类似函数防抖的技巧,除了使用等待一段时间再调用函数的方法,函数节流还限制固定时间内只能调用一次。所以一个事件如果在100毫秒内发生10次,函数节流会每2秒调用一次函数,而不是100毫秒内全部调用。</p>    <p> </p>    <p> </p>    <p>来自:http://www.css88.com/archives/7059</p>    <p> </p>