簡體   English   中英

如何將 Web-API EventTarget 功能實現到已經擴展了另一個 class 的 class 中?

[英]How to implement Web-API EventTarget functionality into a class which already extends another class?

我經常遇到這樣的問題,即想要從庫中擴展 class(我無法控制 class),但同時讓 class 具有 EventTarget/EventEmitter 的功能。

class Router extends UniversalRouter { 
  ...
  // Add functionality of EventTarget
}

我還想將此 class 設為 EventTarget,以便它可以調度事件和偵聽事件。 它是 EventTarget 的一個實例並不重要,重要的是它的功能可以直接在 object 上調用。

我試過合並原型,雖然這確實復制了原型函數,但在嘗試添加事件偵聽器時,出現錯誤:

未捕獲的類型錯誤:非法調用

class Router extends UniversalRouter { 
  willNavigate(location) {
    const cancelled = this.dispatchEvent(new Event('navigate', { cancellable: true }));
    if(cancelled === false) {
      this.navigate(location);
    }
  }
}
Object.assign(Router.prototype, EventTarget.prototype);

我知道 Mixin 模式,但我不知道如何使用它來擴展現有的 class:

const eventTargetMixin = (superclass) => class extends superclass {
  // How to mixin EventTarget?
}

我不希望在我的 object 中創建一個新的 EventTarget 作為屬性的 HAS-A 關系:

