Проблема с помеченными шаблонными строками и закрытием - PullRequest
0 голосов
/ 25 января 2020

Symbol.toPrimitive метод, вызываемый в теге литерала шаблона, теряет доступ к закрытию.

Для воспроизведения просто вставьте предоставленный фрагмент кода в консоль разработчика, запустите его с функцией тега и без нее. Любые соответствующие статьи высоко ценятся.

PS Я также был бы признателен, если бы вы дали мне представление о том, как и где отлаживать код js (включая node.js). Меня интересует лексическая среда, контекст выполнения и стек вызовов.

const isEmptyString = /^\s*$/;

class Thread {
  constructor() {
    this.scope = {
      current: '/test|0::0'
    };

    this.context = {
      current: '/test|0'
    };

    this.html = (strings, ...interpolations) => {
      var output = '';
      var prevMode = this._mode;

      this._mode = 'html';

      var {
        length
      } = interpolations;
      output += strings[0]

      for (let i = 0; i < length; ++i) {
        output += String(interpolations[i]) + strings[i + 1];
      }

      this._mode = prevMode;
      return output;
    };
  }


  get id() {
    var fragment;

    const scope = this.scope.current;
    const context = this.context.current;

    return Object.defineProperties(function self(newFragment) {
      fragment = newFragment;
      return self;
    }, {
      scope: {
        get() {
          return scope
        }
      },
      context: {
        get() {
          return context
        }
      },
      fragment: {
        get() {
          return fragment
        }
      },

      [Symbol.toPrimitive]: {
        value: hint => {
          console.log('::', fragment, '::');
          const isFragmentDefined = !isEmptyString.test(fragment);

          const quote = isFragmentDefined ? '\'' : '';
          const suffix = isFragmentDefined ? `::${fragment}` : '';

          if (isFragmentDefined) fragment = '';

          switch (true) {
            case this._mode === 'html':
              return `node=${quote}${scope}${suffix}${quote}`;
            case this._mode === 'css':
              return `${context}${suffix}`.replace(invalidCSS, char => `\\${char}`);

            default:
              return `${scope}${suffix}`;
          }
        }
      }
    });
  }
}

let thread = new Thread();



async function article() {
  let {
    id,
    html
  } = thread;

  let links = html `
    <ul>
      <li ${id('C-first-id')}></li>
      <li ${id('C-second-id')}></li>
      <li ${id('C-third-id')}></li>
      <li ${id('C-fourth-id')}></li>
    </ul>
  `;

  return html `
    <article>
      <h1 ${id('B-first-id')}>Some header</h1>
      <p ${id('B-second-id')}>Lorem ipsum...</p>
      <p ${id('B-third-id')}>Lorem ipsum...</p>
      <p ${id('B-fourth-id')}>Lorem ipsum...</p>

      <section>
        ${links}
      </section>
    </article>
  `;
}

async function content() {
  let {
    id,
    html
  } = thread;

  return html `
    <main>
      <div>
        <h1 ${id('A-first-id')}>Last article</h1>
        
        
        <div>
          <a href='#' ${id('A-second-id')}>More articles like this</a>
          ${await article()}
          <a href='#' ${id('A-third-id')}>Something else...</a>
          <a href='#' ${id('A-fourth-id')}>Something else...</a>
        </div>
      </div>
    </main>
  `;
}

content();

1 Ответ

2 голосов
/ 25 января 2020

Я не уверен, что понимаю, что вы имеете в виду.


После некоторых комментариев ниже путаница в том, что означает "запускать его с функцией тега и без нее", означает:

let { id, html } = thread;

console.log("Without tag function", `${id("A-first-id")}${id("A-second-id")}${id("A-third-id")}`);
console.log("With tag function", html`${id("A-first-id")}${id("A-second-id")}${id("A-third-id")}`);

Результат:

Without tag function /test|0::0::A-first-id/test|0::0::A-second-id/test|0::0::A-third-id
With tag function node='/test|0::0::A-third-id'node=/test|0::0node=/test|0::0

Разница в том, что без функции тега она работает как задумано, и "A-first-id", "A-second -id "и" A-third-id "присутствует в результате. При использовании функции тега присутствует только «A-третий-идентификатор» (и формат также отличается).

Вопрос в том, почему «А-первый-идентификатор» и «А-второй-идентификатор» "теряются при использовании его с функцией-тегом.


