diff --git a/dev/lib/index.js b/dev/lib/index.js index 51b7144..fa07098 100644 --- a/dev/lib/index.js +++ b/dev/lib/index.js @@ -247,16 +247,32 @@ function compiler(options) { index = -1 + // The handler this-binding receives the same fields as `context` plus a + // per-event `sliceSerialize`. Build the merged object as a stable-shape + // literal so V8 keeps a single hidden class for it and can inline the + // construction; the previous Object.assign({sliceSerialize: ...}, context) + // form created the same object but went through a copy-loop and gave V8 + // less to optimize. A fresh object per event preserves the previous + // contract that handlers may capture `this` or reassign top-level fields + // without leaking into subsequent events. while (++index < events.length) { - const handler = config[events[index][0]] - - if (own.call(handler, events[index][1].type)) { - handler[events[index][1].type].call( - Object.assign( - {sliceSerialize: events[index][2].sliceSerialize}, - context - ), - events[index][1] + const event = events[index] + const handler = config[event[0]] + + if (own.call(handler, event[1].type)) { + handler[event[1].type].call( + { + sliceSerialize: event[2].sliceSerialize, + stack: context.stack, + tokenStack: context.tokenStack, + config: context.config, + enter: context.enter, + exit: context.exit, + buffer: context.buffer, + resume: context.resume, + data: context.data + }, + event[1] ) } } diff --git a/dev/lib/types.d.ts b/dev/lib/types.d.ts index 710de76..43ce11f 100644 --- a/dev/lib/types.d.ts +++ b/dev/lib/types.d.ts @@ -3,6 +3,15 @@ import type {ParseOptions, Token} from 'micromark-util-types' /** * Compiler context. + * + * The object passed to handlers as `this` is scoped to a single handler + * call. A fresh object is constructed for every event in the dispatch loop, + * so handlers must not retain a `this` reference between events nor reassign + * its top-level fields and expect the change to leak into subsequent events. + * Cross-event state belongs in `this.data`, which is the shared key/value + * store described below; mutations through `this.stack`, `this.tokenStack`, + * and the `enter`/`exit`/`buffer`/`resume` helpers go to the same shared + * underlying objects and remain visible across events as before. */ export interface CompileContext { /**