如何创建私有自定义事件或如何以最类型安全和受保护的方式处理/应用事件调度?

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

我想创建一个私人自定义事件,一个其他人无法调度或侦听的事件。我尝试了这个,但它不起作用:

new CustomEvent(Symbol("validate"));

可以吗?

我知道我可以使用随机字符串作为事件的名称。

javascript dom
2个回答
0
投票

来自一些OP和我的评论......

当然,人们可以构建一个系统来封闭/封装除除例如之外的所有内容。实例的 de/register / un/subscribe 方法,可以在其中添加/删除对象,每个对象都有自己的(事件/主题/主题/通道特定)处理函数。调度实例没有公共

dispatchEvent
方法,但会调用每个注册/订阅对象的特定处理函数。 与此类似的东西... – Peter Seliger

@PeterSeliger 事件字符串的问题是:它们共享一个全局名称空间。没有什么可以阻止碰撞。 – 天花板

不...如果一个人[私下]将自己的

EventTarget
实例与能够将事件分派给选定(验证和注册)对象的类型的每个实例绑定,那么除了秘密/私下分派实例本身之外,没有人有能力访问此类分派事件及其相关数据。这是因为只有调度实例本身可以访问调度对象。实例本身控制对象的取消/注册,每个对象必须能够处理此类受控分派(并且否则不可访问)事件类型。 – 彼得·塞利格

特别是关于...

“事件字符串的问题是:它们共享一个全局名称空间。没有什么可以防止冲突。” – ceving

...

EventTarget
的事件调度实现了类型安全的 Signals 和 Slot 方法。没有人可以伪造/拦截/欺骗在 addEventListener
 的调用时间创建的 
event-object
。此外,在事件目标的
dispatchEvent
时间,仅在该事件目标处注册的事件类型特定处理程序函数(每个指定为创建的事件侦听器
handleEvent
方法)会被注册。将被调用。

按照建议(以及后来演示的),通过

addSubscriber/s
/
removeSubscriber/s
实现的发布-订阅与事件目标的封闭(私有)功能相结合,可以实现事件的隐私和类型安全-调度。

const subscriber_1 = {
  uuid: crypto.randomUUID(),
  handleTypeA(evt) {
    console.log({
      eventType: evt.type,
      currentTarget: evt.currentTarget,
      targetConstructorName: evt.detail.target.constructor.name,
      subscriber: subscriber_1,
      payload: evt.detail.payload,
    });
  }
};
const subscriber_2 = {
  uuid: crypto.randomUUID(),
  handleTypeB(evt) {
    console.log({
      eventType: evt.type,
      currentTarget: evt.currentTarget,
      targetConstructorName: evt.detail.target.constructor.name,
      subscriber: subscriber_2,
      payload: evt.detail.payload,
    });
  }
};
const subscriber_3 = {
  uuid: crypto.randomUUID(),
  handleTypeB(evt) {
    console.log({
      eventType: evt.type,
      currentTarget: evt.currentTarget,
      targetConstructorName: evt.detail.target.constructor.name,
      subscriber: subscriber_3,
      payload: evt.detail.payload,
    });
  },
  handleTypeZ(evt) {
    console.log({
      eventType: evt.type,
      currentTarget: evt.currentTarget,
      targetConstructorName: evt.detail.target.constructor.name,
      subscriber: subscriber_3,
      payload: evt.detail.payload,
    });
  }
};
const sampleInstance = new PrivatlyDispatchingType;

console.log(
  'sampleInstance.addSubscribers(subscriber_1, subscriber_2, subscriber_3) ...\n',
  '... sampleInstance.aggregatesAndDispatchesSampleData() ...',
);
sampleInstance.addSubscribers(subscriber_1, subscriber_2, subscriber_3);
sampleInstance.aggregatesAndDispatchesSampleData();

console.log(
  'sampleInstance.removeSubscribers([subscriber_1, subscriber_3]) ...\n',
  '... sampleInstance.aggregatesAndDispatchesSampleData() ...',
);
sampleInstance.removeSubscribers([subscriber_1, subscriber_3]);
sampleInstance.aggregatesAndDispatchesSampleData();;

console.log(
  'sampleInstance.removeSubscribers(subscriber_2) ...\n',
  '... sampleInstance.aggregatesAndDispatchesSampleData() ...',
);
sampleInstance.removeSubscribers(subscriber_2);
sampleInstance.aggregatesAndDispatchesSampleData();
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
// scope of module ... `PrivatlyDispatchingType`

