带有标记的模板字符串和闭包的问题

问题描述 投票:0回答:1

Symbol.toPrimitive方法,在加标签的模板文字中调用将失去对闭包的访问权限。

要进行复制,只需将提供的代码段粘贴到开发控制台中,即可在带有和不带有标签功能的情况下运行它。任何相关文章均受到高度赞赏。

P.S。如果您能给我一个如何调试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();
javascript closures template-strings
1个回答
1
投票

我不确定我是否理解你的意思。


经过下面的一些评论之后,关于“使用和不使用标签功能运行它的含义”的混淆是:

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-third-id”(格式也不同)。

问题是,为什么将其与标记功能一起使用时会丢失“ A-first-id”和“ A-second-id”。


但是我注意到您每次调用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 example
  • 节点:请参见node example

更新

模板字符串的标记函数只是用于将参数传递给函数的语法糖。

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"));

没有语法糖,很明显,每次调用id时都会覆盖该片段,并且转换为原始值时仅使用最后一个值。

当不使用标记功能时,每个值都会在模板字符串中的每个位置转换为原始值。但是,当将其与标记函数一起使用时,会将每个值作为标记函数的参数,并且直到在标记函数中将其转换为原始值时,才会转换为原始值。因此,您只会得到片段的最后一个值。

© www.soinside.com 2019 - 2024. All rights reserved.