在复杂的 web 开发中,我们应采用 DOM 2 级事件来绑定和移除函数。即使用
addEventListener
和 removeEventListener
方法为
DOM 节点绑定和解绑函数。因为 DOM 1
级事件不支持多个函数的绑定。这意味着,当你使用 DOM 1 级为 DOM
绑定新函数时,旧的函数会被取代。这在很多情况下是不被希望看到的。
例如,我们经常会在 document
对象上绑定多个鼠标事件,使用
DOM 2
级事件既不会取消之前的函数,也不用担心对项目组其他成员编写的事件绑定造成影响。
一个问题
一个常见的 DOM 2 级事件绑定和解绑如下:
1 2 3 4 5 6 7 8 9 const fn = ( ) => { console .log ('in function' ); } element.addEventListener ('cilck' , fn); element.removeEventListener ('click' , fn);
如上,由于一个 DOM 2
级事件可以被绑定多个函数,所以当我们解绑函数时,需指明要解绑的函数名。这意味着,如果在
DOM 2
级事件上绑定了一个匿名函数,那么是难以解绑改函数的。所以应尽量避免绑定匿名函数。
但通常,绑定的函数并不是完全孤立的,我们需要传入一些参数。新手常会犯的一个错误就是企图通过以下语句传参:
1 element.addEventListener ('click' , fn (params));
然而,上面的写法会直接执行 fn
函数,而不是等待点击事件的触发时才执行。给 click
绑定的也是
fn(params)
的返回值,而非 fn
函数。
实例
下面是一个例子。该实例实现点击小球时可以拖拽小球,当松开鼠标时停止拖拽。考虑到要在
document
上绑定事件,我们使用 DOM 2 级事件。
通常,实现拖拽效果时,大体思路是:
点击目标对象时,为 document
绑定
mousemove
事件,当鼠标移动时,根据鼠标位置设置目标对象的位置。
当鼠标弹起时,解绑 document
的
mousemove
事件。
这样需要监听目标对象的 mousedown
事件,
document
的 mousemove
和 mouseup
事件。
当鼠标在目标对象上按下时,我们需要计算出此时目标对象的位置,然后将位置信息作为参数,传入
mousemove
事件函数中。这就涉及了 DOM 2
级事件的传参问题。
该例中的实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 const main = ( ) => { const ball = document .getElementById ('ball' ); let isDraging = false ; let dragWithParams; const drag = (params, e ) => { let x = e.clientX - params.offsetLeft ; let y = e.clientY - params.offsetTop ; if (x < 0 ) { x = 0 ; } else if (x > document .documentElement .clientWidth - ball.offsetWidth ) { x = document .documentElement .clientWidth - ball.offsetWidth } if (y < 0 ) { y = 0 ; } else if (y > document .documentElement .clientHeight - ball.offsetHeight ) { y = document .documentElement .clientHeight - ball.offsetHeight ; } ball.style .left = x + 'px' ; ball.style .top = y + 'px' ; } document .addEventListener ('mouseup' , () => { if (isDraging) { console .log ('up' ); document .removeEventListener ('mousemove' , dragWithParams); isDraging = false ; } }) ball.addEventListener ('mousedown' , e => { const offsetLeft = e.clientX - ball.offsetLeft ; const offsetTop = e.clientY - ball.offsetTop ; isDraging = true ; dragWithParams = drag.bind (null , { offsetLeft, offsetTop, }) document .addEventListener ('mousemove' , dragWithParams); }); }document .addEventListener ('DOMContentLoaded' , main);
代码分析
上例中利用了 bind
方法可以绑定参数的特点来为事件函数绑定参数。点击查看
bind
详情 。
同样需要注意的是,代码中为绑定参数后的 drag
函数赋予了新的引用 dragWithParams
。这是因为
bind
方法返回的是原函数的拷贝而非原函数。所以,如果我们不赋予它引用名,该函数就变成了匿名函数,我们依然无法解绑。例如,下面的例子中,是无法解绑的。
1 2 3 4 5 6 7 8 9 10 11 12 const fn = (params ) => { console .log (params); }const params = 'some params' ;document .addEventListener ('mousemove' , fn.bind (null , params));document .remvoeEventListener ('mousemove' , fn);
bind
方法第一个参数指定函数中 this
的指向,其余的参数会被作为绑定的参数传入新的函数,并且会出现在传入参数的前面位置。所以在示例代码
drag
函数中,将 event 对象参数放在了 params
后面接收。