Javascript 面试中经常被问到的三个问题!

标签: 前端 程序员 javascript 面试 | 发表时间:2019-02-28 18:30 | 作者:前端小智
出处:https://segmentfault.com/blogs

图片描述

本文不是讨论最新的 JavaScript 库、常见的开发实践或任何新的 ES6 函数。相反,在讨论 JavaScript 时,面试中通常会提到三件事。我自己也被问到这些问题,我的朋友们告诉我他们也被问到这些问题。

然,这些并不是你在面试之前应该学习的唯一三件事 - 你可以通过 多种 方式更好地为即将到来的面试做准备 - 但面试官可能会问到下面是三个问题,来判断你对 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 times = 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: 在循环中使用闭包

闭包常常出现在面试中,以便面试官衡量你对 JS 的熟悉程度,以及你是否知道何时使用闭包。

闭包基本上是内部函数可以访问其范围之外的变量。 闭包可用于实现隐私和创建函数工厂, 闭包常见的面试题如下:

编写一个函数,该函数将遍历整数列表,并在延迟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 的值,该值在循环结束时为 4,因为它循环经过 0,1,2,3,4并且循环最终停止在 4

实际上有 多处方法来正确的解这道题:

  const arr = [10, 12, 15, 21];

for (var i = 0; i < arr.length; 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++) {
  setTimeout(function() {
    console.log('The index of this number is: ' + i);
  }, 3000);
}


问题 3:事件的节流(throttle)与防抖(debounce)

有些浏览器事件可以在短时间内快速触发多次,比如调整窗口大小或向下滚动页面。例如,监听页面窗口滚动事件,并且用户持续快速地向下滚动页面,那么滚动事件可能在 3 秒内触发数千次,这可能会导致一些严重的性能问题。

如果在面试中讨论构建应用程序,出现滚动、窗口大小调整或按下键等事件请务必提及 防抖(Debouncing)函数节流(Throttling)来提升页面速度和性能。这两兄弟的本质都是以 闭包的形式存在。通过对事件对应的回调函数进行包裹、以自由变量的形式缓存时间信息,最后用 setTimeout 来控制事件的触发频率。

Throttle: 第一个人说了算

throttle 的主要思想在于:在某段时间内,不管你触发了多少次回调,都只认第一次,并在计时结束时给予响应。

这个故事里,‘裁判’ 就是我们的节流阀, 他控制参赛者吃东西的时机, “参赛者吃东西”就是我们频繁操作事件而不断涌入的回调任务,它受 “裁判” 的控制,而计时器,就是上文提到的以自由变量形式存在的时间信息,它是 “裁判” 决定是否停止比赛的依据,最后,等待比赛结果就对应到回调函数的执行。

总结下来,所谓的“节流”,是通过在一段时间内无视后来产生的回调请求来实现的。只要 裁判宣布比赛开始,裁判就会开启计时器,在这段时间内,参赛者就尽管不断的吃,谁也无法知道最终结果。

对应到实际的交互上是一样一样的:每当用户触发了一次 scroll 事件,我们就为这个触发操作开启计时器。一段时间内,后续所有的 scroll 事件都会被当作“参赛者吃东西——它们无法触发新的 scroll 回调。直到“一段时间”到了,第一次触发的 scroll 事件对应的回调才会执行,而“一段时间内”触发的后续的 scroll 回调都会被节流阀无视掉。

现在一起实现一个 throttle:

  // fn 是我们需要包装的事件回调, interval 是时间间隔的阈值
function throttle(fn, interval) {
  // last 为上一次触发回调时间
  let last = 0

  // 将 throttle 处理结果当然函数返回
  return function () {
    // 保留调用时的 this 上下文
    let context = this
    // 保留调用时传入的参数
    let args = arguments
    // 记录本次触发回调的时间
    let now = +new Date()

    // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
    if (now - last >= interval) {
      // 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
      last = fn.apply(context, args)
    }
  }
}

// 用 throttle来包装 scroll 的回调
const better_scroll = throttle(() => { console.log('触发了滚动事件')}, 1000)
document.addEventListener('scroll', better_scroll)

Debounce: 最后一个参赛者说了算

防抖的主要思想在于:我会等你到底。在某段时间内,不管你触发了多少次回调,我都只认最后一次。