Но я заметил, что вы перезаписываете фрагмент каждый раз, когда вызываете id, а код в Symbol.toPrimitive вызывается позднее , Вот почему вы получаете только последнюю строку "[ABC]-fourth-id" и очищаете фрагмент с помощью if (isFragmentDefined) fragment = '';

"use strict";

class Thread {
  constructor() {
    this.html = (strings, ...interpolations) => {
      var output = '';
      var {
        length
      } = interpolations;
      output += strings[0]

      for (let i = 0; i < length; ++i) {
        output += String(interpolations[i]) + strings[i + 1];
      }

      return output;
    };
  }


  get id() {
    var fragment;

    return Object.defineProperties(function self(newFragment) {
      console.log("fragment new '%s' old '%s'", newFragment, fragment);
      fragment = newFragment; // overwrite fragment
      return self;
    }, {
      [Symbol.toPrimitive]: {
        value: hint => {
          // this is called later, fragment is the last value
          console.log("toPrimitive", fragment);
          return fragment;
        }
      }
    });
  }
}

let thread = new Thread();

async function content() {
  let {
    id,
    html
  } = thread;

  return html `
    ${id('A-first-id')}
    ${id('A-second-id')}
    ${id('A-third-id')}
    ${id('A-fourth-id')}
  `;
}

content().then(x => console.log(x));

Запустите приведенный выше код, и вы получите:

fragment new 'A-first-id' old 'undefined'
fragment new 'A-second-id' old 'A-first-id'
fragment new 'A-third-id' old 'A-second-id'
fragment new 'A-fourth-id' old 'A-third-id'
toPrimitive A-fourth-id
toPrimitive A-fourth-id
toPrimitive A-fourth-id
toPrimitive A-fourth-id

  A-fourth-id
  A-fourth-id
  A-fourth-id
  A-fourth-id

Итак, сначала код в id вызывается для КАЖДОГО вхождения в вашей строке, перезаписывая fragment каждый раз. После этого вызывается toPrimitive, и в нем установлен только последний фрагмент: "A-fourth-id".

Я почти уверен, что это не то, что вы хотели.

Я думаю что вы хотели:

fragment new 'A-first-id' old 'undefined'
fragment new 'A-second-id' old 'A-first-id'
fragment new 'A-third-id' old 'A-second-id'
fragment new 'A-fourth-id' old 'A-third-id'
toPrimitive A-first-id
toPrimitive A-second-id
toPrimitive A-third-id
toPrimitive A-fourth-id

  A-first-id
  A-second-id
  A-third-id
  A-fourth-id

И настоящая ошибка это ...

Когда я снова посмотрел на код и попытался объяснить, почему фрагмент был перезаписан Ударь меня: ты определяешь id как добытчик. Поэтому, когда вы делаете:

let { id, html } = thread;

, вы фактически вызываете код в id, и вы получаете функцию. Поэтому каждый раз, когда вы используете id в вашей строке, он использует одну и ту же функцию с одним и тем же фрагментом.

Решение? Измените код, чтобы id не был получателем.

Когда вы используете деконструкцию функций из объекта, функция больше не знает контекст. Это можно исправить, связав функцию в конструкторе:

class MyClass {
  constructor() {


    // Bind this to some functions
    for (const name of ['one', 'two'])
      this[name] = this[name].bind(this);
  }
  one(value) {
    return this.two(value).toString(16);
  }
  two(value) {
    return value * 2;
  }
}

const my = new MyClass();
const {one, two} = my;
console.log(one(1000)); // Works since `one` was bound in the constructor 

и для отладки:

  • Браузер: в Google Chrome нажмите F12, выберите вкладку источника. Вы можете установить точки останова. Chrome пример
  • Узел: см. пример узла

Обновление

Тег -функции для строк шаблона - это просто syntacti c sugar для передачи аргументов в функцию.

let { id, html } = thread;

// A tag function is just syntactic sugar:
html`${id("A-first-id")}${id("A-second-id")}${id("A-third-id")}`;

// for this:    
html(["", "", "", ""], id("A-first-id"), id("A-second-id"), id("A-third-id"));

Без syntacti c sugar очевидно, что вы перезаписываете фрагмент каждый раз, когда вызываете id, и только последнее значение будет использоваться при преобразовании в примитивное значение.

Если вы не используете функцию тега, каждое значение преобразуется в значение примитива в каждом месте строки шаблона. Но когда вы используете его с функцией тега, вы получаете каждое значение в качестве параметра для вашей функции тега, и преобразование в примитивное значение не происходит, пока вы не преобразуете его в функцию тега. Поэтому вы получаете только последнее значение вашего фрагмента.

...