阿里云最近在作活动,低至2折,有兴趣能够看看:promotion.aliyun.com/ntms/yunpar…javascript
本文不是讨论最新的 JavaScript 库、常见的开发实践或任何新的 ES6 函数。相反,在讨论 JavaScript 时,面试中一般会提到三件事。我本身也被问到这些问题,个人朋友们告诉我他们也被问到这些问题。css
然,这些并非你在面试以前应该学习的惟一三件事 - 你能够经过多种方式更好地为即将到来的面试作准备 - 但面试官可能会问到下面是三个问题,来判断你对 JavaScript
语言的理解和 DOM 的掌握程度。html
让咱们开始吧!注意,咱们将在下面的示例中使用原生的 JavaScript,由于面试官一般但愿了解你在没有 jQuery 等库的帮助下对JavaScript 和 DOM 的理解程度。前端
在构建应用程序时,有时须要将事件绑定到页面上的按钮、文本或图像,以便在用户与元素交互时执行某些操做。java
若是咱们以一个简单的待办事项列表为例,面试官可能会告诉你,当用户点击列表中的一个列表项时执行某些操做。他们但愿你用 JavaScript 实现这个功能,假设有以下 HTML 代码:node
<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>
复制代码
你可能想要作以下操做来将事件绑定到元素:git
document.addEventListener('DOMContentLoaded', function() {
let app = document.getElementById('todo-app');
let itimes = 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 ,这样代码执行的效率很是低下。github
在面试中,最好先问面试官用户能够输入的最大元素数量是多少。例如,若是它不超过 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)
}
})
})
复制代码
闭包经常出如今面试中,以便面试官衡量你对 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 秒内触发数千次,这可能会致使一些严重的性能问题。
若是在面试中讨论构建应用程序,出现滚动、窗口大小调整或按下键等事件请务必说起 防抖(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 = now;
fn.apply(context, args);
}
}
}
// 用throttle来包装scroll的回调
const better_scroll = throttle(() => console.log('触发了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)
复制代码
防抖的主要思想在于:我会等你到底。在某段时间内,无论你触发了多少次回调,我都只认最后一次。
继续大胃王比赛故事,此次换了一种比赛方式,时间不限,参赛者吃到不能吃为止,当每一个参赛都吃不下的时候,后面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)
复制代码
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)
复制代码
代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug。
参考:
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 前端性能优化原理与实践
阿里云最近在作活动,低至2折,有兴趣能够看看: promotion.aliyun.com/ntms/yunpar…
干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。
我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,便可看到福利,你懂的。
每次整理文章,通常都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励