Dangus Test

JavaScript performance comparison

Test case created by Duongasaur

Preparation code

<script src="https://cdn.example.com/library.js"></script>

      
<script>
Benchmark.prototype.setup = function() {
  function buildAttributes (prefix) {
    return {
      [`attribute--${prefix}-a`]: 'value-a',
      [`attribute--${prefix}-b`]: {toString () { return 'value-b' }},
      [`attribute--${prefix}-c`]: 0,
      [`attribute--${prefix}-d`]: 111,
      [`attribute--${prefix}-e`]: 1.11,
      [`attribute--${prefix}-f`]: true,
      [`attribute--${prefix}-g`]: false,
      [`attribute--${prefix}-h`]: null,
      [`attribute--${prefix}-i`]: undefined,
    }
  }
  
  let previous
  let children = []
  
  for (let i = 0; i < 8; ++i) {
    previous = children
    children = []
  
    for (let j = 0; j < i; ++j) {
      children.push({
        tag: `tag-${j}`,
        children: previous,
      })
    }
  }
  
  let deepNested = []
  
  for (let i = 0; i < 1000; ++i) {
    previous = deepNested
  
    deepNested = [
      {
        tag: `tag-${i}`,
        children: previous,
      },
    ]
  }
  
  const tag = {
    tag: 'tag-0',
    attributes: {
      ...buildAttributes('a'),
      ...buildAttributes('b'),
      ...buildAttributes('c'),
      ...buildAttributes('d'),
      ...buildAttributes('e'),
      ...buildAttributes('f'),
      ...buildAttributes('g'),
      ...buildAttributes('h'),
      ...buildAttributes('i'),
    },
    children: [
      ...children,
      ...deepNested,
    ],
  }

};
</script>

Preparation code output

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in CCBot 2.0.0 / Other 0.0.0
Test Ops/sec
Erin
function renderTag (definition) {
  const result = [definition]
  const toRender = [result]

  for (let i = 0; i < toRender.length; ++i) {
    const list = toRender[i]

    for (let j = 0; j < list.length; ++j) {
      const entry = list[j]

      if (!entry || typeof entry !== 'object') continue

      const {
        attributes = {},
        children = [],
        isSelfClosing = false,
        tag,
      } = list[j]

      if (!tag) throw new Error('Missing tag name')
      if (isSelfClosing && children.length > 0) throw new Error('Self-closing tags cannot have children')

      const copy = {
        attributes,
        children: children.slice(),
        isSelfClosing,
        tag,
      }

      list[j] = copy
      if (children.length > 0) toRender.push(copy.children)
    }
  }

  for (let i = toRender.length - 1; i >= 0; --i) {
    const list = toRender[i]

    for (let j = 0; j < list.length; ++j) {
      const entry = list[j]
      const type = typeof entry

      if (!entry && type !== 'number') {
        list[j] = ''

        continue
      }

      if (type !== 'object') continue

      const {attributes, children, isSelfClosing, tag} = entry

      let renderedAttributes = ''

      for (const name in attributes) {
        const value = attributes[name]

        if (value === true) {
          renderedAttributes += ` ${name}`
        } else if (value || typeof value === 'number') {
          renderedAttributes += ` ${name}="${value}"`
        }
      }

      const openingTag = `<${tag}${renderedAttributes}>`
      const closingTag = isSelfClosing ? '' : `</${tag}>`

      list[j] = openingTag + children.join('') + closingTag
    }
  }

  return result[0]
}

console.log('ERIN   1', renderTag(tag).length)
pending…
Daniel Best
const formatAttr = ([key, val]) =>
  val === true ? ` ${key}` : val || val === 0 ? ` ${key}="${val}"` : ``;

const tagToString = data => {
  if (typeof data !== "object") return data;
  if (!data.tag) throw Error("Missing tag name");

  const closing = data.children || data.isSelfClosing ? "" : `</${data.tag}>`;

  if (!data.attributes) return `<${data.tag}>${closing}`;

  const attr = Object.entries(data.attributes)
    .map(formatAttr)
    .join("");

  return `<${data.tag}${attr}>${closing}`;
};

const renderTag = data => {
  let stack = [data];
  let tree = [];

  while (stack.length) {
    const next = stack.pop();
    if (!next && next !== 0) continue;
    tree.push(next);

    if (next.children && next.children.constructor === Array) {
      if (next.isSelfClosing) {
        throw Error("Self-closing tags cannot have children");
      }
      stack.push(`</${next.tag}>`, ...next.children.reverse());
    }
  }

  return tree.map(tagToString).join("");
};

