在另一个StackOverflow问题(Creating a clickable grid in a web browser)中,回答者将此作为示例发布:http://jsfiddle.net/6qkdP/2/。
我在学习JavaScript时尝试做类似的事情,但这个答案提供的问题多于答案。现在,我想我理解返回函数的函数的概念。然而,在jsfiddle链接上发布的JS让我很困惑,这就是为什么......
在第15行,创建了该函数:
function clickableGrid( rows, cols, callback ){...}
看起来很简单:传入行数和列数,然后传递正在调用的函数(对吗?)。在第2行的顶部,它调用该函数,对于回调,它传递一个接受一些东西(el,row,col,i)的函数,并在控制台中提供输出。
var grid = clickableGrid(10,10,function(el,row,col,i){...}
这一切似乎都很容易理解(当然,假设我理解正确!)。但是从第24行开始的这一点让我困惑:
它为正在创建的元素创建一个click事件侦听器。此事件侦听器调用一个接受4个参数的新函数... cell.addEventListener( '点击',(功能(EL,R,C,I){
...返回另一个函数......
return function(){
...这是主函数的回调,传递相同的4个参数(?)...
callback(el,r,c,i);
}
...然后这个位是点击事件期间传递的内容吗?
})(cell,r,c,i),false);
是吗?这一切对我来说似乎过于混乱...例如,我不明白为什么创建了一个接受相同参数的函数,然后在那里改变元素的样式,而不需要所有这些回调?我错过了一些东西,并希望这里经验丰富的人能够提供我不能理解的内容。
谢谢!
代码,以防JSFiddle关闭:
var lastClicked;
var grid = clickableGrid(10,10,function(el,row,col,i){
console.log("You clicked on element:",el);
console.log("You clicked on row:",row);
console.log("You clicked on col:",col);
console.log("You clicked on item #:",i);
el.className='clicked';
if (lastClicked) lastClicked.className='';
lastClicked = el;
});
document.body.appendChild(grid);
function clickableGrid( rows, cols, callback ){
var i=0;
var grid = document.createElement('table');
grid.className = 'grid';
for (var r=0;r<rows;++r){
var tr = grid.appendChild(document.createElement('tr'));
for (var c=0;c<cols;++c){
var cell = tr.appendChild(document.createElement('td'));
cell.innerHTML = ++i;
cell.addEventListener('click',(function(el,r,c,i){
return function(){
callback(el,r,c,i);
}
})(cell,r,c,i),false);
}
}
return grid;
}
答案 0 :(得分:0)
cell.addEventListener('click',(function(el,r,c,i){
return function(){
callback(el,r,c,i);
}
})(cell,r,c,i),false);
Javascript变量是函数作用域。他的代码与编写相同。
function clickableGrid( rows, cols, callback ){
var i,grid,r,c,tr,cell;
...
为了在循环的每次迭代中保留每个变量的值,需要创建另一个函数scope。因此,el,r,c,i通过创建闭包来适当确定范围。
如果他没有编写该代码。所有回调都将具有相同的单元格,r,c,i值,即2个循环的最后一次迭代的值。
尝试,它删除即时调用的函数
cell.addEventListener('click', function(){
callback(cell,r,c,i);
}
,false);
所有点击只会传递回调最后一个单元格,最后一个r,c,i值在所有循环完成后。
就像一个人写的那样。
var x=5;
window.getX=function(){
return x;
}
x = 6;
console.log(getX()) // 6
x getX函数内部将始终引用x的最后一个值。
但如果我写
var x=5;
(function(x){
window.getX=function(){
return x;
}
}(x))
x = 6;
console.log(getX()) // 5
getX中的x指的是即时调用的函数表达式(IIFE)的闭包中的x。
还有更多,相信我它比起初看起来要复杂得多。抓住书“JavascriptAllongé”来全面了解。
答案 1 :(得分:0)
好的,所以这是有趣的部分:
cell.addEventListener('click',
(function(el,r,c,i){
return function(){
callback(el,r,c,i);
}
})(cell,r,c,i),
false);
更天真的实现可能如下所示:
cell.addEventListener('click',
function(){
callback(cell,r,c,i);
},
false);
如果您尝试这样做,它将始终说“您点击了单元格#100”。这是因为变量 cell , r , c < / em>, i 在闭包中被捕获,而不是副本。也就是说,当它们的值在下一个循环过程中发生变化时,用于所有回调的值将会变化
通过将闭包封装在一个函数中来避免这种情况,该函数将所述变量作为参数并立即执行,从而有效地创建新的局部变量。
模式
(function(args){
…
})(stuff);
通常用于JS中来创建一个新的变量范围。
答案 2 :(得分:0)
好的,这部分:
cell.addEventListener('click',(function(el,r,c,i){
return function(){
callback(el,r,c,i);
}
})(cell,r,c,i),false);
基本上是这样做的:
cell.addEventListener('click',eventHandler,false);
因此,无论替换eventHandler的部分如何,它都必须将函数作为事件处理程序返回。因此,为了清楚起见,请重写一下:
var eventHandler = (function(el,r,c,i){
return function(){
callback(el,r,c,i);
}
})(cell,r,c,i);
cell.addEventListener('click',eventHandler,false);
现在,eventHandler位是一个自调用函数。此外,它是一个功能工厂(它返回一个功能)。所以我们称之为eventHandlerFactory。现在为了清楚起见重写它:
function eventHandlerFactory (el,r,c,i) {
return function(){
callback(el,r,c,i);
}
}
var eventHandler = eventHandlerFactory(cell,r,c,i);
cell.addEventListener('click',eventHandler,false);
现在,阅读起来容易得多。 eventHandlerFactory返回一个匿名函数,然后将其分配给eventHandler(暂时忽略该匿名函数的内容)。然后将eventHandlerFactory生成的eventHandler函数分配给单元格的onclick事件。单击单元格时,将执行eventHandler函数。而且eventHandler函数的内容(你现在可以停止忽略它)只是对前面提供的回调函数的调用。
问题是,为什么将eventHandler包装在函数工厂的另一层中会增加复杂性?答案是循环问题中的经典闭包。变量r
,c
,i
和cell
被捕获为循环中的闭包,这是一个问题,因为它会使所有单元格共享相同的变量。在javascript中打破闭包的唯一方法是将引用作为函数参数传递。因此附加功能工厂可以打破关闭,从而确保每个单元都使用正确的变量值进行操作。