是迟复的听众可以在JavaScript中预防? -- javascript 领域 和 dom 领域 和 garbage-collection 领域 和 observer-pattern 领域 和 weak-references 领域 相关 的问题

Are lapsed listeners preventable in javascript?


简体版||繁體版
5
vote

问题

中文

我的问题真的"是失效的侦听器问题在javascript中可以预防?" 但显然"问题" 这个词会导致问题。

wikipedia page说失误的侦听器问题可以通过拍摄的主题弱引用对观察者。我以前在Java之前实现了,它很好地工作,我以为我会在JavaScript中实现它,但现在我没有看到如何。 JavaScript甚至是否有弱引用?我看到的是 WeakSet WeakMap 在他们的名字中有"弱" ,但据我所知,他们似乎对此并不有帮助。< / p>

这是一个 jsfiddle 显示一个问题的典型情况。

html:

  <div id="theCurrentValueDiv">current value: false</div> <button id="thePlusButton">+</button>   

javascript:

  'use strict'; console.log("starting"); let createListenableValue = function(initialValue) {   let value = initialValue;   let listeners = [];   return {     // Get the current value.     get: function() {       return value;     },     // Set the value to newValue, and call listener()     // for each listener that has been added using addListener().     set: function(newValue) {       value = newValue;       for (let listener of listeners) {         listener();       }     },     // Add a listener that set(newValue) will call with no args     // after setting value to newValue.     addListener: function(listener) {       listeners.push(listener);       console.log("and now there "+(listeners.length==1?"is":"are")+" "+listeners.length+" listener"+(listeners.length===1?"":"s"));     },   }; };  // createListenable  let theListenableValue = createListenableValue(false);  theListenableValue.addListener(function() {   console.log("    label got value change to "+theListenableValue.get());   document.getElementById("theCurrentValueDiv").innerHTML = "current value: "+theListenableValue.get(); });  let nextControllerId = 0;  let thePlusButton = document.getElementById("thePlusButton"); thePlusButton.addEventListener('click', function() {   let thisControllerId = nextControllerId++;   let anotherDiv = document.createElement('div');   anotherDiv.innerHTML = '<button>x</button><input type="checkbox"> controller '+thisControllerId;   let [xButton, valueCheckbox] = anotherDiv.children;   valueCheckbox.checked = theListenableValue.get();   valueCheckbox.addEventListener('change', function() {     theListenableValue.set(valueCheckbox.checked);   });    theListenableValue.addListener(function() {     console.log("    controller "+thisControllerId+" got value change to "+theListenableValue.get());     valueCheckbox.checked = theListenableValue.get();   });    xButton.addEventListener('click', function() {     anotherDiv.parentNode.removeChild(anotherDiv);     // Oh no! Our listener on theListenableValue has now lapsed;     // it will keep getting called and updating the checkbox that is no longer     // in the DOM, and it will keep the checkbox object from ever being GCed.   });    document.body.insertBefore(anotherDiv, thePlusButton); });   

在此小提琴中,可观察状态是布尔值,您可以添加和删除查看和控制它的复选框,所有这些都通过侦听器保持同步。 问题是当您删除其中一个控制器时,其侦听器不会消失:侦听器不断调用并更新控制器复选框并阻止复选框即使复选框不再在DOM中不再否则可以使用。由于侦听器回调将消息打印到控制台,因此您可以看到JavaScript控制台中发生这种情况。

当我从DOM删除节点时,我喜欢的是,当我从DOM删除节点时,它就是适用于控制器DOM节点及其关联值侦听器。概念上,DOM节点应该拥有侦听器,并且可观察者应持弱引用侦听器。有没有一种干净的方式来实现这个问题?

我知道我可以通过制作 x 按钮与DOM子树一起删除侦听器的按钮来解决问题中的问题,但在某些其他代码中没有帮助应用程序稍后删除包含我的控制器节点的DOM的一部分,例如通过执行 document.body.innerHTML = '' 。我想设置一些东西,这样,当发生这种情况时,我创建的所有DOM节点和侦听器都会发布并变成可粘合的。有没有办法?

英文原文

My question is really "Is the lapsed listener problem preventable in javascript?" but apparently the word "problem" causes a problem.

The wikipedia page says the lapsed listener problem can be solved by the subject holding weak references to the observers. I've implemented that before in Java and it works nicely, and I thought I'd implement it in Javascript, but now I don't see how. Does javascript even have weak references? I see there are WeakSet and WeakMap which have "Weak" in their names, but they don't seem to be helpful for this, as far as I can see.

Here's a jsfiddle showing a typical case of the problem.

The html:

<div id="theCurrentValueDiv">current value: false</div> <button id="thePlusButton">+</button> 

The javascript:

'use strict'; console.log("starting"); let createListenableValue = function(initialValue) {   let value = initialValue;   let listeners = [];   return {     // Get the current value.     get: function() {       return value;     },     // Set the value to newValue, and call listener()     // for each listener that has been added using addListener().     set: function(newValue) {       value = newValue;       for (let listener of listeners) {         listener();       }     },     // Add a listener that set(newValue) will call with no args     // after setting value to newValue.     addListener: function(listener) {       listeners.push(listener);       console.log("and now there "+(listeners.length==1?"is":"are")+" "+listeners.length+" listener"+(listeners.length===1?"":"s"));     },   }; };  // createListenable  let theListenableValue = createListenableValue(false);  theListenableValue.addListener(function() {   console.log("    label got value change to "+theListenableValue.get());   document.getElementById("theCurrentValueDiv").innerHTML = "current value: "+theListenableValue.get(); });  let nextControllerId = 0;  let thePlusButton = document.getElementById("thePlusButton"); thePlusButton.addEventListener('click', function() {   let thisControllerId = nextControllerId++;   let anotherDiv = document.createElement('div');   anotherDiv.innerHTML = '<button>x</button><input type="checkbox"> controller '+thisControllerId;   let [xButton, valueCheckbox] = anotherDiv.children;   valueCheckbox.checked = theListenableValue.get();   valueCheckbox.addEventListener('change', function() {     theListenableValue.set(valueCheckbox.checked);   });    theListenableValue.addListener(function() {     console.log("    controller "+thisControllerId+" got value change to "+theListenableValue.get());     valueCheckbox.checked = theListenableValue.get();   });    xButton.addEventListener('click', function() {     anotherDiv.parentNode.removeChild(anotherDiv);     // Oh no! Our listener on theListenableValue has now lapsed;     // it will keep getting called and updating the checkbox that is no longer     // in the DOM, and it will keep the checkbox object from ever being GCed.   });    document.body.insertBefore(anotherDiv, thePlusButton); }); 

In this fiddle, the observable state is a boolean value, and you can add and remove checkboxes that view and control it, all kept in sync by listeners on it. The problem is that when you remove one of the controllers, its listener doesn't go away: the listener keeps getting called and updating the controller checkbox and prevents the checkbox from being GCed, even though the checkbox is no longer in the DOM and is otherwise GCable. You can see this happening in the javascript console since the listener callback prints a message to the console.

What I'd like instead is for the controller DOM node and its associated value listener to become GCable when I remove the node from the DOM. Conceptually, the DOM node should own the listener, and the observable should hold a weak reference to the listener. Is there a clean way to accomplish that?

I know I can fix the problem in my fiddle by making the x button explicitly remove the listener along with the DOM subtree, but that doesn't help in the case that some other code in the app later removes part of the DOM containing my controller node, e.g. by executing document.body.innerHTML = ''. I'd like set things up so that, when that happens, all the DOM nodes and listeners I created get released and become GCable. Is there a way?

</div
              
         
         

回答列表

0
 
vote

custom_elements 为失效的侦听器问题。他们得到了Chrome和Safari的支持,并且(截至2018年8月),很快就会在Firefox和Edge上得到支持。

我做了一个 jsfiddle 使用html:

  <div id="theCurrentValue">current value: false</div> <button id="thePlusButton">+</button>   

和一个略微修改的 listenableValue ,它现在有能力删除侦听器:

  "use strict"; function createListenableValue(initialValue) {     let value = initialValue;     const listeners = [];     return {         get() { // Get the current value.             return value;         },         set(newValue) { // Set the value to newValue, and call all listeners.             value = newValue;             for (const listener of listeners) {                 listener();             }         },         addListener(listener) { // Add a listener function to  call on set()             listeners.push(listener);             console.log("add: listener count now:  " + listeners.length);             return () => { // Function to undo the addListener                 const index = listeners.indexOf(listener);                 if (index !== -1) {                     listeners.splice(index, 1);                 }                 console.log("remove: listener count now:  " + listeners.length);             };         }     }; }; const listenableValue = createListenableValue(false); listenableValue.addListener(() => {     console.log("label got value change to " + listenableValue.get());     document.getElementById("theCurrentValue").innerHTML         = "current value: " + listenableValue.get(); }); let nextControllerId = 0;   

我们现在可以定义自定义html元素 <my-control>

  customElements.define("my-control", class extends HTMLElement {     constructor() {         super();     }     connectedCallback() {         const n = nextControllerId++;         console.log("Custom element " + n + " added to page.");         this.innerHTML =             "<button>x</button><input type="checkbox"> controller "             + n;         this.style.display = "block";         const [xButton, valueCheckbox] = this.children;         xButton.addEventListener("click", () => {             this.parentNode.removeChild(this);         });         valueCheckbox.checked = listenableValue.get();         valueCheckbox.addEventListener("change", () => {             listenableValue.set(valueCheckbox.checked);         });         this._removeListener = listenableValue.addListener(() => {             console.log("controller " + n + " got value change to "                 + listenableValue.get());             valueCheckbox.checked = listenableValue.get();         });     }     disconnectedCallback() {         console.log("Custom element removed from page.");         this._removeListener();     } });   

此处的关键点是 disconnectedCallback() <my-control> 从DOM中删除任何原因时,都会被调用。我们使用它来删除侦听器。

现在可以添加第一个 <my-control>

  const plusButton = document.getElementById("thePlusButton"); plusButton.addEventListener("click", () => {     const myControl = document.createElement("my-control");     document.body.insertBefore(myControl, plusButton); });   

(此答案发生在我身边,而我在观看这个视频,其中扬声器介绍自定义元素可能有用的其他原因。)

 

Custom_elements offer a solution to the lapsed listener problem. They are supported in Chrome and Safari and (as of Aug 2018), are soon to be supported on Firefox and Edge.

I did a jsfiddle with HTML:

<div id="theCurrentValue">current value: false</div> <button id="thePlusButton">+</button> 

And a slightly modified listenableValue, which now has the ability to remove a listener:

"use strict"; function createListenableValue(initialValue) {     let value = initialValue;     const listeners = [];     return {         get() { // Get the current value.             return value;         },         set(newValue) { // Set the value to newValue, and call all listeners.             value = newValue;             for (const listener of listeners) {                 listener();             }         },         addListener(listener) { // Add a listener function to  call on set()             listeners.push(listener);             console.log("add: listener count now:  " + listeners.length);             return () => { // Function to undo the addListener                 const index = listeners.indexOf(listener);                 if (index !== -1) {                     listeners.splice(index, 1);                 }                 console.log("remove: listener count now:  " + listeners.length);             };         }     }; }; const listenableValue = createListenableValue(false); listenableValue.addListener(() => {     console.log("label got value change to " + listenableValue.get());     document.getElementById("theCurrentValue").innerHTML         = "current value: " + listenableValue.get(); }); let nextControllerId = 0; 

We can now define a custom HTML element <my-control>:

customElements.define("my-control", class extends HTMLElement {     constructor() {         super();     }     connectedCallback() {         const n = nextControllerId++;         console.log("Custom element " + n + " added to page.");         this.innerHTML =             "<button>x</button><input type="checkbox"> controller "             + n;         this.style.display = "block";         const [xButton, valueCheckbox] = this.children;         xButton.addEventListener("click", () => {             this.parentNode.removeChild(this);         });         valueCheckbox.checked = listenableValue.get();         valueCheckbox.addEventListener("change", () => {             listenableValue.set(valueCheckbox.checked);         });         this._removeListener = listenableValue.addListener(() => {             console.log("controller " + n + " got value change to "                 + listenableValue.get());             valueCheckbox.checked = listenableValue.get();         });     }     disconnectedCallback() {         console.log("Custom element removed from page.");         this._removeListener();     } }); 

The key point here is that disconnectedCallback() is guaranteed to be called when the <my-control> is removed from the DOM whatever reason. We use it to remove the listener.

You can now add the first <my-control> with:

const plusButton = document.getElementById("thePlusButton"); plusButton.addEventListener("click", () => {     const myControl = document.createElement("my-control");     document.body.insertBefore(myControl, plusButton); }); 

(This answer occurred to me while I was watching this video, where the speaker explains other reasons why custom elements could be useful.)

</div
 
 
0
 
vote

您可以使用突变观察者

提供观看用于DOM树的更改的能力。它被设计为旧的突变事件特征的替代品,该事件是DOM3事件规范的一部分。

如何使用它的示例可以在上加载

  if (window && window.MutationObserver) {   var observer = new MutationObserver(function (mutations) {     if (Object.keys(watch).length < 1) return     for (var i = 0; i < mutations.length; i++) {       if (mutations[i].attributeName === KEY_ATTR) {         eachAttr(mutations[i], turnon, turnoff)         continue       }       eachMutation(mutations[i].removedNodes, function (index, el) {         if (!document.documentElement.contains(el)) turnoff(index, el)       })       eachMutation(mutations[i].addedNodes, function (index, el) {         if (document.documentElement.contains(el)) turnon(index, el)       })     }   })    observer.observe(document.documentElement, {     childList: true,     subtree: true,     attributes: true,     attributeOldValue: true,     attributeFilter: [KEY_ATTR]   }) }   
 

You can use mutation observers which

provides the ability to watch for changes being made to the DOM tree. It is designed as a replacement for the older Mutation Events feature which was part of the DOM3 Events specification.

An example of how this can be used can be found in the code for on-load

if (window && window.MutationObserver) {   var observer = new MutationObserver(function (mutations) {     if (Object.keys(watch).length < 1) return     for (var i = 0; i < mutations.length; i++) {       if (mutations[i].attributeName === KEY_ATTR) {         eachAttr(mutations[i], turnon, turnoff)         continue       }       eachMutation(mutations[i].removedNodes, function (index, el) {         if (!document.documentElement.contains(el)) turnoff(index, el)       })       eachMutation(mutations[i].addedNodes, function (index, el) {         if (document.documentElement.contains(el)) turnon(index, el)       })     }   })    observer.observe(document.documentElement, {     childList: true,     subtree: true,     attributes: true,     attributeOldValue: true,     attributeFilter: [KEY_ATTR]   }) } 
</div
 
 

相关问题

164  你什么时候使用弱舞剧或弱势指长?  ( When would you use a weakhashmap or a weakreference ) 
使用弱引用是我从未见过的事情,所以我试图弄清楚它们的用例以及实现如何工作。您需要使用 WeakHashMap 或 WeakReference 以及它如何使用? ...

26  .NET的弱势事件?  ( Weak events in net ) 
如果对象从对象b侦听事件,则对象b将保持对象。 是否有标准执行弱事件会阻止这种情况? 我知道WPF有一些机制,但我正在寻找没有与WPF相关的东西。 我猜这个解决方案应该在某处使用弱引用。 ...

247  弱点和未能参考之间有什么区别?  ( What is the difference between a weak reference and an unowned reference ) 
swift有: 强引用 弱引用 未能参考 如何与弱引用不同的引用? 使用何时可以安全地使用所直接的参考? 是unowned引用的安全风险,如悬挂指针在c / c ++中? ...

3  迅速的薄弱薄弱  ( Weak reference to closure in swift ) 
我有以下代码来为数据绑定创建可观察属性。它在作品中,所以我不确定最终实施将是什么,我仍然很新来迅速。 uint64_t u_val 4 我想保持对观察者关闭的弱引用。我不想依靠客户来确保在通过捕获列表之前确保关闭/触发器。特别是因为在给定的类上可能存在许多可观察性的属性。 是可以使封闭引用弱在我可观察的类中...

8  其他用途的弱引用?  ( Other uses of weak references ) 
我知道,弱引用是 Memoizing 潜在的大包数据,以及 wikipedia关于弱引用的文章只列出了"在应用程序中追踪当前变量" 和"另一种使用弱引用正在写入缓存" 。 哪些其他情况(比仅仅是"缓存结果" ),使用弱引用是一个好主意 tm ? ...

15  为什么弱指针有用?  ( Why are weak pointers useful ) 
我一直在阅读垃圾收集,寻找包含在我的编程语言中的功能,我遇到了"弱指针" 。来自这里: 弱指针就像指针, 除了弱者的参考 指针不会阻止垃圾 集合,弱指针必须 之前检查了他们的有效性 它们被使用。 弱点与之相互作用 垃圾收集器因为内存 事实上,他们所指的是 有效,但包含不同的 物体...

8  弱方法参数语义  ( Weak method argument semantics ) 
是否有任何方法可以指定特定方法参数具有弱语义? 要详细说明,这是一个按预期工作的Objective-C示例代码: - (void)runTest { __block NSObject *object = [NSObject new]; dispatch_async(dispatch_get...

9  测试弱势推导  ( Testing weakreference ) 
测试Java中弱引用的正确方法是什么? 我的初始想法是执行以下操作: if ( ( input[0] == enemyDestroyer[0][0] && input[1] == enemyDestroyer[0][1] ) || (input[0] == enemyDestroyer[1][0] && inpu...

6  GC不会删除弱键的循环引用吗?  ( Gc doesnt delete circular references in weakkeydictionaries ) 
我有一种情况,我希望只要第一个对象存在,我希望将从一个对象的映射到另一个对象。我的第一个想法是使用弱点。 import weakref import gc class M: pass w = weakref.WeakKeyDictionary() m = M() w[m] = some_other_o...

4  更有效的方法可用在物体上使用弱ref?  ( A more efficient way to use a weakref on an object as a property ) 
我知道Python我可以做一些如下所示: from weakref import ref class A(object): def __init__(self, parent): self._parent = ref(parent) @property def pare...




© 2022 it.wenda123.org All Rights Reserved. 问答之家 版权所有