console.log('DANIEL !', renderTag(tag).length)
pending…
Daniel Competitor
const formatAttr = ([key, val]) =>
  val === true ? ` ${key}` : val || val === 0 ? ` ${key}="${val}"` : ``;

const tags = data => {
  if (!data.tag) throw Error("Missing tag name");

  const closing = data.children || data.isSelfClosing ? "" : `</${data.tag}>`;

  if (!data.attributes) return `<${data.tag}>${closing}`;

  const attr = Object.entries(data.attributes)
    .map(formatAttr)
    .join("");

  return `<${data.tag}${attr}>${closing}`;
};

const stringReducer = (acc, val) => acc + val;

const mapConditional = fn => reducer => (acc, val) =>
  typeof data !== "object" ? reducer(acc, val) : reducer(acc, fn(val));

const transduce = (transformer, reducer, seed, iterable) => {
  const transformedReducer = transformer(reducer);
  let accumulation = seed;

  while (iterable.length) {
    const next = iterable.pop();
    if (!next && next !== 0) continue;
    accumulation = transformedReducer(accumulation, next);

    if (next.children && next.children.constructor === Array) {
      if (next.isSelfClosing) {
        throw Error("Self-closing tags cannot have children");
      }
      iterable.push(`</${next.tag}>`, ...next.children.reverse());
    }
    continue;
  }

  return accumulation;
};

const renderTag = data =>
  transduce(mapConditional(tags), stringReducer, "", [data]);
console.log('DANIEL 4', renderTag(tag).length)
pending…
dangus stack
const formatAttr = ([key, val]) =>
  val === true ? ` ${key}` : val || val === 0 ? ` ${key}="${val}"` : ``;

const tags = data => {
  if (typeof data !== "object") return data;
  if (!data.tag) throw Error("Missing tag name");

  const closing = data.children || data.isSelfClosing ? "" : `</${data.tag}>`;

  if (!data.attributes) return `<${data.tag}>${closing}`;

  const attr = Object.entries(data.attributes)
    .map(formatAttr)
    .join("");

  return `<${data.tag}${attr}>${closing}`;
};

const stringReducer = (acc, val) => acc + val;

const map = fn => reducer => (acc, val) => reducer(acc, fn(val));

const transduce = (transformer, reducer, seed, iterable) => {
  const transformedReducer = transformer(reducer);
  let accumulation = seed;

  while (iterable.length) {
    const next = iterable.pop();
    if (!next && next !== 0) continue;
    accumulation = transformedReducer(accumulation, next);

    if (next.children && next.children.constructor === Array) {
      if (next.isSelfClosing) {
        throw Error("Self-closing tags cannot have children");
      }
      iterable.push(`</${next.tag}>`, ...next.children.reverse());
    }
  }

  return accumulation;
};

const renderTag = data => transduce(map(tags), stringReducer, "", [data]);

console.log('DANIEL 5', renderTag(tag).length)
pending…
New
const formatAttr = ([key, val]) =>
  val === true ? ` ${key}` : val || val === 0 ? ` ${key}="${val}"` : ``;

const tags = data => {
  if (typeof data !== "object") return data;
  if (!data.tag) throw Error("Missing tag name");

  const closing = data.children || data.isSelfClosing ? "" : `</${data.tag}>`;

  if (!data.attributes) return `<${data.tag}>${closing}`;

  const attr = Object.entries(data.attributes)
    .map(formatAttr)
    .join("");

  return `<${data.tag}${attr}>${closing}`;
};

const stringReducer = (acc, val) => `${acc}${val}`;

const map = fn => reducer => (acc, val) => reducer(acc, fn(val));

const transduce = (transformer, reducer, seed, iterable) => {
  const transformedReducer = transformer(reducer);
  let accumulation = seed;

  while (iterable.length) {
    const next = iterable.pop();
    if (!next && next !== 0) continue;
    accumulation = transformedReducer(accumulation, next);

    if (next.children && next.children.constructor === Array) {
      if (next.isSelfClosing) {
        throw Error("Self-closing tags cannot have children");
      }
      iterable.push(`</${next.tag}>`, ...next.children.reverse());
    }
  }

  return accumulation;
};

const renderTag = data => transduce(map(tags), stringReducer, "", [data]);

console.log('DANIEL 6', renderTag(tag).length)
pending…

You can edit these tests or add even more tests to this page by appending /edit to the URL.

Compare results of other browsers

0 Comments