资讯专栏INFORMATION COLUMN

那些年,前端学习之路的疑难杂症(四):面试中遇到的3个问题概览

gecko23 / 775人阅读

摘要:闭包能用来实现私有化和创建工厂函数等作用。关于闭包的常见面试题是这样的写一个函数,循环一个整数数组,延迟秒打印这个数组中每个元素的索引。

文章来源:http://mp.weixin.qq.com/s/vs0...

前言

在公众号上看到了这篇文章,觉得很有用,有助于理解JS学习中的一些重点难点。决定把它整理下发布出来。该文章主要介绍了JS中的三个问题。在以后的几篇文章里,我会详细介绍这三个问题。

文章内容

JavaScript 是所有现代浏览器的官方语言。因此,各种语言的开发者面试中都会遇到 JavaScript 问题。
本文不讲最新的 JavaScript 库,通用开发实践,或任何新的 ES6 函数。而是讲讲面试中经常出现的 3 个 JavaScript 问题。我问过这些问题,我的朋友说他们也问。
当然不是说你在准备 JavaScript 面试时只要学习这 3 个问题 —— 你还有很多途径去更好的准备即将到来的面试 —— 但面试官很有可能通过下面 3 个问题来判断你了解和掌握 JavaScript 和 DOM 的情况。
让我们开始吧!注意下面的例子中我们使用原生 JavaScript, 因为面试官通常想考查你在不借助库(例如 jQuery)的帮助时掌握 JavaScript 和 DOM 的情况。

问题 #1: 事件代理

创建应用时,有时需要给页面中的按钮,文字,或图片添加事件监听器,当用户与这些元素交互时触发某些操作。

我们以一个简单的代办事项列表为例,面试官会告诉你,他们希望在用户点击列表中某一项时触发一个动作。并让你用 JavaScript 根据下面的 HTML 代码实现这个功能:

  • Walk the dog
  • Pay bills
  • Make dinner
  • Code for one hour

你可能会像下面的代码一样给元素添加事件监听器:

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 项时没有问题,但如果有人给代办事项列表新增了 10,000 个事项呢(他们也许有一大堆事情要做)?那时函数会创建 10,000 个事件监听器,然后把它们都添加到 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: 在循环中使用闭包

面试中经常会问到闭包,因为面试官能通过这个问题的回答判断你对语言的熟悉程度,以及考察你是否知道什么时候使用闭包。

闭包就是能访问作用域外部变量的内部函数 。闭包能用来实现私有化和创建工厂函数等作用。关于闭包的常见面试题是这样的:

写一个函数,循环一个整数数组,延迟 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 后最终停在了 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 秒之内被触发数千次。这会导致非常严重的性能问题。

如果在面试中讨论到构建应用程序,以及滚动事件,窗口调整事件,或者键盘事件等,请务必提及 debouncing 或者 throttling,作为提高页面速度与性能的方法。来一个 css-tricks 的实例:

2011 年,Twitter 出了一个问题:当滚动 Twitter 摘要时,页面变的很卡甚至无响应。John Resig 写了一篇关于这个问题的博客,解释了直接将耗时的函数绑定在 scroll 事件上是一个多么糟糕的想法。
Debouncing 是解决这个问题的一种方法,它的做法是限制下次函数调用之前必须等待的时间间隔。正确实现 debouncing 的方法是将若干个函数调用 合成 一次,并在给定时间过去之后仅被调用一次。下面是一个原生 JavaScript 的实现,用到了作用域, 闭包, this, 和 计时事件:

// 将会包装事件的 debounce 函数
function debounce(fn, delay) {
  // 维护一个 timer
  let timer = null;
  // 能访问 timer 的闭包
  return function() {
    // 通过 ‘this’ 和 ‘arguments’ 获取函数的作用域和变量
    let context = this;
    let args = arguments;
    // 如果事件被调用,清除 timer 然后重新设置 timer
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    }, delay);
  }
}

这个函数 — 当传入一个事件(fn)时 — 会在经过给定的时间(delay)后执行。

函数这样用:

// 当用户滚动时被调用的函数
function foo() {
  console.log("You are scrolling!");

}

// 在 debounce 中包装我们的函数,过 2 秒触发一次
let elem = document.getElementById("container");
elem.addEventListener("scroll", debounce(foo, 2000));

Throttling 是与 debouncing 类似的一种技术,但它不是在调用函数之前等待一段时间,throttling 是在较长的时间间隔内调用函数。所以如果一个事件每 100 毫秒被触发 10 次,throttling 会在每隔 2 秒时执行一次这个函数,而不是在 100 毫秒内执行 10 次事件。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/82029.html

相关文章

  • 那些前端学习之路疑难杂症(三):数组和Date一些梳理

    摘要:将指定的数字索引值转换成字符串索引值变成,然后将其作为属性名来用。返回一个由删除元素组成的数组。该方法返回的数组元素是调用的数组的一个子集。使用的函数有四个参数初始值积累值数组元素元素索引数组本身。 前言 很多人在学习原生JS的过程中会遇到一些疑惑,比如在学习array时,就很容易搞不清哪些方法会改变原来数组,哪些方法不会改变原来数组?再比如很多人会使用new Date()获取时间,却...

    xeblog 评论0 收藏0
  • 那些前端学习之路疑难杂症(二):delete使用介绍

    摘要:如果你想让一个数组元素的值变为而不是删除它,可以使用给其赋值而不是使用操作符。此时数组元素是在数组中的操作符与直接释放内存只能通过解除引用来间接释放没有关系。 delete 操作符用来删除一个对象的属性 语法EDIT delete expression expression 应该是一个对象的属性引用,例如: delete object.property delete object[p...

    tainzhi 评论0 收藏0
  • 前端开发-从入门到Offer - 收藏集 - 掘金

    摘要:一些知识点有哪些方法方法前端从入门菜鸟到实践老司机所需要的资料与指南合集前端掘金前端从入门菜鸟到实践老司机所需要的资料与指南合集归属于笔者的前端入门与最佳实践。 工欲善其事必先利其器-前端实习简历篇 - 掘金 有幸认识很多在大厂工作的学长,在春招正式开始前为我提供很多内部推荐的机会,非常感谢他们对我的帮助。现在就要去北京了,对第一份正式的实习工作也充满期待,也希望把自己遇到的一些问题和...

    sf_wangchong 评论0 收藏0
  • 深入理解js

    摘要:详解十大常用设计模式力荐深度好文深入理解大设计模式收集各种疑难杂症的问题集锦关于,工作和学习过程中遇到过许多问题,也解答过许多别人的问题。介绍了的内存管理。 延迟加载 (Lazyload) 三种实现方式 延迟加载也称为惰性加载,即在长网页中延迟加载图像。用户滚动到它们之前,视口外的图像不会加载。本文详细介绍了三种延迟加载的实现方式。 详解 Javascript十大常用设计模式 力荐~ ...

    caikeal 评论0 收藏0
  • 那些前端学习之路疑难杂症(一):严格模式与非严格模式

    摘要:反之亦然非严格合并严格看起来是非严格的。在普通的里面给一个拼写错误的变量名赋值会使全局对象新增一个属性并继续工作尽管后面可能出错在现在的中有可能。第三严格模式禁止删除声明变量。 文章整理自MSDN:https://developer.mozilla.org... 1.逐步使用严格模式 ECMAScript 5的严格模式是JavaScript中的一种限制性更强的变种方式。严格模式不是一个...

    zombieda 评论0 收藏0

发表评论

0条评论

gecko23

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<