我也谈谈闭包
今天第一次在实际运用中遇到闭包的问题。
我要给几个视频video增加鼠标悬停自动播放的功能。
刚开始时,我只有一个video。代码如下:
HTML
<li><video class="work-vidz" src='vids/vader-loader.mp4' loop="true" preload="auto" preload="metadata"></video>jumping-box</li>
JS
var videoNodes = document.getElementsByClassName('work-vidz');
for(var i=0;i<videoNodes.length;i++){
videoNodes[i].addEventListener('mouseover',function(){
videoNodes[i].play();
});
videoNodes[i].addEventListener('mouseout',function(){
videoNodes[i].pause();
});
}
刚开始没思考代码,直接运行,报错:
Uncaught TypeError: Cannot read property ‘play’ of undefined
at HTMLVideoElement. ((index):106)
Uncaught TypeError: Cannot read property ‘pause’ of undefined
at HTMLVideoElement. ((index):109)
我想了一下,怎么能是undefined呢?看到anonymous想起了闭包,应该是i值在for循环完毕后执行了i++,由0变成了1(我这里刚开始只有一个video),鼠标移到视频上时,执行的videoNodes[i].play();就变成了videoNodes[1].play();,所以是undefined。
为了确定是i值的问题,我在F12中设置i=0,鼠标就可以控制视频的播放与暂停了。
找到了病因,我自作聪明,把代码改了改。
for(var i=0;i<videoNodes.length;i++){
videoNodes[i].addEventListener('mouseover',play(i));
videoNodes[i].addEventListener('mouseout',pause(i));
}
function play(i){
videoNodes[i].play();
}
function pause(i){
videoNodes[i].pause();
}
我觉得,新建立两个函数,分别控制播放暂停,并且能接收参数,这样就可以把for循环不同的i值传递给每个video了。但是:
Uncaught (in promise) DOMException: The play() request was interrupted by a call to pause().
错误大概是播放多个视频引起的。
我犯的错误是:因为我想传递参数i,在EventListener中的这种写法play(i),直接就会执行。
如果不传参,只把play和pause函数的引用写在EventListener中,这样就无法区分不同的视频了。
于是再修改一下代码,增加个函数的返回:匿名函数。
for(var i=0;i<videoNodes.length;i++){
videoNodes[i].addEventListener('mouseover',play(i));
videoNodes[i].addEventListener('mouseout',pause(i));
}
function play(i){
return function(){
videoNodes[i].play();
}
}
function pause(i){
return function(){
videoNodes[i].pause();
}
}
测试正常。这个return function就是一个闭包,匿名函数中的i就会顺着作用域链一级级向函数外部查找。
把play()和pause()也修改成匿名函数直接放入EventListener:
var videoNodes = document.getElementsByClassName('work-vidz');
for(var i=0;i<videoNodes.length;i++){
videoNodes[i].addEventListener('mouseover',(function(id){
return function(){
videoNodes[id].play();
}
})(i));
videoNodes[i].addEventListener('mouseout',(function(id){
return function(){
videoNodes[id].pause();
}
})(i));
}
外层的匿名函数其实和之前定义的播放与暂停函数类似,都是传递一个参数,然后执行,内部是返回另一个匿名函数。
这里需要注意的是id不能写成i,不然最终执行时还会顺着作用域链寻找i值,从而找到最外层的for循环中的i值。
通过这个例子,我观察到闭包的核心是变量在作用域链中寻找值的过程。以前之所以迷糊,一是没有尝试过把匿名函数改成非匿名函数,二是对于匿名函数传参的方法不熟悉:
(function(var){
//todo sth.
alert(var);
})();
THE
END
本文介绍了解决使用JavaScript为多个视频元素添加鼠标悬停自动播放功能时遇到的闭包问题。作者通过逐步修改代码解决了因作用域链导致的错误,并详细解释了如何利用闭包正确实现该功能。
1万+

被折叠的 条评论
为什么被折叠?



