- 首要任务是让代码运行。
- 第二个任务将详细解释实际发生的情况以及为什么根本不需要使用 JavaScript
class 运行的方法。
class ListItems{
constructor(listItemQuery) {
const listItems = this;
listItemQuery.forEach((listItem) => {
listItem.addEventListener('click', listItems.clickGreen, false);
listItem.addEventListener('onmousedown', listItems.noSelectText, false);
});
}
noSelectText(evt) {
evt.preventDefault;
return false;
}
clickGreen(evt) {
const listItem = evt.currentTarget;
if (evt.ctrlKey || evt.metaKey) {
listItem.classList.add('selected');
return;
}
const firstLevelListItems = Array.from(listItem.parentNode.children);
firstLevelListItems.forEach(function(item) {
item.classList.remove('selected');
});
listItem.classList.add('selected');
}
}
function initializeListItems() {
new ListItems(document.querySelectorAll('li'));
}
window.addEventListener('load', initializeListItems, false);
.as-console-wrapper { max-height: 100%!important; top: 0; }
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
.selected {
background: #0f0;
}
li {
cursor: pointer;
}
</style>
</head>
<body>
Кликни на элемент списка, чтобы выделить его.
<br/>
<ul id="ul">
<li>Кристофер Робин</li>
<li>Винни Пух</li>
<li>Тигра</li>
<li>Кенга</li>
<li>Кролик. Просто Кролик.</li>
</ul>
</body>
</html>
为了更好地识别 OP 提供的示例的不同部分,将现在的可执行代码分开。
如果使用 JS 类,最好让构造函数只负责最必要的引导部分。因此,我们有一个初始化步骤,将 DOM 查询直接传递给构造函数。
接下来,将类命名为 List 具有误导性,因为 OP 主要将其用作结构元素,其中包含专门操作列表项的功能。让我们将其重命名为ListItems。
每个HTMLLIElement 然后添加两个非常自己的事件处理程序到自己。在 OP 的示例代码中,它是两个箭头函数,每个都接受 event 参数但不将其传递到预期的类方法。
代码可能已经这样修复了......
list.addEventListener('click', (event) => this.click_green(event));
list.addEventListener('onmousedown', (event) => this.no_select_text(event));
...但更直观的方法是直接将类方法分配为事件处理程序,例如...
listItem.addEventListener('click', this.clickGreen, false);
listItem.addEventListener('onmousedown', this.noSelectText, false);
到目前为止,由于这两种方法的 this 上下文,人们开始与 class 方法作斗争。
click_green(event) { ... this.classList.add('selected') ... } 中的this 不是HTMLLIElement,但this 始终是加载时创建的OP 的List 类的唯一实例。
evt 对象与固定代码示例一样提供 target 和 currentTarget 对象。使用给定的 HTML 结构,两者都包含对列表项的引用,例如点击。
一旦列表项不再仅包含文本节点,而且还是其他可能成为事件触发目标的 HTML 元素的父级,evt.currentTarget 就是唯一的事实来源,因为它添加了两个事件处理程序。
现在确定单击的列表项不想只查询父列表的所有列表项,因为列表项可能是其他列表的容器等等。相反,一个人想要获取列表项的所有兄弟姐妹。
通过parentNode 定位他们应该知道parentNode.childNodes 确实列出了任何节点,包括文本节点,例如来自提供的示例代码的新行。一个总是与parentNode.children 一起保存,这是一个仅列出元素节点的HTMLCollection。
将这样的集合转换为数组...Array.from(listItem.parentNode.children);...对于通过数组方法处理其项目是必要的。
关于如何使 OP 的代码按预期运行,没有什么可说的了。
希望对代码有更好的理解,甚至可以摆脱class 语法,用另一种构建代码的方法取而代之...
// list item module
//
// - written as immediately invoked function expression ...
// - ... mainly for encapsulation of domain specific code.
//
const ListItems = (function () {
function preventSelection(evt) {
evt.preventDefault;
return false;
}
function selectItem(evt) {
const listItem = evt.currentTarget;
if (evt.ctrlKey || evt.metaKey) {
listItem.classList.add('selected');
} else {
const firstLevelListItems = Array.from(listItem.parentNode.children);
firstLevelListItems.forEach((item) => {
item.classList.remove('selected');
});
listItem.classList.add('selected');
}
}
function initializeListItems() {
document.querySelectorAll('li').forEach((listItem) => {
listItem.addEventListener('click', selectItem, false);
listItem.addEventListener('onmousedown', preventSelection, false);
})
}
// the module:
return {
initialize: initializeListItems
};
}());
// another task ...
window.addEventListener('load', ListItems.initialize, false);
.as-console-wrapper { max-height: 100%!important; top: 0; }
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
.selected {
background: #0f0;
}
li {
cursor: pointer;
}
</style>
</head>
<body>
Кликни на элемент списка, чтобы выделить его.
<br/>
<ul id="ul">
<li>Кристофер Робин</li>
<li>Винни Пух</li>
<li>Тигра</li>
<li>Кенга</li>
<li>Кролик. Просто Кролик.</li>
</ul>
</body>
</html>