原文连接javascript
事件委托,也叫事件委派,事件代理。css
当构建应用程序时,有时须要将事件监听器绑定到页面上的某些元素上,以便在用户与元素交互时执行某些操做。html
假设咱们如今有一个无序列表:java
<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>
咱们须要在<li>
上绑定点击事件,咱们可能会这样操做:node
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 。这样代码执行的效率很是低下。面试
更高效的解决方案是将一个事件侦听器实际绑定到父容器<ul>
上,而后在实际单击时能够访问每一个确切元素。这被称为事件委托,而且它比每一个元素单独绑定事件的处理程序更高效。浏览器
那么上面的代码能够改变为:闭包
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); } });
闭包的本质是一个内部函数访问其做用域以外的变量。闭包能够用于实现诸如 私有变量 和 建立工厂函数之类的东西。app
在面试中咱们可能会见到一段这样的代码:函数
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); }
有一些浏览器事件能够在很短的时间内快速启动屡次,例如页面滚动事件。若是将事件侦听器绑定到窗口滚动事件上,而且用户快速滚动页面,事件极可能会在短期屡次触发。这可能会致使一些严重的性能问题。
所以,在侦听滚动,窗口调整大小,或键盘按下的事件时,请务必使用函数防抖动(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毫秒内所有调用。
(完)