Javascript 回调函数和递归

Javascript callback functions and recursion

提问人:Lawrence 提问时间:11/9/2008 最后编辑:Shadow Wizard Is Sad And AngryLawrence 更新时间:5/20/2016 访问量:33107

问:

这是一个脑筋急转弯的问题,因为代码按原样运行良好,它只是稍微刺激了我的审美意识。我正在转向 Stack Overflow,因为我自己的大脑现在正在让我失望。

下面是一段代码,它使用 Google Maps JS API 查找地址并在地图上放置标记。但是,有时初始查找会失败,因此我想使用不同的地址重复该过程。

geocoder.getLatLng(item.mapstring, function(point) {
    if (!point) {
        geocoder.getLatLng(item.backup_mapstring, function(point) {
            if (!point) return;
            map.setCenter(point, 13);
            map.setZoom(7);
            map.addOverlay(new GMarker(point));
        })
        return;
    }
    map.setCenter(point, 13);
    map.setZoom(7);
    map.addOverlay(new GMarker(point));
})

(第二个参数是回调函数。getLatLng

当然,您可以看到居中和缩放地图并添加标记的三条线是重复的,一次在主回调中,一次在“回退回调”(哈哈)中。你能找到一种方法来表达整个事情而没有任何冗余吗?如果你的解决方案适用于任意数量的备份映射字符串,你将获得奖励积分和我的赞美。

JavaScript 递归 回调

评论


答:

1赞 Jay Kominek 11/9/2008 #1

这个怎么样?

function place_point(mapstrings,idx)
{
    if(idx>=mapstrings.length) return;
    geocoder.getLatLng(mapstrings[idx],
                       function(point)
                       {
                           if(!point)
                           {
                               place_point(mapstrings,idx+1);
                               return;
                           }
                           map.setCenter(point, 13);
                           map.setZoom(7);
                           map.addOverlay(new GMarker(point));
                       });
}

任意数量的备份字符串。第一次用 0 作为第二个参数调用它即可。

2赞 Ricky 11/9/2008 #2

是的,将其分解为函数:)

geocoder.getLatLng(item.mapstring, function(point) {
    if (!point) {
        geocoder.getLatLng(item.backup_mapstring, function(point) {
                if (point) {
                    setPoint(point);
                }
        })
        return;
    }

    function setPoint(point) {
        map.setCenter(point, 13);
        map.setZoom(7);
        map.addOverlay(new GMarker(point));
    }

    setPoint(point);
});
8赞 1800 INFORMATION 11/9/2008 #3

有一种非常好的方法可以在不显式支持递归的语言结构中执行递归,称为定点组合器。最著名的是 Y-Combinator

这是 Javascript 中一个参数函数的 Y 组合器

function Y(le, a) {
    return function (f) {
        return f(f);
    }(function (f) {
        return le(function (x) {
            return f(f)(x);
        }, a);
    });
}

这看起来有点吓人,但你只需要写一次。使用它实际上非常简单。基本上,你把一个参数的原始 lambda 变成一个由两个参数组成的新函数 - 第一个参数现在是你可以进行递归调用的实际 lambda 表达式,第二个参数是你想要使用的原始第一个参数 ()。point

这就是您在示例中使用它的方式。请注意,我使用字符串列表进行查找,pop 函数会破坏性地从头部删除元素。mapstrings

geocoder.getLatLng(pop(mapstrings), Y(
  function(getLatLongCallback, point)
  {
    if (!point)
    {
      if (length(mapstrings) > 0)
        geocoder.getLatLng(pop(mapstrings), getLatLongCallback);
      return;
    }

    map.setCenter(point, 13);
    map.setZoom(7);
    map.addOverlay(new GMarker(point));
  });
21赞 tway 11/9/2008 #4

其他答案都很好,但这里还有一个选择。这允许您保留开始时使用的相同形式,但使用命名 lambda 函数的技巧,以便您可以递归引用它:

mapstrings = ['mapstring1', 'mapstring2', 'mapstring3'];

geocoder.getLatLng(mapstrings.shift(), function lambda(point) {
   if(point) {
        // success
        map.setCenter(point, 13);
        map.setZoom(7);
        map.addOverlay(new GMarker(point));
    }
    else if(mapstrings.length > 0) {
        // Previous mapstring failed... try next mapstring
        geocoder.getLatLng(mapstrings.shift(), lambda);
    }
    else {
        // Take special action if no mapstring succeeds?
    }
})

第一次使用符号“lambda”时,是将其作为新的函数文字名称引入。第二次使用它时,它是一个递归引用。

函数文字命名在 Chrome 中有效,我认为它适用于大多数现代浏览器,但我没有测试过它,也不了解较旧的浏览器。

评论

0赞 Jason Bunting 11/9/2008
你不需要文字命名,你可以使用我在解决方案中使用的 - arguments.callee 引用该函数。
4赞 1800 INFORMATION 11/9/2008
不过,文字命名比您的解决方案更干净、更不健谈
1赞 1800 INFORMATION 11/10/2008
如果你看了一段代码,发现自己捂着鼻子,这可能是一个好兆头
2赞 Jay Kominek 11/10/2008
对于没有大量 Javascript 经验的人来说,arguments.callee 的东西将是不明显的。这当然更易于维护。
1赞 1800 INFORMATION 11/10/2008
命名的 lambda 表达式让我觉得“我认为所有语言都应该有这个”。arguments.callee 的事情让我想“多么巧妙的黑客,我可以看到可能需要在某个地方使用它”