[英]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
之后具有事件目標的所有方法,如dispatchEvent
, addEventListener
和removeEventListener
。
// 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 要求的其他用例/場景將特此鏈接到......
擴展 Web-API EventTarget
為 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.