function isValidSubscriber(value) {
  // e.g. implement other custom type check functionality.
  return (
    (typeof value?.uuid === 'string') &&
    (Object
      .entries(value)
      .filter(([key, value]) =>
        key.startsWith('handle') && (typeof value === 'function')
      )
      .length >= 1)      
  );
}

function getValidSubscribers(...subscribers) {
  return subscribers.flat().filter(isValidSubscriber);
}

const eventTypeLookup = {
  handleTypeA: 'private-event-type-A',
  handleTypeB: 'private-event-type-B',
  handleTypeZ: 'private-event-type-Z',
}

class PrivatlyDispatchingType {
  #dispatcher = new EventTarget;
  #subscribers = new Map;

  /**
   *  type specific implementation
   */

  //  ----- ----- ----- ----- -----

  addSubscribers(...args) {
    getValidSubscribers(...args)

      .forEach(subscriber => {
        const { uuid } = subscriber;

        if (!this.#subscribers.has(uuid)) {
          this.#subscribers.set(uuid, subscriber);

          Object
            .entries(subscriber)
            .filter(([key, value]) =>
              key.startsWith('handle') && (typeof value === 'function')
            )
            .forEach(([handlerName, handlerFunction]) =>

              this.#dispatcher.addEventListener(
                eventTypeLookup[handlerName], handlerFunction
              )
            );
        }
      });
  }
  removeSubscribers(...args) {
    getValidSubscribers(...args)

      .forEach(subscriber => {
        const { uuid } = subscriber;

        if (this.#subscribers.has(uuid)) {
          this.#subscribers.delete(uuid);

          Object
            .entries(subscriber)
            .filter(([key, value]) =>
              key.startsWith('handle') && (typeof value === 'function')
            )
            .forEach(([handlerName, handlerFunction]) =>

              this.#dispatcher.removeEventListener(
                eventTypeLookup[handlerName], handlerFunction
              )
            );
        }
      });
  }

  aggregatesAndDispatchesSampleData() {
    this.#dispatcher.dispatchEvent(new CustomEvent(
      'private-event-type-A', {
        detail: {
          target: this,
          payload: {
            foo: 'Foo',
            bar: 'Bar',
          }
        }
      })
    );
    this.#dispatcher.dispatchEvent(new CustomEvent(
      'private-event-type-B', {
        detail: {
          target: this,
          payload: {
            baz: 'Baz',
            biz: 'Biz',
          }
        }
      })
    );
    this.#dispatcher.dispatchEvent(new CustomEvent(
      'private-event-type-Z', {
        detail: {
          target: this,
          payload: {
            quick: 'quick',
            brown: 'brown',
            fox: 'fox',
          }
        }
      })
    );
  }
}
</script>

代码复用“独家发布行为”

下一个提供的示例代码演示了一种可能的 代码重用方式,它仍然支持 独家发布

该实现确实通过利用单个

WeakMap
实例来覆盖可重用实现的
ExclusivePublisher
代码的隐私部分来实现这种行为。然而,后一个类不能被想要获得独占发布行为的第三方代码直接实例化。相反,
"exclusive-publishing"
模块公开了一个
useExclusivePublishing
方法,该方法可以访问
ExclusivePublisher
实例的 publisherdispatcher 功能。

因此,任何使用

publisher
first
dispatcher
useExclusivePublishing 返回值的第三方代码都必须将两个引用保持为私有,并且 second 需要实现
addSubscribers
removeSubscribers
的转发代码,并且此外还需要实现围绕
dispatcher
dispatchEvent
方法构建的自己的发布代码。

// import { useExclusivePublishing } from 'exclusive-publishing.js'

class MyExclusivePublishingType {
  #publisher;
  #dispatcher;

  constructor(eventTypes, isSubscriber) {

    const { publisher, dispatcher } =
      useExclusivePublishing(eventTypes, isSubscriber);

    // private property based aggregation.
    this.#publisher = publisher;
    this.#dispatcher = dispatcher;    
  }

  // forwarding via aggregated private properties.

  addSubscribers(...args) {
    return this.#publisher.addSubscribers(...args);
  }
  removeSubscribers(...args) {
    return this.#publisher.removeSubscribers(...args);
  }

  // type-specific prototypal implementation/s.