继续大胃王比赛故事,这次换了一种比赛方式,时间不限,参赛者吃到不能吃为止,当每个参赛都吃不下的时候,后面10分钟如果没有人在吃,比赛结束,如果有人在10分钟内还能吃,则比赛继续,直到下一次10分钟内无人在吃时为止。

对比 throttle 来理解 debounce: 在 throttle 的逻辑里, ‘裁判’ 说了算,当比赛时间到时,就执行回调函数。而 debounce 认为最后一个参赛者说了算,只要还能吃的,就重新设定新的定时器。

现在一起实现一个 debounce:

  // fn 是我们需要包装事件回调,delay 是每次推迟执行的等待时间
function debounce(fn, delay) {
  // 定时器
  let timer = null

  // 将 debounce 处理结果当作函数返回
  return function () {
    // 保留调用时的 this 上下文
    let context = this
    // 保留调用时传入的参数
    let args = arguments

    // 每次事件被触发时,都去清除之前的旧定时器
    if (timer) {
      clearTimeout(timer)
    }
    // 设立新定时器
    timer = setTimeout(function() {
      fn.apply(context, args)
    }, delay)
  }
}

// 用 debounce 来包装 scroll 的回调
const better_scroll = debounce(() => { console.log('发了滚动事件')}, 1000)
document.addEventListener('scroll', better_scroll)

用 Throttle 来优化 Debounce

debounce 的问题在于它“太有耐心了”。试想,如果用户的操作十分频繁——他每次都不等 debounce 设置的 delay 时间结束就进行下一次操作,于是每次 debounce 都为该用户重新生成定时器,回调函数被延迟了不计其数次。频繁的延迟会导致用户迟迟得不到响应,用户同样会产生“这个页面卡死了”的观感。

为了避免弄巧成拙,我们需要借力 throttle 的思想,打造一个“有底线”的 debounce——等你可以,但我有我的原则:delay 时间内,我可以为你重新生成定时器;但只要delay的时间到了,我必须要给用户一个响应。这个 throttle 与 debounce “合体”思路,已经被很多成熟的前端库应用到了它们的加强版 throttle 函数的实现中:

  // fn是我们需要包装的事件回调, delay是时间间隔的阈值
function throttle(fn, delay) {
  // last为上一次触发回调的时间, timer是定时器
  let last = 0, timer = null
  // 将throttle处理结果当作函数返回
  
  return function () { 
    // 保留调用时的this上下文
    let context = this
    // 保留调用时传入的参数
    let args = arguments
    // 记录本次触发回调的时间
    let now = +new Date()
    
    // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
    if (now - last < delay) {
    // 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
       clearTimeout(timer)
       timer = setTimeout(function () {
          last = now
          fn.apply(context, args)
        }, delay)
    } else {
        // 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
        last = now
        fn.apply(context, args)
    }
  }
}

// 用新的throttle包装scroll的回调
const better_scroll = throttle(() => console.log('触发了滚动事件'), 1000)

document.addEventListener('scroll', better_scroll)






参考:

Throttling and Debouncing in JavaScript
The Difference Between Throttling and Debouncing
Examples of Throttling and Debouncing
Remy Sharp’s blog post on Throttling function calls
前端性能优化原理与实践

原文:

https://medium.freecodecamp.o...

你的点赞是我持续分享好东西的动力,欢迎点赞!

一个笨笨的码农,我的世界只能终身学习!

更多内容请关注公众号 《大迁世界》

相关 [javascript 面试 问题] 推荐:

Javascript 面试中经常被问到的三个问题!

- - SegmentFault 最新的文章
本文不是讨论最新的 JavaScript 库、常见的开发实践或任何新的 ES6 函数. 相反,在讨论 JavaScript 时,面试中通常会提到三件事. 我自己也被问到这些问题,我的朋友们告诉我他们也被问到这些问题. 然,这些并不是你在面试之前应该学习的唯一三件事 - 你可以通过 多种 方式更好地为即将到来的面试做准备 - 但面试官可能会问到下面是三个问题,来判断你对 JavaScript 语言的理解和 DOM 的掌握程度.

再谈JavaScript的数据类型问题

