mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
fix: avoid creating functions on each handleEvent() call
This commit is contained in:
@@ -231,6 +231,26 @@ export class DOMEvent implements Event {
|
|||||||
this.propagationState = EventPropagationState.stop;
|
this.propagationState = EventPropagationState.stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// During handleEvent(), we want to work on a copy of the listeners array,
|
||||||
|
// as any callback could modify the original array during the loop.
|
||||||
|
//
|
||||||
|
// However, cloning arrays is expensive on this hot path, so we'll do it
|
||||||
|
// lazily - i.e. only take a clone if a mutation is about to happen.
|
||||||
|
// This optimisation is particularly worth doing as it's very rare that
|
||||||
|
// an event listener callback will end up modifying the listeners array.
|
||||||
|
private listenersLive: MutationSensitiveArray<ListenerEntry> = emptyArray;
|
||||||
|
private listenersLazyCopy: ListenerEntry[] = emptyArray;
|
||||||
|
|
||||||
|
// Creating this upon class construction as an arrow function rather than as
|
||||||
|
// an inline function bound afresh on each usage saves about 210 nanoseconds
|
||||||
|
// per run of handleEvent().
|
||||||
|
private onCurrentListenersMutation = () => {
|
||||||
|
// Cloning the array via spread syntax is up to 180 nanoseconds
|
||||||
|
// faster per run than using Array.prototype.slice().
|
||||||
|
this.listenersLazyCopy = [...this.listenersLive];
|
||||||
|
this.listenersLive.onMutation = null;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches a synthetic event event to target and returns true if either
|
* Dispatches a synthetic event event to target and returns true if either
|
||||||
* event's cancelable attribute value is false or its preventDefault()
|
* event's cancelable attribute value is false or its preventDefault()
|
||||||
@@ -257,6 +277,8 @@ export class DOMEvent implements Event {
|
|||||||
this.target = null;
|
this.target = null;
|
||||||
this.eventPhase = this.NONE;
|
this.eventPhase = this.NONE;
|
||||||
this.propagationState = EventPropagationState.resume;
|
this.propagationState = EventPropagationState.resume;
|
||||||
|
this.listenersLive = emptyArray;
|
||||||
|
this.listenersLazyCopy = emptyArray;
|
||||||
};
|
};
|
||||||
|
|
||||||
// `Observable.removeEventListener` would likely suffice, but grabbing
|
// `Observable.removeEventListener` would likely suffice, but grabbing
|
||||||
@@ -282,10 +304,10 @@ export class DOMEvent implements Event {
|
|||||||
// event. This keeps behaviour as consistent with DOM Events as
|
// event. This keeps behaviour as consistent with DOM Events as
|
||||||
// possible.
|
// possible.
|
||||||
|
|
||||||
|
this.listenersLazyCopy = this.listenersLive = getGlobalEventHandlersPreHandling?.() || emptyArray;
|
||||||
this.handleEvent({
|
this.handleEvent({
|
||||||
data,
|
data,
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
getListenersForType: () => getGlobalEventHandlersPreHandling?.() ?? emptyArray,
|
|
||||||
removeEventListener: removeGlobalEventListener,
|
removeEventListener: removeGlobalEventListener,
|
||||||
phase: this.CAPTURING_PHASE,
|
phase: this.CAPTURING_PHASE,
|
||||||
});
|
});
|
||||||
@@ -297,13 +319,14 @@ export class DOMEvent implements Event {
|
|||||||
this.currentTarget = currentTarget;
|
this.currentTarget = currentTarget;
|
||||||
this.eventPhase = this.target === this.currentTarget ? this.AT_TARGET : this.CAPTURING_PHASE;
|
this.eventPhase = this.target === this.currentTarget ? this.AT_TARGET : this.CAPTURING_PHASE;
|
||||||
|
|
||||||
|
this.listenersLazyCopy = this.listenersLive = currentTarget.getEventList(this.type) || emptyArray;
|
||||||
this.handleEvent({
|
this.handleEvent({
|
||||||
data,
|
data,
|
||||||
isGlobal: false,
|
isGlobal: false,
|
||||||
getListenersForType: () => currentTarget.getEventList(this.type) ?? emptyArray,
|
|
||||||
removeEventListener: currentTarget.removeEventListener.bind(currentTarget) as Observable['removeEventListener'],
|
removeEventListener: currentTarget.removeEventListener.bind(currentTarget) as Observable['removeEventListener'],
|
||||||
phase: this.CAPTURING_PHASE,
|
phase: this.CAPTURING_PHASE,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.propagationState !== EventPropagationState.resume) {
|
if (this.propagationState !== EventPropagationState.resume) {
|
||||||
reset();
|
reset();
|
||||||
return this.returnValue;
|
return this.returnValue;
|
||||||
@@ -316,13 +339,14 @@ export class DOMEvent implements Event {
|
|||||||
const currentTarget = eventPath[i];
|
const currentTarget = eventPath[i];
|
||||||
this.eventPhase = this.target === this.currentTarget ? this.AT_TARGET : this.BUBBLING_PHASE;
|
this.eventPhase = this.target === this.currentTarget ? this.AT_TARGET : this.BUBBLING_PHASE;
|
||||||
|
|
||||||
|
this.listenersLazyCopy = this.listenersLive = currentTarget.getEventList(this.type) || emptyArray;
|
||||||
this.handleEvent({
|
this.handleEvent({
|
||||||
data,
|
data,
|
||||||
isGlobal: false,
|
isGlobal: false,
|
||||||
getListenersForType: () => currentTarget.getEventList(this.type) ?? emptyArray,
|
|
||||||
removeEventListener: currentTarget.removeEventListener.bind(currentTarget) as Observable['removeEventListener'],
|
removeEventListener: currentTarget.removeEventListener.bind(currentTarget) as Observable['removeEventListener'],
|
||||||
phase: this.BUBBLING_PHASE,
|
phase: this.BUBBLING_PHASE,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.propagationState !== EventPropagationState.resume) {
|
if (this.propagationState !== EventPropagationState.resume) {
|
||||||
reset();
|
reset();
|
||||||
return this.returnValue;
|
return this.returnValue;
|
||||||
@@ -341,10 +365,10 @@ export class DOMEvent implements Event {
|
|||||||
this.eventPhase = this.BUBBLING_PHASE;
|
this.eventPhase = this.BUBBLING_PHASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.listenersLazyCopy = this.listenersLive = getGlobalEventHandlersPostHandling?.() || emptyArray;
|
||||||
this.handleEvent({
|
this.handleEvent({
|
||||||
data,
|
data,
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
getListenersForType: () => getGlobalEventHandlersPostHandling?.() ?? emptyArray,
|
|
||||||
removeEventListener: removeGlobalEventListener,
|
removeEventListener: removeGlobalEventListener,
|
||||||
phase: this.BUBBLING_PHASE,
|
phase: this.BUBBLING_PHASE,
|
||||||
});
|
});
|
||||||
@@ -353,32 +377,12 @@ export class DOMEvent implements Event {
|
|||||||
return this.returnValue;
|
return this.returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleEvent({ data, isGlobal, getListenersForType, phase, removeEventListener }: { data: EventData; isGlobal: boolean; getListenersForType: () => MutationSensitiveArray<ListenerEntry>; phase: 0 | 1 | 2 | 3; removeEventListener: (eventName: string, callback?: any, thisArg?: any, capture?: boolean) => void }) {
|
private handleEvent({ data, isGlobal, phase, removeEventListener }: { data: EventData; isGlobal: boolean; phase: 0 | 1 | 2 | 3; removeEventListener: (eventName: string, callback?: any, thisArg?: any, capture?: boolean) => void }) {
|
||||||
// We want to work on a copy of the array, as any callback could modify
|
|
||||||
// the original array during the loop.
|
|
||||||
//
|
|
||||||
// However, cloning arrays is expensive on this hot path, so we'll do it
|
|
||||||
// lazily - i.e. only take a clone if a mutation is about to happen.
|
|
||||||
// This optimisation is particularly worth doing as it's very rare that
|
|
||||||
// an event listener callback will end up modifying the listeners array.
|
|
||||||
const listenersLive: MutationSensitiveArray<ListenerEntry> = getListenersForType();
|
|
||||||
|
|
||||||
// Set a listener to clone the array just before any mutations.
|
// Set a listener to clone the array just before any mutations.
|
||||||
let listenersLazyCopy: ListenerEntry[] = listenersLive;
|
this.listenersLive.onMutation = this.onCurrentListenersMutation;
|
||||||
listenersLive.onMutation = () => (mutation: string, payload?: unknown) => {
|
|
||||||
console.log(`handleEvent "${data.eventName}": doLazyCopy due to "${mutation}"`, payload);
|
|
||||||
// Cloning the array via spread syntax is up to 180 nanoseconds
|
|
||||||
// faster per run than using Array.prototype.slice().
|
|
||||||
listenersLazyCopy = [...listenersLive];
|
|
||||||
listenersLive.onMutation = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Make sure we clear the callback before we exit the function,
|
for (let i = this.listenersLazyCopy.length - 1; i >= 0; i--) {
|
||||||
// otherwise we may wastefully clone the array on future mutations.
|
const listener = this.listenersLazyCopy[i];
|
||||||
const cleanup = () => (listenersLive.onMutation = null);
|
|
||||||
|
|
||||||
for (let i = listenersLazyCopy.length - 1; i >= 0; i--) {
|
|
||||||
const listener = listenersLazyCopy[i];
|
|
||||||
|
|
||||||
// Assigning variables this old-fashioned way is up to 50
|
// Assigning variables this old-fashioned way is up to 50
|
||||||
// nanoseconds faster per run than ESM destructuring syntax.
|
// nanoseconds faster per run than ESM destructuring syntax.
|
||||||
@@ -394,7 +398,7 @@ export class DOMEvent implements Event {
|
|||||||
// We simply use a strict equality check here because we trust that
|
// We simply use a strict equality check here because we trust that
|
||||||
// the listeners provider will never allow two deeply-equal
|
// the listeners provider will never allow two deeply-equal
|
||||||
// listeners into the array.
|
// listeners into the array.
|
||||||
if (!listenersLive.includes(listener)) {
|
if (!this.listenersLive.includes(listener)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,12 +428,13 @@ export class DOMEvent implements Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.propagationState === EventPropagationState.stopImmediate) {
|
if (this.propagationState === EventPropagationState.stopImmediate) {
|
||||||
cleanup();
|
break;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup();
|
// Make sure we clear the callback before we exit the function,
|
||||||
|
// otherwise we may wastefully clone the array on future mutations.
|
||||||
|
this.listenersLive.onMutation = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user