javascript - 快速调用点击事件时随机出现类型错误
问题描述
我正在创建一个小的 javascript 小程序,当新按钮之前没有被单击时,它只会生成一个新按钮。但是,当您快速单击按钮时,我随机得到“未捕获的 TypeError:无法读取未定义的属性 'innerHTML'”。为什么会出现这种情况?
代码:“ https://codepen.io/fyun89/pen/PgWMNg?editors=1111 ”
let btn = document.getElementsByClassName('btn');
let container = document.getElementsByClassName('container');
const action = function(e) {
// set up new button
let newBtn = document.createElement('button');
newBtn.innerHTML = 'this is has been clicked <span>0</span> times';
// for new button decoration
let color1 = Math.min(Math.floor((Math.random() * 1000)/3), 255);
let color2 = Math.min(Math.floor((Math.random() * 1000)/3), 255);
let color3 = Math.min(Math.floor((Math.random() * 1000)/3), 255);
// if the button hasn't been clicked
if (e.target.children[0].innerHTML < 1) {
container[0].appendChild(newBtn);
// target the last created child - button
let currentElem = container[0].children;
currentElem[currentElem.length - 1].setAttribute('class', 'btn');
currentElem[currentElem.length - 1].style.backgroundColor = `rgb(${color1}, ${color2}, ${color3})`;
currentElem[currentElem.length - 1].addEventListener('click', action);
}
// get the current element's count
let numb = Number(e.target.children[0].innerHTML);
e.target.children[0].innerHTML = numb + 1;
}
for (let i = 0; i < btn.length; i++) {
btn[i].addEventListener('click', action);
}
解决方案
您可以通过单击数字/跨度标签来重现该问题。
span 标签没有子标签,因此会引发错误。
你可以在这里阅读更多关于绑定事件如何从我们心爱的 CodePen 创造者那里传播到它的孩子的信息: https ://css-tricks.com/slightly-careful-sub-elements-clickable-things/
TLDR ; 要么检查你正在使用的元素是预期的元素,要么添加一些 CSS 以防止向下传播(CSS 控制这个恕我直言......)
✨⭐ 或者只是在您的代码中替换e.target
为e.currentTarget
,以便使用该绑定元素,而不是包含其中包含的任何内容。
可能的修复
CSS 方法
如果按钮内部也不需要点击处理,则可以执行此操作。
.btn > * {
pointer-events: none;
}
JS方法
CSS 技巧文章中的评论者提到使用currentTarget
.
我试过了,它似乎工作
只需在您的代码中替换e.target
为。e.currentTarget
另一个 JS 方法
const tagName = e.target.tagName.toLowerCase();
if (tagName === 'button') {
// we have the button
}
else if (tagName === 'SPAN') {
// we have the span
}
else {
console.error('Unrecognized element', tagName, e.target)
}
错误的调试
发生错误时,请注意目标不是整个按钮,它只是跨度,并且跨度没有任何子级。
当您单击按钮中的数字(跨度)时会发生这种情况
此外
从您的代码中有一个地方,事件处理程序不会在很短的时间内绑定,但可能存在于 DOM 中。我想由于渲染,您甚至可能无法单击以找到此错误,特别是因为它仅存在于新按钮上,但它以编程方式存在于代码中的时间很短。
您应该在使用之前绑定 click 事件appendChild
以将其添加到 DOM。在您设置样式并添加事件处理之前,我会避免将您的元素添加到 DOM。
红色区域表示元素存在于 DOM 中但没有点击处理程序的时间。
同行评审和重构
清理第一轮——也许这个重构会有所帮助?
如果变量只分配一次并且不希望重新分配,我建议您使用const
而不是。let
尽管这听起来可能毫无用处,但它可以帮助阐明您定义的变量的意图。
我见过很多时候人们有范围和代码问题,只是简单地通过和设置明确的let
vsconst
有助于阐明这个问题。
在下面的重构中,我已更改let
为const
适用的地方,并对代码进行了 DRY,并使用描述性变量名称使某些部分更具可读性。
const btn = document.getElementsByClassName('btn');
const container = document.getElementsByClassName('container');
// name it for readability, but then run it
// let hosting bring action up
(function bindCurrentButtons() {
for (let i = 0; i < btn.length; i++) {
btn[i].addEventListener('click', action);
}
})();
function action(e) {
// set up new button
const newBtn = document.createElement('button');
newBtn.innerHTML = 'this is has been clicked <span>0</span> times';
// for new button decoration
const randomColor = () => Math.min(Math.floor((Math.random() * 1000) / 3), 255);
const colorRed = randomColor();
const colorGreen = randomColor();
const colorBlue = randomColor();
// if the button hasn't been clicked
if (e.target.children[0].innerHTML < 1) {
// target the last created child - button
const currentElemChildren = container[0].children;
const elementIndex = currentElemChildren.length - 1;
const currentElem = currentElemChildren[elementIndex];
currentElem.setAttribute('class', 'btn');
currentElem.style.backgroundColor = `rgb(${colorRed}, ${colorGreen}, ${colorBlue})`;
currentElem.addEventListener('click', action);
container[0].appendChild(newBtn);
}
// get the current element's count
const numb = Number(e.target.children[0].innerHTML);
e.target.children[0].innerHTML = numb + 1;
}
清理第 2 轮 - 继续重构
这是一个更新的 CodePen。我确实删除了这些类以进一步简化它。
https://codepen.io/fyun89/pen/PgWMNg?editors=1111
const buttons = document.querySelectorAll('button');
const container = document.getElementById('container');
// name it for readability, but then run it right away
// let hosting bring other functions up
(function bindCurrentButtons() {
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', action);
}
})();
function buttonCount(buttonElement) {
const span = buttonElement.querySelector('span');
return Number(span.innerText);
}
function incrementCounter(buttonElement) {
const span = buttonElement.querySelector('span');
const number = buttonCount(buttonElement) + 1;
span.innerText = number.toString();
}
const randomColor = () => Math.min(Math.floor((Math.random() * 1000) / 3), 255);
const randomRgbColor = () => `rgb(${randomColor()}, ${randomColor()}, ${randomColor()})`;
function action(e) {
const button = e.currentTarget;
const currentCount = buttonCount(button);
incrementCounter(button);
if (currentCount === 0) {
const newButton = createNewButton();
container.appendChild(newButton);
}
}
function createNewButton() {
// set up new button
const newButton = document.createElement('button');
newButton.innerHTML = 'this is has been clicked <span>0</span> times';
newButton.style.backgroundColor = randomRgbColor();
newButton.addEventListener('click', action);
return newButton;
}
第 3 轮重构 - JS 类
我已将 HTML 更改为不以按钮开头。现在在 Javascript 中创建了初始按钮。它现在以随机颜色按钮而不是橙色按钮开始。该类现在管理状态/计数器。我还删除了跨度,因为它不再需要用作数据存储。
https://codepen.io/codyswartz/pen/GLmbvW?editors=0011
const randomColor = () => Math.min(Math.floor((Math.random() * 1000) / 3), 255);
const randomRgbColor = () => `rgb(${randomColor()}, ${randomColor()}, ${randomColor()})`;
class CounterButton {
constructor(containerElement) {
this.counter = 0;
this.container = containerElement;
this.button = this.createElement();
this.updateCounter();
this.randomBackgroundColor();
this.bindClickEvent();
this.addToDom();
}
createElement() {
return document.createElement('button');
}
updateCounter() {
this.button.innerHTML = `this is has been clicked ${this.counter} times`;
}
randomBackgroundColor() {
this.button.style.backgroundColor = randomRgbColor();
}
bindClickEvent() {
this.button.addEventListener('click', this.clickEvent.bind(this));
}
clickEvent() {
if(this.counter === 0) {
new CounterButton(this.container);
}
this.counter++;
this.updateCounter();
}
addToDom() {
this.container.appendChild(this.button);
}
}
const container = document.getElementById('container');
new CounterButton(container);
推荐阅读
- mysql - 根据给定表中的外键查找多个表中的行数
- git - 在日志中重述提交
- postgresql - 优化包含测试数据的 dockerized postgres 数据库的(磁盘/内存)
- python-3.x - python将文件下载到内存中并处理断开的链接
- formula - 使用公式点击按钮获取价格
- oracle - 如何创建将记录表创建的 Oracle 事件触发器
- c# - 尝试单击菜单选项并带我进入新视图
- java - 如何在我不想修改的 json 字段中保留一个值
- c++ - 为什么调整指向向量* 的指针(在向量* 数组中)的大小比调整向量(在向量数组中)更快?
- java - javafx:在intellij中制作控制器