我的问题真的是“在javascript中the lapsed listener problem是可预防的吗?”但显然“问题”这个词造成了一个问题。
维基百科的页面上说,失效的听众问题可以通过向观察者手持weak references的主题来解决。我以前用Java实现过它,它工作得很好,我想我可以用Javascript实现它,但现在我看不出怎么做了。javascript有弱引用吗?我看到有WeakSet
和WeakMap
,它们的名字中有“弱”,但据我所知,它们似乎对此没有帮助。
这是一个显示该问题的典型案例的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);
});
在这个小提琴中,可观察的状态是一个布尔值,您可以添加和删除查看和控制它的复选框,所有这些都由侦听器保持同步。问题是,当您删除其中一个控制器时,它的侦听器并没有消失:侦听器不断被调用并更新控制器复选框,并阻止复选框为GCed,即使复选框不再位于DOM中,否则为GCable。您可以在javascript控制台中看到这一点,因为侦听器回调会将一条消息打印到控制台。
相反,我希望控制器DOM节点及其关联值侦听器在从DOM中删除节点时变为GCable。从概念上讲,DOM节点应该拥有侦听器,而可观察对象应该持有对侦听器的弱引用。有没有一种干净利落的方法来实现这一点?
我知道我可以通过让x
按钮显式地删除侦听器和DOM子树来解决我的问题,但是如果应用程序中的一些其他代码后来删除了包含我的控制器节点的DOM的一部分,例如通过执行document.body.innerHTML = ''
,这就没有什么帮助了。我想进行一些设置,这样,当发生这种情况时,我创建的所有DOM节点和侦听器都会被释放并成为GCable。有什么办法吗?
发布于 2018-08-19 02:05:15
Custom_elements为lapsed listener problem提供了解决方案。它们在Chrome和Safari中受支持,(截至2018年8月)将很快在Firefox和Edge上受支持。
我用超文本标记语言做了一个jsfiddle:
<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();
}
});
这里的关键点是,无论出于何种原因,当<my-control>
从DOM中删除时,都会保证调用disconnectedCallback()
。我们使用它来删除监听器。
现在,您可以使用以下命令添加第一个<my-control>
:
const plusButton = document.getElementById("thePlusButton");
plusButton.addEventListener("click", () => {
const myControl = document.createElement("my-control");
document.body.insertBefore(myControl, plusButton);
});
(我在观看this video时想到了这个答案,演讲者解释了自定义元素可能有用的其他原因。)
https://stackoverflow.com/questions/43758217
复制相似问题