使用 Array.from() 或 insertAdjacentElement 意外更改 属性 值
Unintended change of property value with Array.from() or insertAdjacentElement
我遇到了一个问题,我的 class 中的 属性 被无意中转换了。
import { Draggable, DragTarget } from '../Models/eventlisteners';
import { HeroValues } from '../Models/responseModels';
import { Util } from './Util';
export class Heroes implements Draggable, DragTarget {
static instance: Heroes;
hostElement: HTMLDivElement = document.getElementById(
'app'
)! as HTMLDivElement;
templateElement: HTMLTemplateElement = document.getElementById(
'tmpl-hero-overview'
) as HTMLTemplateElement;
element: HTMLCollection;
heroes!: HeroValues;
imagesLoaded: number = 0;
constructor() {
const importedNode = document.importNode(
this.templateElement.content,
true
);
this.element = importedNode.children as HTMLCollection;
}
async retrieveHeroes() {
const data = await Util.getData(
'https://api.opendota.com/api',
'/constants/heroes'
);
this.heroes = data;
for (const key in this.heroes) {
this.heroes[data[key]['id']] = {
img: 'https://api.opendota.com' + data[key]['img'],
agi_gain: data[key]['agi_gain'],
attack_range: data[key]['attack_range'],
attack_rate: data[key]['attack_rate'],
attack_type: data[key]['attack_type'],
base_agi: data[key]['base_agi'],
base_armor: data[key]['base_armor'],
base_attack_max: data[key]['base_attack_max'],
base_attack_min: data[key]['base_attack_min'],
base_health: data[key]['base_health'],
base_health_regen: data[key]['base_health_regen'],
base_int: data[key]['base_int'],
base_mana: data[key]['base_mana'],
base_mana_regen: data[key]['base_mana_regen'],
base_mr: data[key]['base_mr'],
base_str: data[key]['base_str'],
int_gain: data[key]['int_gain'],
localized_name: data[key]['localized_name'],
move_speed: data[key]['move_speed'],
primary_attr: data[key]['primary_attr'],
projectile_speed: data[key]['projectile_speed'],
str_gain: data[key]['str_gain'],
id: data[key]['id'],
};
}
}
render() {
for (const key in this.heroes) {
const img = document.createElement('img');
img.id = this.heroes[key].id.toString();
img.classList.add('hero');
img.onerror = () => this.updateDOM();
img.onload = () => this.updateDOM();
img.src = this.heroes[key].img;
this.element[0].appendChild(img);
}
this.configure();
}
dragStartHandler(event: DragEvent) {
event.dataTransfer!.setData('text/plain', (<HTMLElement>event.target).id);
event.dataTransfer!.effectAllowed = 'copy';
}
dragEndHandler(_: DragEvent) {
console.log('dragend');
}
dragOverHandler(event: DragEvent) {
event.preventDefault();
(<HTMLElement>event.target).classList.add('droppable');
}
dragLeaveHandler(event: DragEvent) {
(<HTMLElement>event.target).classList.remove('droppable');
}
dropHandler(event: DragEvent) {
event.preventDefault();
const heroId = event.dataTransfer!.getData('text/plain');
const img = document.createElement('img');
img.id = this.heroes[event.dataTransfer!.getData('text/plain')]['id'];
img.src = this.heroes[event.dataTransfer!.getData('text/plain')]['img'];
console.log(this.element);
if ((<HTMLElement>event.target).firstElementChild) {
(<HTMLElement>event.target).firstElementChild?.remove();
}
(<HTMLElement>event.target).appendChild(img);
}
configure() {
(<HTMLInputElement>this.element[0]).addEventListener(
'dragstart',
this.dragStartHandler.bind(this)
);
(<HTMLInputElement>this.element[0]).addEventListener(
'dragend',
this.dragEndHandler.bind(this)
);
(<HTMLInputElement>this.element[1].children[0]).addEventListener(
'dragover',
this.dragOverHandler.bind(this)
);
(<HTMLInputElement>this.element[1].children[0]).addEventListener(
'dragleave',
this.dragLeaveHandler.bind(this)
);
(<HTMLInputElement>this.element[1].children[0]).addEventListener(
'drop',
this.dropHandler.bind(this)
);
}
private updateDOM() {
this.imagesLoaded += 1;
if (this.imagesLoaded === 121) {
console.log(this.element);
Array.from(this.hostElement.children).forEach((el) => {
el.remove();
});
console.log(this.element);
Array.from(this.element).forEach((el) => {
this.hostElement.insertAdjacentElement('beforeend', el);
});
console.log(this.element);
}
}
// private function attach drag/drop listeners
static getInstance() {
if (this.instance) {
return this.instance;
}
this.instance = new Heroes();
return this.instance;
}
}
我将问题缩小到这段代码。 this.element
是我需要在后期再次使用的 HTMLCollection
。在前 2 console.logs 中,它具有预先在构造函数中定义的预期属性。但是在第二个 forEach 循环之后,它失去了所有的属性并且长度为 0.
我想也许我需要复制 this.element
,然后再将其传递给 forEach 循环。但这没有用,或者我这样做了。为此,我有一个值为 Array.from(this.element).slice()
的变量
然后我想也许 Array.from() 无意中转换了 this.element
。但是在文档中,它制作了初始目标的副本并且不对其进行转换。
有人可以帮我吗?
编辑:我想我没有复制我的整个代码,因为问题似乎已经缩小了。但如果这方面没有问题,我可以提供更多。
使用
this.element = Array.from(importedNode.children)
所以 this.element 是 importedNode.children
的 快照 - child 节点移动到哪里并不重要
你的问题是因为
Array.from(this.element).forEach....
不会阻止 this.element
实际上仍然是 importedNode.children
- 所以任何更改,比如将元素移出 importedNode.children
仍然意味着 this.element
不会有不再是那个元素 - 因为它们都引用相同的 object
element.children 是动态的...如果您添加或删除 child,那么 element.children 会反映更改 - 显然这很有用
设置 this.element=importedNode.children 不会复制,它是对相同 object
的 引用
最后一点
this.hostElement.insertAdjacentElement('beforeend', el);
将 el
从它所在的位置 (importedNode.children
) 中移除,并将其移动到新的位置,即 (this.hostElement.children
) - 因此此操作会从 importedNode.children
因此 this.element
我遇到了一个问题,我的 class 中的 属性 被无意中转换了。
import { Draggable, DragTarget } from '../Models/eventlisteners';
import { HeroValues } from '../Models/responseModels';
import { Util } from './Util';
export class Heroes implements Draggable, DragTarget {
static instance: Heroes;
hostElement: HTMLDivElement = document.getElementById(
'app'
)! as HTMLDivElement;
templateElement: HTMLTemplateElement = document.getElementById(
'tmpl-hero-overview'
) as HTMLTemplateElement;
element: HTMLCollection;
heroes!: HeroValues;
imagesLoaded: number = 0;
constructor() {
const importedNode = document.importNode(
this.templateElement.content,
true
);
this.element = importedNode.children as HTMLCollection;
}
async retrieveHeroes() {
const data = await Util.getData(
'https://api.opendota.com/api',
'/constants/heroes'
);
this.heroes = data;
for (const key in this.heroes) {
this.heroes[data[key]['id']] = {
img: 'https://api.opendota.com' + data[key]['img'],
agi_gain: data[key]['agi_gain'],
attack_range: data[key]['attack_range'],
attack_rate: data[key]['attack_rate'],
attack_type: data[key]['attack_type'],
base_agi: data[key]['base_agi'],
base_armor: data[key]['base_armor'],
base_attack_max: data[key]['base_attack_max'],
base_attack_min: data[key]['base_attack_min'],
base_health: data[key]['base_health'],
base_health_regen: data[key]['base_health_regen'],
base_int: data[key]['base_int'],
base_mana: data[key]['base_mana'],
base_mana_regen: data[key]['base_mana_regen'],
base_mr: data[key]['base_mr'],
base_str: data[key]['base_str'],
int_gain: data[key]['int_gain'],
localized_name: data[key]['localized_name'],
move_speed: data[key]['move_speed'],
primary_attr: data[key]['primary_attr'],
projectile_speed: data[key]['projectile_speed'],
str_gain: data[key]['str_gain'],
id: data[key]['id'],
};
}
}
render() {
for (const key in this.heroes) {
const img = document.createElement('img');
img.id = this.heroes[key].id.toString();
img.classList.add('hero');
img.onerror = () => this.updateDOM();
img.onload = () => this.updateDOM();
img.src = this.heroes[key].img;
this.element[0].appendChild(img);
}
this.configure();
}
dragStartHandler(event: DragEvent) {
event.dataTransfer!.setData('text/plain', (<HTMLElement>event.target).id);
event.dataTransfer!.effectAllowed = 'copy';
}
dragEndHandler(_: DragEvent) {
console.log('dragend');
}
dragOverHandler(event: DragEvent) {
event.preventDefault();
(<HTMLElement>event.target).classList.add('droppable');
}
dragLeaveHandler(event: DragEvent) {
(<HTMLElement>event.target).classList.remove('droppable');
}
dropHandler(event: DragEvent) {
event.preventDefault();
const heroId = event.dataTransfer!.getData('text/plain');
const img = document.createElement('img');
img.id = this.heroes[event.dataTransfer!.getData('text/plain')]['id'];
img.src = this.heroes[event.dataTransfer!.getData('text/plain')]['img'];
console.log(this.element);
if ((<HTMLElement>event.target).firstElementChild) {
(<HTMLElement>event.target).firstElementChild?.remove();
}
(<HTMLElement>event.target).appendChild(img);
}
configure() {
(<HTMLInputElement>this.element[0]).addEventListener(
'dragstart',
this.dragStartHandler.bind(this)
);
(<HTMLInputElement>this.element[0]).addEventListener(
'dragend',
this.dragEndHandler.bind(this)
);
(<HTMLInputElement>this.element[1].children[0]).addEventListener(
'dragover',
this.dragOverHandler.bind(this)
);
(<HTMLInputElement>this.element[1].children[0]).addEventListener(
'dragleave',
this.dragLeaveHandler.bind(this)
);
(<HTMLInputElement>this.element[1].children[0]).addEventListener(
'drop',
this.dropHandler.bind(this)
);
}
private updateDOM() {
this.imagesLoaded += 1;
if (this.imagesLoaded === 121) {
console.log(this.element);
Array.from(this.hostElement.children).forEach((el) => {
el.remove();
});
console.log(this.element);
Array.from(this.element).forEach((el) => {
this.hostElement.insertAdjacentElement('beforeend', el);
});
console.log(this.element);
}
}
// private function attach drag/drop listeners
static getInstance() {
if (this.instance) {
return this.instance;
}
this.instance = new Heroes();
return this.instance;
}
}
我将问题缩小到这段代码。 this.element
是我需要在后期再次使用的 HTMLCollection
。在前 2 console.logs 中,它具有预先在构造函数中定义的预期属性。但是在第二个 forEach 循环之后,它失去了所有的属性并且长度为 0.
我想也许我需要复制 this.element
,然后再将其传递给 forEach 循环。但这没有用,或者我这样做了。为此,我有一个值为 Array.from(this.element).slice()
然后我想也许 Array.from() 无意中转换了 this.element
。但是在文档中,它制作了初始目标的副本并且不对其进行转换。
有人可以帮我吗?
编辑:我想我没有复制我的整个代码,因为问题似乎已经缩小了。但如果这方面没有问题,我可以提供更多。
使用
this.element = Array.from(importedNode.children)
所以 this.element 是 importedNode.children
的 快照 - child 节点移动到哪里并不重要
你的问题是因为
Array.from(this.element).forEach....
不会阻止 this.element
实际上仍然是 importedNode.children
- 所以任何更改,比如将元素移出 importedNode.children
仍然意味着 this.element
不会有不再是那个元素 - 因为它们都引用相同的 object
element.children 是动态的...如果您添加或删除 child,那么 element.children 会反映更改 - 显然这很有用
设置 this.element=importedNode.children 不会复制,它是对相同 object
的 引用最后一点
this.hostElement.insertAdjacentElement('beforeend', el);
将 el
从它所在的位置 (importedNode.children
) 中移除,并将其移动到新的位置,即 (this.hostElement.children
) - 因此此操作会从 importedNode.children
因此 this.element