- 茄 - aimingoo的专栏
 JavaScript的数据类型问题已经讨论过很多次了,但许多人还有许多书仍然沿用着错误的、混乱的一些观点,所以就再细讲一回. 提及这个讨论的原因在于argb同学在我的MSN博客(现在变成了wordproess,在这里)上的一段回复,又更早的起源则是两年前关于《JavaScript征途》一书的大讨论:.

详图实证:再谈JavaScript的语源问题

- blankyao - aimingoo的专栏
  【本文发表于《程序员》2011.03期】. 有两个错误的观点,其一是“JavaScript在语源上继承自Cmm”. 这个错误的观点主要的来自于以下途径(部分):. 2002年10月7日的《Wired Magazine(连线杂志)》的一份名为“Mother Tongues”的图;. O’Reilly公布的“The History of Programming Languages图;.

招聘 JavaScript 程序员时应该问什么问题

- rex - 一名开发
有使用过服务端 JavaScript 框架吗?. ECMAScript 和 JavaScript 的区别是什么?. 有用过 JavaScript 代码校验工具吗?. 有读过或推荐的 JavaScirpt 书籍吗?. 会为你的 JavaScript 代码写单元测试吗?. 为什么基本上所有对象都有 toString 方法?.

JAVASCRIPT 浏览器兼容性问题及解决方案列表

- - ITeye博客
原文链接 http://www.javaarch.net/jiagoushi/611.htm. 如果需要传递参数,可以使用frame或者iframe. 兼容所有: 在声明变量时,一律加上 var ,以避免歧义,这样在 IE 中亦可正常运行. 此外,最好不要取与 HTML 对象 id 相同的变量名,以减少错误.

你有必要知道的 25 个 JavaScript 面试题

- - 淡忘~浅思
你有必要知道的 25 个 JavaScript 面试题. typeof bar === "object" 判断. bar 是不是一个对象有神马潜在的弊端. 使用 typeof 的弊端是显而易见的(这种弊端同使用 instanceof):. let obj = {}; let arr = []; console.log(typeof obj === 'object'); //true console.log(typeof arr === 'object'); //true console.log(typeof null === 'object'); //true.

面试题:火车运煤问题

- Louis - 酷壳 - CoolShell.cn
这个可能是一个比较经典的智力题了,和以前的那个《赛马问题》很相似,其题目如下:. 你是山西的一个煤老板,你在矿区开采了有3000吨煤需要运送到市场上去卖,从你的矿区到市场有1000公里,你手里有一列烧煤的火车,这个火车最多只能装1000吨煤,且其能耗比较大——每一公里需要耗一吨煤. 请问,作为一个懂编程的煤老板的你,你会怎么运送才能运最多的煤到集市.

技术面试中,什么样的问题才是好问题?

- - 四火的唠叨
其实很久以前就想谈一谈这个话题了,但是最近才有了足够的动机. 因为从最近参加的很多 debrief 来看,我认为身边大多数的软件工程师面试中,在通过技术问题来考察候选人这方面,很多都做得不够好. 比方说,我看到对于一些经验丰富的软件工程师候选人的面试,一些面试官依然是草率地扔出一道算法题让做了事,并且认为能不能够比较清晰完整地将代码写出来,是工程师级别裁定的最重要的标准.

面试时,你会问面试官哪些问题?

- Walter8529 - 博客园-首页原创精华区
每次面试的时候,面试官都会在最后给面试者一些时间,来问问题. 这是个非常好的机会,能按照自己的思路,来了解职位、技术、企业文化、福利待遇、企业状况和前景等情况,以弥补前面面试过程中没有了解到的情况. 但较早以前面试准备不太充分,虽然也能地问上一些问题,但挂一漏万,每次回来后,总觉得对企业、对职位没有完全了解清楚,不能依此作出很理性的决断.

机器学习及大数据相关面试的职责和面试问题

- - IT瘾-bigdata
· 机器学习、大数据相关岗位的职责. 各个企业对这类岗位的命名可能有所不同,比如推荐算法/数据挖掘/自然语言处理/机器学习算法工程师,或简称算法工程师,还有的称为搜索/推荐算法工程师,甚至有的并入后台工程师的范畴,视岗位具体要求而定. 机器学习、大数据相关岗位的职责. 根据业务的不同,岗位职责大概分为:.