使用 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