我也谈谈闭包
今天第一次在实际运用中遇到闭包的问题。
我要给几个视频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