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翻译,转载请与译者联系。否则将追究法律责任。

最新文章

  1. springmvc 组合注解
  2. JS基础回顾,小练习(判断数组,以及函数)
  3. WP7:模拟开始屏幕Tile漂动效果
  4. hdu 2824 The Euler function(欧拉函数)
  5. C#文件流写入方法
  6. delphi popupmenu控件用法
  7. python_爬校花图片
  8. BZOJ 3669: [Noi2014]魔法森林 [LCT Kruskal | SPFA]
  9. js循环语句
  10. Win8 64位安装Oracle 11g时错
  11. Android:Unable to find explicit activity class
  12. 玩具谜题(NOIP2016)
  13. 不同数据源之间的数据同步jdbc解决方案
  14. SSIS 更新变量
  15. .NET中CORS跨域访问WebApi
  16. mysql状态查看 QPS/TPS/缓存命中率查看【转】
  17. 使用gSOAP工具生成onvif框架代码
  18. Nginx启用ssl以及免费证书申请
  19. 支持向量机通俗导论(SVM学习)
  20. 那些年,我们一起误解过的REST

热门文章

  1. How to solve “Device eth0 does not seem to be present, delaying initialization” error
  2. ZooKeeper Java例子(六)
  3. sublime 常用插件 感觉比较全了 够用了
  4. LightOJ 1013 - Love Calculator LCS
  5. JAVA嵌套类:静态嵌套类和非静态嵌套类
  6. Little Mathematics Knowledge 数学小常识
  7. ASP.NET 简单鼠标右键效果contextmenutrip
  8. apache log 按日期记录 格式 &lt;GOOD&gt;-- (转)
  9. AndroidStudio获得发布版安全码SHA1
  10. Android中自定义属性attr.xml的格式详解