zoukankan      html  css  js  c++  java
  • avaScript技术面试时要小心的三个问题

    JavaScript是所有现代浏览器的官方语言。同样的,JavaScript面试题出现在各种各样的面试中。

    这篇文章不是讲述JavaScript最新的库、日常的开发实践,或是ES6的新功能。当然了,上面说的这3点经常出现在JavaScript的面试中。我自己也曾经问过面试者这些问题,我的朋友告诉我,他们同样也是。

    当然,你去面试前不能只准备上面提到的3点,这里有许多方法让你能够更好的面对即将到来的面试。

    但是,接下来的这3个问题,面试官可能会根据你的回答,去判断你对JavaScript和DOM的了解程度。

    所以,现在开始吧!请注意,我们在接下来的例子中将使用原生JavaScript,因为你的面试官可能想看看在没有第三方库的情况下,比如jQuery,你对JavaScript和DOM的理解程度。

    问题 #1: 事件委托

    当构建一个程序时,有些时候你需要监听按钮、文本、图片的事件,因为当用户与界面元素发生互动时,你需要执行一些动作。

    如果我们拿到一个简单的待办事项列表,就像下面例子中这样,面试官可能会告诉你,当用户点击其中一个列表项时,我们需要执行一些动作。面试官希望你用JavaScript实现这个功能,假设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>
    

    你也许会像下面这样去给这些元素绑定事件监听:

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

    虽然实现了功能,但问题是我们给每个列表项都单独绑定了事件监听。现在只有4个元素,没问题,但是如果有10000个待办事项呢(程序可能还有其他事情要做)?接下来你会创建10000个事件监听函数绑定要每个DOM元素上,这是十分低效的。

    在面试时,最好问问面试官用户可以输入的最大元素数量。如果数量小于10个,上面的例子可以很好的运行。但是如果数量不受限制,你可能需要使用一个更有效率的解决办法。

    如果你的应用有数百个事件监听,更有效率的解决办法是:把事件监听绑定在包裹这些元素的容器上,当元素被点击时,我可以得到当前点击的确切元素。这种技巧叫事件委托,而且它比给每个元素绑定事件监听效率要高。

    用事件委托的方式实现上面的功能:

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

    问题 #2: 在循环内使用闭包

    闭包问题常常在面试中被提出,面试官能通过它估算出你对JavaScript的熟悉程度,同时了解你对闭包是否熟悉。

    闭包的精髓就是:在外部函数中可以读取到内部函数的作用域。闭包可以做这些事:创建私有变量创建私有函数等。大多数关于闭包的面试题像这样:

    循环一个数组,并在3秒后打印出每个数组元素的索引。

    一个通常的实现方式像下面这样,但其实是错误的:

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

    如果运行上面代码你会发现,3秒后,每次循环输出的都是4,而不是期望的0,1,2,3

    真正的去理解为什么会发生这些,它会帮助你更好的认识JavaScript,因为你也不知道面试官会出怎样确切的题测试你。

    出现上面现象的原因是:setTimeout会创建一个函数(就是闭包),它可以读取到外部作用域,每个循环都包含了索引i。函数在3秒后执行,它打印出外部作用域中i的值,在循环结束后i等于4,因为它的循环周期经历了0,1,2,3,4,最终在i为4时停止。

    这里有几种正确的方法去解决这个问题,下面列举两种:

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

    问题 #3: 函数防抖(Debouncing)

    有些浏览器事件可以在很短的时间内执行多次,就像改变浏览器窗口尺寸和滚动页面。如果你绑定一个事件去监听窗口的滚动,用户快速连续的滚动页面,这个事件可能会在3秒内被触发几千次。这可能会导致很严重的性能问题。

    如果你们在面试中讨论构建一个应用,谈到类似滚动、改变窗口尺寸或键盘按下的事件时,一定会提及函数防抖、函数节流(Throttling)去优化页面速度和性能。一个真实的案例,来自guest post on css-tricks:

    在2011年,一个问题在Twitter上被提出:当你滚动Twitter feed时,它会十分缓慢且迟钝。John Resig就这个问题发布了一篇博客,它解释了直接绑定函数到滚动事件上是多么糟糕和昂贵的事。

    函数防抖是解决这个问题的一种方式,通过限制函数被调用的次数。一个正确实现函数防抖的方法是:把多个函数放在一个函数里调用,隔一定时间执行一次。这里有一个使用原生JavaScript实现的例子,用到了作用域、闭包、this和定时时间:

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

    当这个函数绑定在一个事件上,只有经过一段指定的时间后才会被调用。

    你可以像这样去使用这个函数:

    // 当用户滚动时函数会被调用
    function foo() {
      console.log('You are scrolling!');
    }
    
    // 在事件触发的两秒后,我们包裹在debounce中的函数才会被触发
    let elem = document.getElementById('container');
    elem.addEventListener('scroll', debounce(foo, 2000));
    

    函数节流是另一个类似函数防抖的技巧,除了使用等待一段时间再调用函数的方法,函数节流还限制固定时间内只能调用一次。所以一个事件如果在100毫秒内发生10次,函数节流会每2秒调用一次函数,而不是100毫秒内全部调用。

    想了解更多关于函数防抖和函数节流的信息,下面的文章和教程可能会帮到你:

    译者注:之前听朋友讲过这个例子,我就很清晰的分辨了两者的区别:

    想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应。假设电梯有两种运行策略 throttle 和 debounce ,超时设定为15秒,不考虑容量限制。

    throttle 策略的电梯。保证如果电梯第一个人进来后,15秒后准时运送一次,不等待。如果没有人,则待机。

    debounce 策略的电梯。如果电梯里有人进来,等待15秒。如果又人进来,15秒等待重新计时,直到15秒超时,开始运送。

    本文由Rockjins Blog翻译,转载请与译者联系。否则将追究法律责任。

  • 相关阅读:
    C#网络编程系列(两)它Socket同步TCPserver
    [LeetCode] ZigZag Conversion [9]
    设计模式迭代器模式
    Android_WebServices_介绍
    (UML两个汇总)九种图。
    你不明白 String 类别
    Mockito使用注意事项
    Xcode6为什么干掉pch(Precompile Prefix Header)&amp;怎样加入pch文件
    atoi()函数的实现
    多种方法求解八数码问题
  • 原文地址:https://www.cnblogs.com/zzsdream/p/6372690.html
Copyright © 2011-2022 走看看