在元素中保存超时
Saving a Timeout within the Element
我创建了一个带有下拉菜单的 <select multiple>
(如果您对完整代码感兴趣:Codepen 或底部的代码段)以及处理模糊的辅助函数(下面的简化版本)事件(输入焦点丢失,也就是点击其他地方):
function blur(element) {
clearTimeout(eval(element.dataset.timer));
element.dataset.timer = setTimeout(() => {
if (activeElement !== el)
el.style.display = "none";
}, 200).toString();
}
它创建一个超时并将其存储在元素的 dataset-property 中。由于此 属性 仅接受字符串,因此它由 toString()
转换并在清除前一个计时器时由 eval()
转换回来。这不是必需的,因为它在不进行转换和评估时工作得很好:
function blur(element) {
clearTimeout(element.dataset.timer);
element.dataset.timer = setTimeout(() => {
if (activeElement !== el)
el.style.display = "none";
}, 200);
}
无论哪种方式,它都感觉像是一个 hack,我也知道不应该使用 eval()
,这就是为什么我想知道我应该如何解决这个问题。我已经 showed my code to CodeReview 特别提到了这个功能,但没有人抱怨它。这不可能是正确的做法,对吗?
如果相关,这里是完整代码的片段:
convertSelect("001", "Options");
function convertSelect(el_id, name) {
let el = document.getElementById(el_id),
opts = Array.from(el.options);
let input_el = document.createElement('input');
input_el.setAttribute('id', el_id + '_input');
input_el.setAttribute('type', 'text');
input_el.setAttribute('autocomplete', 'off');
input_el.setAttribute('readonly', 'readonly');
input_el.setAttribute('style', `width:${el.offsetWidth}px`);
input_el.addEventListener('focus', () => document.getElementById(el_id + '_span').style.display = "");
input_el.addEventListener('blur', () => blur(el_id));
el.parentNode.insertBefore(input_el, el.nextSibling);
let span_el = document.createElement('span');
span_el.setAttribute('id', el_id + '_span');
span_el.setAttribute('style', `min-width:${(input_el.offsetWidth + 50)}px;margin-top:${input_el.offsetHeight}px;margin-left:-${input_el.offsetWidth}px;position:absolute;border:1px solid grey;display:none;z-index:9999;text-align:left;background:white;max-height:130px;overflow-y:auto;overflow-x:hidden;`);
span_el.addEventListener('mouseout', () => blur(el_id));
span_el.addEventListener('click', () => document.getElementById(el_id + '_input').focus());
input_el.parentNode.insertBefore(span_el, input_el.nextSibling);
opts.forEach(opt => {
let i = opts.indexOf(opt);
let temp_label = document.createElement('label');
temp_label.setAttribute('for', el_id + '_' + i);
let temp_input = document.createElement('input');
temp_input.setAttribute('style', 'width:auto;');
temp_input.setAttribute('type', 'checkbox');
temp_input.setAttribute('id', el_id + '_' + i);
temp_input.checked = opt.selected;
temp_input.disabled = opt.disabled || el.disabled;
temp_input.addEventListener('change', () => check(el_id, name));
temp_label.appendChild(temp_input);
temp_label.appendChild(document.createTextNode(opt.textContent));
span_el.appendChild(temp_label);
});
el.style.display = 'none';
check(el_id, name);
}
function blur(el_id) {
let el = document.getElementById(el_id);
clearTimeout(el.dataset.timer);
el.dataset.timer = setTimeout(() => {
if (document.activeElement.id !== el_id + '_input' && document.activeElement.id !== el_id + '_span')
document.getElementById(el_id + '_span').style.display = "none";
}, 200);
}
function check(el_id, name) {
let el = document.getElementById(el_id),
opts = Array.from(el.options),
select_qty = 0,
select_name;
opts.forEach(opt => {
let i = opts.indexOf(opt),
checkbox = document.getElementById(`${el_id}_${i}`);
el.options[i].selected = checkbox.checked;
if (checkbox.checked) {
select_name = checkbox.parentElement.childNodes[1].textContent;
select_qty++;
}
document.getElementById(`${el_id}_input`).value = select_qty < 1 ? '' : (select_qty > 1 ? `${select_qty} ${name}` : select_name);
});
el.dispatchEvent(new Event('change', { 'bubbles': true }));
}
label {
display: block;
}
input[type="text"]:hover {
cursor: default;
}
<select id="001" multiple>
<option value="2">Option Two</option>
<option value="4">Option Four</option>
<option value="6">Option Six</option>
<option value="8" disabled>Disabled Option</option>
</select>
您能否创建一个全局计时器对象,在其中使用连续键存储超时并仅将键存储在数据集中?
有点像:
var timersObj = {};
//store timeout in object
timersObj["timerIndex_0"] = setTimeout(() => {
if (activeElement !== el)
el.style.display = "none";
}, 200)
element.dataset.timer = "timerIndex_0";
//clear timeout by key
clearTimeout(timersObj[element.dataset.timer])
如果您使用的是静态编译语言,有时 JavaScript 松散的类型装箱/拆箱会让人觉得陌生。不过,您的示例应该没有任何问题。
但是,还有其他方法可以在元素上存储数据,如果您的数据变得更加复杂,存储在 data-attribute 上可能不是最好的主意。
一个选项,只是你data-attribute想法的延伸。您可以使用 JSON 序列化您的数据:
element.dataset.options = JSON.stringify({timerId: setTimeout(....)});
上述的一个缺点是您可以存储的类型仅限于 JSON 可以序列化的类型,但是字符串/整数/数组等可以很好地工作。
JavaScript 中的元素与任何其他对象一样。所以理论上你可以存储为 属性:
element._mytimerid = setTimeout(...`,
使用这种方法,您必须小心所谓的 属性。
闭包是一个流行的选择:
function setupBlur() {
var timerId = null;
element.onBlur = function () {
clearTimeout(timerId);
timerId = setTimeout(.....
}
}
setupBlur();
您也可以在 IIFE 中使用上面的方法,以节省调用 setupBlur:
(function () {
var timerId = null;
element.onBlur = function () {
clearTimeout(timerId);
timerId = setTimeout(.....
}());
一种更现代的在元素上存储数据的方法是使用 WeakMap:
const timerIds = new WeakMap();
.....
const timerId = timerIds.get(element);
clearTimeout(timerId);
timerIds.set(element, setTimer(.....));
我创建了一个带有下拉菜单的 <select multiple>
(如果您对完整代码感兴趣:Codepen 或底部的代码段)以及处理模糊的辅助函数(下面的简化版本)事件(输入焦点丢失,也就是点击其他地方):
function blur(element) {
clearTimeout(eval(element.dataset.timer));
element.dataset.timer = setTimeout(() => {
if (activeElement !== el)
el.style.display = "none";
}, 200).toString();
}
它创建一个超时并将其存储在元素的 dataset-property 中。由于此 属性 仅接受字符串,因此它由 toString()
转换并在清除前一个计时器时由 eval()
转换回来。这不是必需的,因为它在不进行转换和评估时工作得很好:
function blur(element) {
clearTimeout(element.dataset.timer);
element.dataset.timer = setTimeout(() => {
if (activeElement !== el)
el.style.display = "none";
}, 200);
}
无论哪种方式,它都感觉像是一个 hack,我也知道不应该使用 eval()
,这就是为什么我想知道我应该如何解决这个问题。我已经 showed my code to CodeReview 特别提到了这个功能,但没有人抱怨它。这不可能是正确的做法,对吗?
如果相关,这里是完整代码的片段:
convertSelect("001", "Options");
function convertSelect(el_id, name) {
let el = document.getElementById(el_id),
opts = Array.from(el.options);
let input_el = document.createElement('input');
input_el.setAttribute('id', el_id + '_input');
input_el.setAttribute('type', 'text');
input_el.setAttribute('autocomplete', 'off');
input_el.setAttribute('readonly', 'readonly');
input_el.setAttribute('style', `width:${el.offsetWidth}px`);
input_el.addEventListener('focus', () => document.getElementById(el_id + '_span').style.display = "");
input_el.addEventListener('blur', () => blur(el_id));
el.parentNode.insertBefore(input_el, el.nextSibling);
let span_el = document.createElement('span');
span_el.setAttribute('id', el_id + '_span');
span_el.setAttribute('style', `min-width:${(input_el.offsetWidth + 50)}px;margin-top:${input_el.offsetHeight}px;margin-left:-${input_el.offsetWidth}px;position:absolute;border:1px solid grey;display:none;z-index:9999;text-align:left;background:white;max-height:130px;overflow-y:auto;overflow-x:hidden;`);
span_el.addEventListener('mouseout', () => blur(el_id));
span_el.addEventListener('click', () => document.getElementById(el_id + '_input').focus());
input_el.parentNode.insertBefore(span_el, input_el.nextSibling);
opts.forEach(opt => {
let i = opts.indexOf(opt);
let temp_label = document.createElement('label');
temp_label.setAttribute('for', el_id + '_' + i);
let temp_input = document.createElement('input');
temp_input.setAttribute('style', 'width:auto;');
temp_input.setAttribute('type', 'checkbox');
temp_input.setAttribute('id', el_id + '_' + i);
temp_input.checked = opt.selected;
temp_input.disabled = opt.disabled || el.disabled;
temp_input.addEventListener('change', () => check(el_id, name));
temp_label.appendChild(temp_input);
temp_label.appendChild(document.createTextNode(opt.textContent));
span_el.appendChild(temp_label);
});
el.style.display = 'none';
check(el_id, name);
}
function blur(el_id) {
let el = document.getElementById(el_id);
clearTimeout(el.dataset.timer);
el.dataset.timer = setTimeout(() => {
if (document.activeElement.id !== el_id + '_input' && document.activeElement.id !== el_id + '_span')
document.getElementById(el_id + '_span').style.display = "none";
}, 200);
}
function check(el_id, name) {
let el = document.getElementById(el_id),
opts = Array.from(el.options),
select_qty = 0,
select_name;
opts.forEach(opt => {
let i = opts.indexOf(opt),
checkbox = document.getElementById(`${el_id}_${i}`);
el.options[i].selected = checkbox.checked;
if (checkbox.checked) {
select_name = checkbox.parentElement.childNodes[1].textContent;
select_qty++;
}
document.getElementById(`${el_id}_input`).value = select_qty < 1 ? '' : (select_qty > 1 ? `${select_qty} ${name}` : select_name);
});
el.dispatchEvent(new Event('change', { 'bubbles': true }));
}
label {
display: block;
}
input[type="text"]:hover {
cursor: default;
}
<select id="001" multiple>
<option value="2">Option Two</option>
<option value="4">Option Four</option>
<option value="6">Option Six</option>
<option value="8" disabled>Disabled Option</option>
</select>
您能否创建一个全局计时器对象,在其中使用连续键存储超时并仅将键存储在数据集中? 有点像:
var timersObj = {};
//store timeout in object
timersObj["timerIndex_0"] = setTimeout(() => {
if (activeElement !== el)
el.style.display = "none";
}, 200)
element.dataset.timer = "timerIndex_0";
//clear timeout by key
clearTimeout(timersObj[element.dataset.timer])
如果您使用的是静态编译语言,有时 JavaScript 松散的类型装箱/拆箱会让人觉得陌生。不过,您的示例应该没有任何问题。
但是,还有其他方法可以在元素上存储数据,如果您的数据变得更加复杂,存储在 data-attribute 上可能不是最好的主意。
一个选项,只是你data-attribute想法的延伸。您可以使用 JSON 序列化您的数据:
element.dataset.options = JSON.stringify({timerId: setTimeout(....)});
上述的一个缺点是您可以存储的类型仅限于 JSON 可以序列化的类型,但是字符串/整数/数组等可以很好地工作。
JavaScript 中的元素与任何其他对象一样。所以理论上你可以存储为 属性:
element._mytimerid = setTimeout(...`,
使用这种方法,您必须小心所谓的 属性。
闭包是一个流行的选择:
function setupBlur() {
var timerId = null;
element.onBlur = function () {
clearTimeout(timerId);
timerId = setTimeout(.....
}
}
setupBlur();
您也可以在 IIFE 中使用上面的方法,以节省调用 setupBlur:
(function () {
var timerId = null;
element.onBlur = function () {
clearTimeout(timerId);
timerId = setTimeout(.....
}());
一种更现代的在元素上存储数据的方法是使用 WeakMap:
const timerIds = new WeakMap();
.....
const timerId = timerIds.get(element);
clearTimeout(timerId);
timerIds.set(element, setTimer(.....));