javascriptecmascript-6template-stringsweakmaptagged-templates

Are TemplateObject arrays for tagged template literals weakly referenced by their realm?


while (c) {
  tag`str0 ${e} str1`
}

The JavaScript runtime creates a frozen array like Object.freeze(['str0 ', ' str1']) but with an additional .raw property.

Is it okay to use that object as a key in a WeakMap to avoid having to redo work based on the array each time through the loop?

const memoTable = new WeakMap
function tag(templateStrings, ...values) {
  let cached = memoTable.get(templateStrings)
  if (!cached) {
    // Compute cached and put it in the table for next time.
  }
  // Do something with cached and values
}

Section 12.2.9.3 Runtime Semantics: GetTemplateObject ( templateLiteral ) describes how this value is cached:

  1. Let realm be the current Realm Record.
  2. Let templateRegistry be realm.[[TemplateMap]].

so it should be the same from use to use of tag in the loop above which would be a nice property for a key to have.

It seems to me that the [[TemplateMap]] would have to weakly reference the template object array because otherwise

for (let i = 0; i < 1e6; ++i) {
  eval('(() => {})`' + i + '`');
}

would leak memory.

I don't see anything in the specification but is it the case for widely used JavaScript engines that WeakMap entries for tagged string template uses not in re-enterable scopes will eventually be collected?

I ask because I've implemented something based on this assumption but haven't figured out how to test it.


Solution

  • Is it okay to use that object as a key in a WeakMap to avoid having to redo work based on the array each time through the loop?

    Yep, that's exactly what you're meant to do and it's one of the great features of template literals.

    It seems to me that the [[TemplateMap]] would have to weakly reference the template object array because otherwise

    for (let i = 0; i < 1e6; ++i) {
      eval('(() => {})`' + i + '`');
    }
    

    would leak memory.

    It does leak in fact, since [[TemplateMap]] is not weak. :(

    This is an open point of discussion on the current spec. The discussion as of this writing is whether the spec should be changed to have template literals be be per-source-text-position instead of having the [[TemplateMap]] be global state. e.g. as of this writing:

    var id = v => v;
    id`tpl` === id`tpl` // true
    

    which is a bit weird.

    Is it acceptable to break that such that two separate templates are created? If so, then there's at least the possibility that your eval example could be allowed to collect the template.

    You can see some discussion here, https://github.com/tc39/ecma262/issues/840, where at least tentatively this could be fixed.

    EDIT:

    The spec did indeed change, so templates are now per-source-location instead of per-realm, so two templates with the same content and different locations will be two different objects. This means that engines can potentially garbage-collect template literal object if their source file is no longer reachable.