  publishSampleData() {
    this.#dispatcher.dispatchEvent(new CustomEvent(
      'private-event-type-A', {
        detail: {
          target: this,
          payload: {
            foo: 'Foo',
            bar: 'Bar',
          }
        }
      })
    );
    this.#dispatcher.dispatchEvent(new CustomEvent(
      'private-event-type-B', {
        detail: {
          target: this,
          payload: {
            baz: 'Baz',
            biz: 'Biz',
          }
        }
      })
    );
    this.#dispatcher.dispatchEvent(new CustomEvent(
      'private-event-type-Z', {
        detail: {
          target: this,
          payload: {
            quick: 'quick',
            brown: 'brown',
            fox: 'fox',
          }
        }
      })
    );
  }
}

const publishingType = new MyExclusivePublishingType({
  handleTypeA: 'private-event-type-A',
  handleTypeB: 'private-event-type-B',
  handleTypeZ: 'private-event-type-Z',
});

const subscriber_1 = {
  uuid: crypto.randomUUID(),
  handleTypeA(evt) {
    console.log({
      eventType: evt.type,
      currentTarget: evt.currentTarget,
      targetConstructorName: evt.detail.target.constructor.name,
      subscriber: subscriber_1,
      payload: evt.detail.payload,
    });
  }
};
const subscriber_2 = {
  uuid: crypto.randomUUID(),
  handleTypeB(evt) {
    console.log({
      eventType: evt.type,
      currentTarget: evt.currentTarget,
      targetConstructorName: evt.detail.target.constructor.name,
      subscriber: subscriber_2,
      payload: evt.detail.payload,
    });
  }
};
const subscriber_3 = {
  uuid: crypto.randomUUID(),
  handleTypeB(evt) {
    console.log({
      eventType: evt.type,
      currentTarget: evt.currentTarget,
      targetConstructorName: evt.detail.target.constructor.name,
      subscriber: subscriber_3,
      payload: evt.detail.payload,
    });
  },
  handleTypeZ(evt) {
    console.log({
      eventType: evt.type,
      currentTarget: evt.currentTarget,
      targetConstructorName: evt.detail.target.constructor.name,
      subscriber: subscriber_3,
      payload: evt.detail.payload,
    });
  }
};

console.log(
  'publishingType.addSubscribers(subscriber_1, subscriber_2, subscriber_3) ...\n',
  '... publishingType.publishSampleData() ...',
);
publishingType.addSubscribers(subscriber_1, subscriber_2, subscriber_3);
publishingType.publishSampleData();

console.log(
  'publishingType.removeSubscribers([subscriber_1, subscriber_3]) ...\n',
  '... publishingType.publishSampleData() ...',
);
publishingType.removeSubscribers([subscriber_1, subscriber_3]);
publishingType.publishSampleData();;

console.log(
  'publishingType.removeSubscribers(subscriber_2) ...\n',
  '... publishingType.publishSampleData() ...',
);
publishingType.removeSubscribers(subscriber_2);
publishingType.publishSampleData();
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
// scope of module ... "exclusive-publishing.js"

const publisherRegistry = new WeakMap;

function isFunction(value) {
  return (
    (typeof value === 'function') &&
    (typeof value.call === 'function') &&
    (typeof value.apply === 'function')
  );
}

function isMinimumSubscriber(value) {
  return (
    (typeof value?.uuid === 'string') &&
    (Object
      .entries(value)
      .filter(([key, value]) =>
        key.startsWith('handle') && isFunction(value)
      )
      .length >= 1)      
  );
}

function getSubscribers(isSubscriber, ...subscribers) {
  return subscribers.flat().filter(isSubscriber);
}
function getHandlerEntries(subscriber) {
  return Object
    .entries(subscriber)
    .filter(([key, value]) =>
      key.startsWith('handle') && isFunction(value)
    );
}

function addSubscribers(publisher, ...args) {
  const {
    eventTypes, dispatcher, subscribers: subscriberRegistry, isSubscriber,
  } =
    publisherRegistry.get(publisher);

  getSubscribers(isSubscriber, ...args)
    .forEach(subscriber => {
      const { uuid } = subscriber;

      if (!subscriberRegistry.has(uuid)) {
        subscriberRegistry.set(uuid, subscriber);

        getHandlerEntries(subscriber)
          .forEach(([handlerName, handlerFunction]) =>

            dispatcher.addEventListener(
              eventTypes[handlerName], handlerFunction
            )
          );
      }
    });
}
function removeSubscribers(publisher, ...args) {
  const {
    eventTypes, dispatcher, subscribers: subscriberRegistry, isSubscriber,
  } =
    publisherRegistry.get(publisher);

  getSubscribers(isSubscriber, ...args)
    .forEach(subscriber => {
      const { uuid } = subscriber;

      if (subscriberRegistry.has(uuid)) {
        subscriberRegistry.delete(uuid);

        getHandlerEntries(subscriber)
          .forEach(([handlerName, handlerFunction]) =>

            dispatcher.removeEventListener(
              eventTypes[handlerName], handlerFunction
            )
          );
      }
    });
}