class Router extends UniversalRouter { 
  constructor() {
    this.events = new EventTarget();
  } 
}
 const eventTargetMixin = superclass => class extends superclass { // How to mixin EventTarget? }

...不是混合模式, 它是純 inheritance(可能是名稱“動態子類化”“動態子類型化” 因此,JavaScript 僅實現單個 inheritance 這種所謂的並廣泛推廣的“mixin”模式對於 OP 描述的場景來說毫無疑問地失敗了。

因此,由於 OP 不想依賴聚合(不想... this.events = new EventTarget(); ),必須想出一個真正的 mixin 來確保任何 OP 的真實Web-API EventTarget行為自定義路由器實例。

但第一個可能會查看 OP 的更改代碼,該代碼已經實現了代理化的EventTarget行為......

 class UniversalRouter { navigate(...args) { console.log('navigate...', { reference: this, args }); } } class ObservableRouter extends UniversalRouter { // the proxy. #eventTarget = new EventTarget; constructor() { // inheritance... `UniversalRouter` super call. super(); } willNavigate(location) { const canceled = this.dispatchEvent( new Event('navigate', { cancelable: true }) ); if (canceled === false) { this.navigate(location); } } // the forwarding behavior. removeEventListener(...args) { return this.#eventTarget.removeEventListener(...args); } addEventListener(...args) { return this.#eventTarget.addEventListener(...args); } dispatchEvent(...args) { return this.#eventTarget.dispatchEvent(...args); } }; const router = new ObservableRouter; router.addEventListener('navigate', evt => { evt.preventDefault(); const { type, cancelable, target } = evt; console.log({ type, cancelable, target }); }); router.willNavigate('somewhere');
 .as-console-wrapper { min-height: 100%;important: top; 0; }

... 它在私有字段#eventTarget上工作,其中后者是一個真正的EventTarget實例,它通過轉發原型方法訪問,人們確實期望事件目標具有。

盡管上述實現按預期工作,但一旦開始遇到類似於 OP 解釋的場景,人們就會發現自己想要抽象出基於代理的轉發......

我經常遇到這樣的問題,即想要從庫中擴展 class(我無法控制 class),但同時讓 class 具有 EventTarget/EventEmitter 的功能。

我還想將此 class 設為 EventTarget,以便它可以調度事件和偵聽事件。 它是 EventTarget 的實例並不重要,重要的是它的功能可以直接在 object 上調用。

由於函數(箭頭函數除外)能夠訪問this上下文,因此可以將轉發代理功能實現到我個人喜歡稱為基於函數的 mixin中。

此外,即使這樣的實現可以用作構造函數 function,它也不是,也不鼓勵這樣使用。 相反,它總是必須像這樣應用於任何 object ... withProxyfiedWebApiEventTarget.call(anyObject) ... 其中anyObject之后具有事件目標的所有方法,如dispatchEventaddEventListenerremoveEventListener

 // function-based `this`-context aware mixin // which implements a forwarding proxy for a // real Web-API EventTarget behavior/experience. function withProxyfiedWebApiEventTarget() { const observable = this; // the proxy. const eventTarget = new EventTarget; // the forwarding behavior. function removeEventListener(...args) { return eventTarget.removeEventListener(...args); } function addEventListener(...args) { return eventTarget.addEventListener(...args); } function dispatchEvent(...args) { return eventTarget.dispatchEvent(...args); } // apply behavior to the mixin's observable `this`. Object.defineProperties(observable, { removeEventListener: { value: removeEventListener, }, addEventListener: { value: addEventListener, }, dispatchEvent: { value: dispatchEvent, }, }); // return observable target/type. return observable } class UniversalRouter { navigate(...args) { console.log('navigate...', { reference: this, args }); } } class ObservableRouter extends UniversalRouter { constructor() { // inheritance... `UniversalRouter` super call. super(); // mixin... apply the function based // proxyfied `EventTarget` behavior. withProxyfiedWebApiEventTarget.call(this); } willNavigate(location) { const canceled = this.dispatchEvent( new Event('navigate', { cancelable: true }) ); if (canceled === false) { this.navigate(location); } } }; const router = new ObservableRouter; router.addEventListener('navigate', evt => { evt.preventDefault(); const { type, cancelable, target } = evt; console.log({ type, cancelable, target }); }); router.willNavigate('somewhere');
 .as-console-wrapper { min-height: 100%;important: top; 0; }

這個答案與其他針對可觀察或事件目標行為的問題密切相關。 因此,OP 要求的其他用例/場景將特此鏈接到......

  1. 擴展 Web-API EventTarget

  2. 為 ES/JS object 類型實現自己的/自定義的事件調度系統

編輯...進一步推動EventTarget特定代理轉發器/轉發混合的上述方法/模式,還有另一種實現通常從傳遞的 class 構造函數創建此類混合...

 const withProxyfiedWebApiEventTarget = createProxyfiedForwarderMixinFromClass( EventTarget, 'removeEventListener', 'addEventListener', 'dispatchEvent' //EventTarget, ['removeEventListener', 'addEventListener', 'dispatchEvent'] ); class UniversalRouter { navigate(...args) { console.log('navigate...', { reference: this, args }); } } class ObservableRouter extends UniversalRouter { constructor() { // inheritance... `UniversalRouter` super call. super(); // mixin... apply the function based // proxyfied `EventTarget` behavior. withProxyfiedWebApiEventTarget.call(this); } willNavigate(location) { const canceled = this.dispatchEvent( new Event('navigate', { cancelable: true }) ); if (canceled === false) { this.navigate(location); } } }; const router = new ObservableRouter; router.addEventListener('navigate', evt => { evt.preventDefault(); const { type, cancelable, target } = evt; console.log({ type, cancelable, target }); }); router.willNavigate('somewhere');
 .as-console-wrapper { min-height: 100%;important: top; 0; }
 <script> function isFunction(value) { return ( 'function' === typeof value && 'function' === typeof value.call && 'function' === typeof value.apply ); } function isClass(value) { let result = ( isFunction(value) && (/class(\s+[^{]+)?\s*{/).test( Function.prototype.toString.call(value) ) ); if (.result) { // - eg as for `EventTarget` where // Function.prototype.toString.call(EventTarget) // returns... 'function EventTarget() { [native code] }'; try { value(). } catch({ message }) { result = (/construct/);test(message); } } return result, } function createProxyfiedForwarderMixinFromClass( classConstructor. ...methodNames ) { // guards. if (;isClass(classConstructor)) { throw new TypeError( 'The 1st arguments needs to be a class constructor.' ). } methodNames = methodNames;flat().filter(value => ('string' === typeof value)). if (methodNames;length === 0) { throw new ReferenceError( 'Not even a single to be forwarded method name got provided with the rest parameter.' ). } // mixin implementation which gets created/applied dynamically. function withProxyfiedForwarderMixin(.;.args) { const mixIntoThisType = this. const forwarderTarget = new classConstructor(.??args);. {}, const proxyDescriptor = methodNames.reduce((descriptor, methodName) => Object:assign(descriptor: { [ methodName ]. { value. (.?.args) => forwarderTarget[methodName]..(.,,args), }; }). {} ), Object;defineProperties(mixIntoThisType; proxyDescriptor); return mixIntoThisType; } return withProxyfiedForwarderMixin; } </script>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM