百度地图js marker 点击窗口信息始终都是最后一次问题
作者:smice分类:Java
日期:2021-09-14 20:55:002021-09-14阅读:166

这几天有个需求,要在将某事件自定义标识且事件分布要在地图上显示出来,还要点击标志后显示具体信息。乍一看很简单,实则踩了百度地图的巨坑。
一般会这么写:

for (var p in datas) {

    var positions = datas[p].q25;//获取事件经纬度
    if (positions == ("")) {//跳过没包含经纬度
       return;
        }
     var position_x = positions.split(",")[0]//经度
     var position_y = positions.split(",")[1]//纬度
     var point = new BMapGL.Point(position_x, position_y);
     map.centerAndZoom(point, 15);
     if (datas[p].q20 == "B") {//区分不同严重等级
     icon_marker = bdicon2;
     } else if (datas[p].q20 == "C") {
        icon_marker = bdicon3;
      } else
        icon_marker = bdicon1;

      markers[p]= new BMapGL.Marker(point, {icon: icon_marker});// 创建点标记
   
      map.addOverlay(markers[p]);// 在地图上添加点标记
         // 创建信息窗口
          var opts = {
          width: 200,
          height: 100,
           title: datas[p].q1//户主
          };
        infos[p]= new BMapGL.InfoWindow(datas[p].q6 + "<br>" + datas[p].q7 + "<br>" + datas[p].q13 + "<br>" + datas[p].q15, opts);
        // 点标记添加点击事件
                
        markers[x].addEventListener('click', function () {
        markers[x].openInfoWindow(infos[x]);
                    });}

看似没有问题,一运行,窗口只会显示在最后一个添加的marker上,以为是自己问题,改了好久都不行。
百度了一看,这问题还挺多的,大致解决方案都是添加闭包,但都没说明原因。
写法如下:

for (var p in datas) {

    var positions = datas[p].q25;//获取事件经纬度
    if (positions == ("")) {//跳过没包含经纬度
       return;
        }
     var position_x = positions.split(",")[0]//经度
     var position_y = positions.split(",")[1]//纬度
     var point = new BMapGL.Point(position_x, position_y);
     map.centerAndZoom(point, 15);
     if (datas[p].q20 == "B") {//区分不同严重等级
     icon_marker = bdicon2;
     } else if (datas[p].q20 == "C") {
        icon_marker = bdicon3;
      } else
        icon_marker = bdicon1;

      markers[p]= new BMapGL.Marker(point, {icon: icon_marker});// 创建点标记
   
      map.addOverlay(markers[p]);// 在地图上添加点标记
         // 创建信息窗口
          var opts = {
          width: 200,
          height: 100,
           title: datas[p].q1//户主
          };
        infos[p]= new BMapGL.InfoWindow(datas[p].q6 + "<br>" + datas[p].q7 + "<br>" + datas[p].q13 + "<br>" + datas[p].q15, opts);
        // 点标记添加点击事件
        (function (x) {        

        markers[x].addEventListener('click', function () {
        markers[x].openInfoWindow(infos[x]);
                    });

    })(p);

}

个人猜测是作用域和js线程的问题,众所周知js是单线程的。而百度地图的openInfoWindow是延迟方法,不使用数组的、闭包的话for循环已经执行完,值也被覆盖,而百度地图的openInfoWindow才执行,所以获取到的都是最后一次遍历的值。
这个bug跟一个很经典的js面试题很相似:

forvar i=0;i<5;i++){
 setTimeout(function () {

console.log(i)}}

实际输出为五个5

为什么是五个5呢?因为js是单线程的,定时任务会放到一边等待执行,而for循环立即执行,同时因为i是var定义的,栈中维护的是同一个变量,等定时任务执行时,for循环已经执行完,i变为了5。
解决方法有两种:
第一种是将var改为let,因为let是块变量,只在块({}中)中生效,且有暂死性(不能重复定义),所以相当于console.log(i)中的i是5个不相同的对象。
第二种是添加闭包,如下:

for (var i=0;i<5;i++) {
    (function (x) {


    setTimeout(function () {
        console.log(x)

    },500)
    })(i);
}

此时输出入下:

因为闭包会保存自己的变量,不会被垃圾回收器回收,这也是闭包用多了会对性能有影响的原因。

弹幕评论