class ExclusivePublisher {
  constructor(eventTypes = {}, isSubscriber) {

    publisherRegistry
      .set(this, {
        eventTypes,
        dispatcher: new EventTarget,
        subscribers: new Map,
        isSubscriber: isFunction(isSubscriber)
          && (value => isMinimumSubscriber(value) && isSubscriber(value))
          || isMinimumSubscriber,
      });
  }

  // - minimum prototypal implementation/footprint because of fowarding
  //   which is possible due to the `WeakMap`-based privacy-approach.

  addSubscribers(...args) {
    return addSubscribers(this, ...args);
  }
  removeSubscribers(...args) {
    return removeSubscribers(this, ...args);
  }
}

/*export */function useExclusivePublishing(eventTypes, isSubscriber) {

  const publisher = new ExclusivePublisher(eventTypes, isSubscriber);
  const { dispatcher } =  publisherRegistry.get(publisher);

  return {
    publisher, dispatcher,
  };
}
</script>

通过重写我最近提供的答案之一的示例代码,将再次证明“独家发布行为”的实用性、可重用性和优点...“使用 Node EventEmitter,如何是否可以从服务列表中删除对象,同时取消订阅服务的事件调度?”.

关于刚刚提到的答案的最后一个示例代码...而不是

Bakery
类让扩展
EventTarget
允许每个有权访问面包店实例的人调用其所有 3 个继承方法(
add/removeEventListener
/
 dispatchEvent
),可以在上面介绍的
Bakery
模块的
useExclusivePublishing
的帮助下实现
'exclusive-publishing'
类。

// import { Bakery } from './bakery.js';
// import { Customer } from './customer.js';

const klugesherz =
  new Bakery({ name: 'Pâtisserie Klugesherz', breadPrice: 1.5 });
const hanss =
  new Bakery({ name: 'Boulangerie Patisserie Hanss', breadPrice: 2.7 });

const johnRich = new Customer({
  name: 'John Rich',
  maxPrice: 5,
  moneyTotal: 20,
  handlePurchaseBread: function ({ detail: { bakery } }) {
    this.buyBread(bakery, 3);
  },
});
const martinPoor = new Customer({
  name: 'Martin Poor',
  maxPrice: 3,
  moneyTotal: 10,
  handlePurchaseBread: function ({ detail: { bakery } }) {
    const quantity = (
      ((bakery.name === 'Boulangerie Patisserie Hanss') && 1) ||
      ((bakery.name === 'Pâtisserie Klugesherz') && 2) || 0
    );
    this.buyBread(bakery, quantity);
  },
});

klugesherz.addCustomers(johnRich, martinPoor);
hanss.addCustomers(johnRich, martinPoor);

console.log({
  bakeries: {
    klugesherz: klugesherz.valueOf(),
    hanss: hanss.valueOf(),
  },
  customers: {
    johnRich: johnRich.valueOf(),
    martinPoor: martinPoor.valueOf(),
  },
});

console.log('\n+++ klugesherz.bakeBread(4) +++');
klugesherz.bakeBread(4);

console.log('\n+++ hanss.bakeBread(5) +++');
hanss.bakeBread(5);

console.log('\n+++ klugesherz.bakeBread(4) +++');
klugesherz.bakeBread(4);

console.log('\n+++ hanss.bakeBread(5) +++');
hanss.bakeBread(5);

console.log('\n... remove John Rich from the customer list of Patisserie Hanss.\n\n');
hanss.removeCustomers(johnRich);

console.log('\n+++ klugesherz.bakeBread(4) +++');
klugesherz.bakeBread(4);

console.log('\n+++ hanss.bakeBread(5) +++');
hanss.bakeBread(5);

console.log('\n... remove Martin Poor from the customer list of Patisserie Hanss.');
hanss.removeCustomers(martinPoor);

