协助理解函数回调

时间:2014-06-10 23:57:16

标签: javascript javascript-events

在另一个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;
}

3 个答案:

答案 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包装在函数工厂的另一层中会增加复杂性?答案是循环问题中的经典闭包。变量rcicell被捕获为循环中的闭包,这是一个问题,因为它会使所有单元格共享相同的变量。在javascript中打破闭包的唯一方法是将引用作为函数参数传递。因此附加功能工厂可以打破关闭,从而确保每个单元都使用正确的变量值进行操作。