摘要:更高效的解决方案是将一个事件侦听器实际绑定到父容器上,然后在实际单击时可以访问每个确切元素。如果将事件侦听器绑定到窗口滚动事件上,并且用户快速滚动页面,事件很可能会在短时间多次触发。
原文链接
问题 #1: 事件委托事件委托,也叫事件委派,事件代理。
当构建应用程序时,有时需要将事件监听器绑定到页面上的某些元素上,以便在用户与元素交互时执行某些操作。
假设我们现在有一个无序列表:
我们需要在上绑定点击事件,我们可能会这样操作:
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 。这样代码执行的效率非常低下。
更高效的解决方案是将一个事件侦听器实际绑定到父容器上,然后在实际单击时可以访问每个确切元素。这被称为事件委托,并且它比每个元素多带带绑定事件的处理程序更高效。
那么上面的代码可以改变为:
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: 在循环内使用闭包(Closures)
闭包的本质是一个内部函数访问其作用域之外的变量。闭包可以用于实现诸如 私有变量 和 创建工厂函数之类的东西。
在面试中我们可能会见到一段这样的代码:
for (var i = 0; i < 4; i++) { setTimeout(function() { console.log(i); }, 1000); }
运行上面的代码控制台会在1秒后打印4个4,而不是0,1,2,3。
其原因是因为setTimeout函数创建了一个可以访问其外部作用域的函数(也就是我们经常说的闭包),每个循环都包含了索引i。
1秒后,该函数被执行并且打印出i的值,其在循环结束时为4,因为它的循环周期经历了0,1,2,3,4,并且循环最终在4时停止。
下面列举两种方案解决这个问题:
for (var i = 0; i < 4; i++) { // 通过传递变量 i // 在每个函数中都可以获取到正确的索引 setTimeout(function(j) { return function() { console.log(j); } }(i), 1000); }
for (let i = 0; i < 4; i++) { // 使用ES6的let语法,它会创建一个新的绑定 // 每个方法都是被多带带调用的 setTimeout(function() { console.log(i); }, 1000); }问题 #3: 函数防抖(Debouncing)
有一些浏览器事件可以在很短的时间内快速启动多次,例如页面滚动事件。如果将事件侦听器绑定到窗口滚动事件上,并且用户快速滚动页面,事件很可能会在短时间多次触发。这可能会导致一些严重的性能问题。
因此,在侦听滚动,窗口调整大小,或键盘按下的事件时,请务必使用函数防抖动(Debouncing)或函数节流(Throttling)来提升页面速度和性能。
函数防抖(Debouncing)是解决这个问题的一种方式,通过限制需要经过的时间,直到再次调用函数。一个实现函数防抖的方法是:把多个函数放在一个函数里调用,隔一定时间执行一次。
这里有一个使用原生JavaScript实现的例子,用到了作用域、闭包、this和定时事件:
function debounce(fn, delay) { // 持久化一个定时器 timer let timer = null; // 闭包函数可以访问 timer return function() { // 通过 "this" 和 "arguments" 获得函数的作用域和参数 let self = this; let args = arguments; // 如果事件被触发,清除 timer 并重新开始计时 clearTimeout(timer); timer = setTimeout(function() { fn.apply(self, args); }, delay); } } // 当用户滚动时调用函数foo() function foo() { console.log("You are scrolling!"); } // 在事件触发的两秒后,包裹在debounce()中的函数才会被触发 window.addEventListener("scroll", debounce(foo, 2000));
函数节流是另一个类似函数防抖的技巧,除了使用等待一段时间再调用函数的方法,函数节流还限制固定时间内只能调用一次。所以,如果一个事件在100毫秒内发生10次,函数节流会每2秒调用一次函数,而不是100毫秒内全部调用。
(完)
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/88170.html
摘要:相反,在讨论时,面试中通常会提到三件事。而认为最后一个参赛者说了算,只要还能吃的,就重新设定新的定时器。试想,如果用户的操作十分频繁他每次都不等设置的时间结束就进行下一次操作,于是每次都为该用户重新生成定时器,回调函数被延迟了不计其数次。本文不是讨论最新的 JavaScript 库、常见的开发实践或任何新的 ES6 函数。相反,在讨论 JavaScript 时,面试中通常会提到三件事。我自己...
摘要:相反,在讨论时,面试中通常会提到三件事。通过对事件对应的回调函数进行包裹以自由变量的形式缓存时间信息,最后用来控制事件的触发频率。而认为最后一个参赛者说了算,只要还能吃的,就重新设定新的定时器。 showImg(https://segmentfault.com/img/bVboH5x?w=1000&h=750); 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! 本...
摘要:可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。我工作中只用到,对和不怎么熟与的区别相同点都支持指令内置指令和自定义指令都支持过滤器内置过滤器和自定义过滤器都支持双向数据绑定都不支持低端浏览器。 看看面试题,只是为了查漏补缺,看看自己那些方面还不懂。切记不要以为背了面试题,就万事大吉了,最好是理解背后的原理,这样面试的时候才能侃侃而谈。不然,稍微有水平的面试官一看就能看出,是...
摘要:作者今年大三,在春招过程中参加了多家大公司的面试后,拿到了腾讯的前端实习,在这里做一些总结,希望给还未参加过实习面试的同学一些帮助。在之后的面试时就更加从容一些了。 作者今年大三,在春招过程中参加了多家大公司的面试后,拿到了腾讯的前端实习 offer,在这里做一些总结,希望给还未参加过实习面试的同学一些帮助。 一、简历的准备 简历制作是很重要的一个环节,一份好的简历会给面试官留下很不错...
摘要:我觉得了解简历和面试的技巧可以帮助你更好的去学习重要的知识点以及更好地去准备面试以及面试,说实话,我个人觉得这些东西还挺重要的。在本文里,我将介绍我这段时间里更新简历和面试的相关经历。 分享一篇很不错的文章!本文作者曾经写过《Java Web轻量级开发面试教程》和 《Java核心技术及面试指南》这两本书。我觉得了解简历和面试的技巧可以帮助你更好的去学习重要的知识点以及更好地去准备面试以...
阅读 2334·2021-11-16 11:44
阅读 802·2021-09-10 11:16
阅读 2194·2019-08-30 15:54
阅读 909·2019-08-30 15:53
阅读 1853·2019-08-30 13:00
阅读 573·2019-08-29 17:07
阅读 3480·2019-08-29 16:39
阅读 3108·2019-08-29 13:30