console.log('... remove John Rich and Martin Poor from the customer list of Pâtisserie Klugesherz.\n\n');
klugesherz.removeCustomers(johnRich, martinPoor);

console.log('+++ klugesherz.bakeBread(4) +++');
klugesherz.bakeBread(4);

console.log('+++ hanss.bakeBread(5) +++');
hanss.bakeBread(5);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
// scope of module ... "exclusive-publishing.js"

const publisherRegistry = new WeakMap;

function isFunction(value) {
  return (
    (typeof value === 'function') &&
    (typeof value.call === 'function') &&
    (typeof value.apply === 'function')
  );
}

function isMinimumSubscriber(subscriber) {
  return (
    (typeof subscriber?.uuid === 'string') &&
    (Object
      .entries(subscriber.valueOf?.() ?? subscriber)
      .filter(([key, value]) =>
        key.startsWith('handle') && isFunction(value)
      )
      .length >= 1)      
  );
}

function getSubscribers(isSubscriber, ...subscribers) {
  return subscribers.flat().filter(isSubscriber);
}
function getHandlerEntries(subscriber) {
  return Object
    .entries(subscriber.valueOf?.() ?? subscriber)
    .filter(([key, value]) =>
      key.startsWith('handle') && isFunction(value)
    );
}

function addSubscribers(publisher, ...args) {
  const {
    eventTypes, dispatcher, subscribers: subscriberRegistry, isSubscriber,
  } =
    publisherRegistry.get(publisher);

  getSubscribers(isSubscriber, ...args)
    .forEach(subscriber => {
      const { uuid } = subscriber;

      if (!subscriberRegistry.has(uuid)) {
        subscriberRegistry.set(uuid, subscriber);

        getHandlerEntries(subscriber)
          .forEach(([handlerName, handlerFunction]) =>

            dispatcher.addEventListener(
              eventTypes[handlerName], handlerFunction
            )
          );
      }
    });
}
function removeSubscribers(publisher, ...args) {
  const {
    eventTypes, dispatcher, subscribers: subscriberRegistry, isSubscriber,
  } =
    publisherRegistry.get(publisher);

  getSubscribers(isSubscriber, ...args)
    .forEach(subscriber => {
      const { uuid } = subscriber;

      if (subscriberRegistry.has(uuid)) {
        subscriberRegistry.delete(uuid);

        getHandlerEntries(subscriber)
          .forEach(([handlerName, handlerFunction]) =>

            dispatcher.removeEventListener(
              eventTypes[handlerName], handlerFunction
            )
          );
      }
    });
}

class ExclusivePublisher {
  constructor(eventTypes = {}, isSubscriber) {

    publisherRegistry
      .set(this, {
        eventTypes,
        dispatcher: new EventTarget,
        subscribers: new Map,
        isSubscriber: isFunction(isSubscriber)
          && (value => isMinimumSubscriber(value) && isSubscriber(value))
          || isMinimumSubscriber,
      });
  }

  // - minimum prototypal implementation/footprint because of fowarding
  //   which is possible due to the `WeakMap`-based privacy-approach.

  addSubscribers(...args) {
    return addSubscribers(this, ...args);
  }
  removeSubscribers(...args) {
    return removeSubscribers(this, ...args);
  }
}

/*export */function useExclusivePublishing(eventTypes, isSubscriber) {

  const publisher = new ExclusivePublisher(eventTypes, isSubscriber);
  const { dispatcher, subscribers } =  publisherRegistry.get(publisher);

  return {
    publisher, dispatcher, subscribers,
  };
}
</script>

<script>
// scope of module ... "customer.js"

