Mapbox GL JS,在mousemove事件处理器内部使用setTimeout时,遇到事件对象在延迟执行的异步函数中无法访问到原始的event对象属性event.features

发现问题

在开发webmap时遇到一个需求,即鼠标在Mapbox GL JS创建的地图中移动时不执行mousemove事件,等鼠标停止后执行。那么很容易想到的代码是下面这样的。

let timeoutfn;

function throttleMousemove(e){
	console.log(e.features); // 输出 features
	if (timeoutfn) {
		clearTimeout(timeoutfn);
	}
	timeoutfn = setTimeout(function () {
		console.log(e.features); // 输出 undefined
	}, 50);
}
// 添加mousemove事件
map.on("mousemove", '测试图层', throttleMousemove);

很简单的实现,在setTimeout方法中获取Event对象,然后输入该对象的features属性。

但是,setTimeout中的console.log(e.features)输出的是 undefined,没错,确定是undefined

解决问题

因为业务代码比较复杂,首先想到的是数据在上下文变更了,然后排查上下文业务代码,各种业务逻辑都注释掉,也没发现问题,最后精简代码,只有上面的简单的监听事件,问题还是存在。

难道,Mapbox GL JS可能需要时间来解析鼠标所在位置下的地图特性(features),这些特性可能不是立即可用的?这也不可能啊,明显可以知道setTimeout内部的执行时间比外部要靠后,外部可以获取,内部不可能获取不到。

难道因为数据量大被垃圾回收机制自动给回收了?这个更不可能了,setTimeout 内部可以获取到e对象的,没道理只回收features属性。这个时候考虑是不是e.features被Mapbox GL JS库的开发者销毁了?

难道这是Mapbox GL JS内部机制的问题?Mapbox GL JS可能在事件处理的某些阶段对事件对象进行了修改或清理操作,特别是在处理大量数据交互时,为了性能考虑,可能释放了非必需的资源?这确实也可能导致某些属性在异步回调中不可用。

然后我去扒了Mapbox GL JS的源码,确认了我的想法,确实是Mapbox GL JS内部机制的问题,在事件执行完之后,确实将e.features给回收了。贴一下部分源码:

_createDelegatedListener(type: keyof MapEventType | string, layerId: string, listener: Listener): {
        layer: string;
        listener: Listener;
        delegates: {[type in keyof MapEventType]?: (e: any) => void};
    } {
        if (type === 'mouseenter' || type === 'mouseover') {
            let mousein = false;
            const mousemove = (e) => {
                const features = this.getLayer(layerId) ? this.queryRenderedFeatures(e.point, {layers: [layerId]}) : [];
                if (!features.length) {
                    mousein = false;
                } else if (!mousein) {
                    mousein = true;
                    listener.call(this, new MapMouseEvent(type, this, e.originalEvent, {features}));
                }
            };
            const mouseout = () => {
                mousein = false;
            };
            return {layer: layerId, listener, delegates: {mousemove, mouseout}};
        } else if (type === 'mouseleave' || type === 'mouseout') {
            let mousein = false;
            const mousemove = (e) => {
                const features = this.getLayer(layerId) ? this.queryRenderedFeatures(e.point, {layers: [layerId]}) : [];
                if (features.length) {
                    mousein = true;
                } else if (mousein) {
                    mousein = false;
                    listener.call(this, new MapMouseEvent(type, this, e.originalEvent));
                }
            };
            const mouseout = (e) => {
                if (mousein) {
                    mousein = false;
                    listener.call(this, new MapMouseEvent(type, this, e.originalEvent));
                }
            };
            return {layer: layerId, listener, delegates: {mousemove, mouseout}};
        } else {
            const delegate = (e) => {
                const features = this.getLayer(layerId) ? this.queryRenderedFeatures(e.point, {layers: [layerId]}) : [];
                if (features.length) {
                    // Here we need to mutate the original event, so that preventDefault works as expected.
                    e.features = features;
                    listener.call(this, e);
                    delete e.features;
                }
            };
            return {layer: layerId, listener, delegates: {[type]: delegate}};
        }
    }

看源码,作者的的确确通过delete e.features删除了features属性,所以在setTimeout方法中获取不到features。作者删除可以,我再次赋值也可以吧,你做初一我做十五,解决代码如下:

let timeoutfn;

function throttleMousemove(e){
	console.log(e.features); // 输出 features
	const featuresSnapshot = e.features.slice(); 
	if (timeoutfn) {
		clearTimeout(timeoutfn);
	}
	timeoutfn = setTimeout(function () {
		e.features = featuresSnapshot;
		console.log(e.features); // 输出 features
	}, 50);
}
// 添加mousemove事件
map.on("mousemove", '测试图层', throttleMousemove);

至此,问题解决。下面是吐槽时间。

Here we need to mutate the original event, so that preventDefault works as expected.

你说为了preventDefault按预期工作需要更改原始事件。好,你事件传递完了,有必要将e.features delete掉吗?啊,你用完了,不给别人用是吧。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值