/*export */class Customer {
  #uuid;
  #name;
  #maxPrice;
  #moneyTotal;
  #handlePurchaseBread;

  constructor({ name, maxPrice, moneyTotal, handlePurchaseBread }) {
    this.#uuid = crypto.randomUUID();
    this.#name = name;
    this.#maxPrice = maxPrice;
    this.#moneyTotal = moneyTotal;
    this.#handlePurchaseBread = (typeof handlePurchaseBread === 'function')
      && handlePurchaseBread.bind(this)
      || (({ currentTarget: bakery }) => { this.buyBread(bakery, 1); });
  }
  get uuid() {
    return this.#uuid;
  }
  get name() {
    return this.#name;
  }
  get maxPrice() {
    return this.#maxPrice;
  }
  get moneyTotal() {
    return this.#moneyTotal;
  }
  get handlePurchaseBread() {
    return this.#handlePurchaseBread;
  }

  buyBread(bakery, quantity = 1) {
    const { approved, reason } = bakery.sellBread(this, quantity);

    if (approved === true) {

      this.#moneyTotal = this.moneyTotal - (bakery.breadPrice * quantity);

      console.log(
        `Customer ${ this.name } bought ${ quantity } piece/s of bread for a total of ${ (bakery.breadPrice * quantity).toFixed(2) } at ${ bakery.name }.`
      );
    } else if (typeof reason === 'string') {

      console.log('Buying a bread did fail, due to ...', reason);
    }
  }

  valueOf() {
    const { uuid, name, maxPrice, moneyTotal, handlePurchaseBread } = this;
    return { uuid, name, maxPrice, moneyTotal, handlePurchaseBread };
  }
}

/*export */function isCustomer(value) {
  return ((value instanceof Customer) || (
    Object
      .keys(value.valueOf())
      .sort()
      .join('_') === 'handlePurchaseBread_maxPrice_moneyTotal_name_uuid'
  ));
}
</script>

<script>
// scope of module ... "bakery.js"

// import { useExclusivePublishing } from './exclusive-publishing.js';
// import { isCustomer } from './customer.js';

/*export */class Bakery {
  #name;

  #breadPrice;
  #breadCount;
  #moneyTotal;

  #customers;

  #publisher;
  #dispatcher;

  constructor({ name, breadPrice }) {
    this.#name = name;

    this.#breadPrice = breadPrice;
    this.#breadCount = 0;
    this.#moneyTotal = 0;

    const { publisher, dispatcher, subscribers: customers } =
      useExclusivePublishing({ handlePurchaseBread: 'bread-baked' }, isCustomer);

    this.#customers = customers;

    this.#publisher = publisher;
    this.#dispatcher = dispatcher;
  }

  get name() {
    return this.#name;
  }
  get breadPrice() {
    return this.#breadPrice;
  }
  get breadCount() {
    return this.#breadCount;
  }
  get moneyTotal() {
    return this.#moneyTotal;
  }
  get customers() {
    return [...this.#customers.values()];
  }

  addCustomers(...args) {
    return this.#publisher.addSubscribers(...args);
  }
  removeCustomers(...args) {
    return this.#publisher.removeSubscribers(...args);
  }

  bakeBread(quantity = 10) {
    this.#breadCount = this.#breadCount + quantity;

    this.#dispatcher.dispatchEvent(
      new CustomEvent('bread-baked', { detail: { bakery: this, quantity } })
    );
  }

  sellBread(customer, quantity = 1) {
    const transaction = { approved: false };

    if (quantity >= 1) {
      if (this.breadCount >= quantity) {

        const isWithinPriceLimit = this.breadPrice <= customer.maxPrice;
        const canEffortPurchase = (this.breadPrice * quantity) <= customer.moneyTotal;

        if (isWithinPriceLimit) {
          if (canEffortPurchase) {

            this.#breadCount = this.breadCount - quantity;

            transaction.approved = true;
          } else {
            transaction.reason =
              `Customer ${ customer.name } doesn't have enough money for buying a bread at ${ this.name }.`;
          }
        } else {
          transaction.reason =
            `Customer ${ customer.name } does have a price limit which just did exceed at ${ this.name }.`;
        }
      } else {
        transaction.reason =
          `The ${ this.name } bakery is too low on bread stock in order to fulfill ${ customer.name }'s order.`;
      }
    } else {
      transaction.reason =
        `Customer ${ customer.name } did not provide a valid quantity for purchasing bread at ${ this.name }.`;
    }
    return transaction;
  }

  valueOf() {
    const { name, breadPrice, breadCount, moneyTotal, customers } = this;
    return {
      name, breadPrice, breadCount, moneyTotal,
      customers: customers.map(customer => customer.valueOf()),
    };
  }
}
</script>


-1
投票

这似乎是可以达到的最大值。

class ObscureEvent {
  #name;
  #event;
  constructor (options) {
    this.#name = crypto.randomUUID();
    this.#event = new CustomEvent(this.#name, options); }
  listen(target, handler) {
    target.addEventListener(this.#name, handler); }
  dispatch(target) {
    target.dispatchEvent(this.#event); } }
      
const validate = new ObscureEvent({ bubbles: true });
© www.soinside.com 2019 - 2024. All rights reserved.